From 9003448b0b3afc14297efd22cd847548afc83639 Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Mon, 21 Nov 2022 08:56:16 -0800 Subject: [PATCH 1/6] Add advanced options to LN payout form --- .../TradeBox/Forms/LightningPayout.tsx | 273 +++++++++++++++--- .../TradeBox/Prompts/RoutingFailed.tsx | 1 + frontend/src/components/TradeBox/index.tsx | 9 +- frontend/src/models/Coordinator.model.ts | 1 + frontend/static/federation.json | 3 +- frontend/static/lnproxies.json | 7 + 6 files changed, 257 insertions(+), 37 deletions(-) create mode 100644 frontend/static/lnproxies.json diff --git a/frontend/src/components/TradeBox/Forms/LightningPayout.tsx b/frontend/src/components/TradeBox/Forms/LightningPayout.tsx index febfec4dc..f2afa3811 100644 --- a/frontend/src/components/TradeBox/Forms/LightningPayout.tsx +++ b/frontend/src/components/TradeBox/Forms/LightningPayout.tsx @@ -1,28 +1,60 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { Grid, Typography, TextField } from '@mui/material'; +import { + Box, + Grid, + Typography, + TextField, + Tooltip, + FormControlLabel, + Checkbox, + useTheme, + Collapse, + Switch, +} from '@mui/material'; import { Order } from '../../../models'; import WalletsButton from '../WalletsButton'; import { LoadingButton } from '@mui/lab'; import { pn } from '../../../utils'; +import { RoundaboutRight, Route, SelfImprovement } from '@mui/icons-material'; +import { apiClient } from '../../../services/api'; + +import lnproxies from '../../../../static/lnproxies.json'; + export interface LightningForm { invoice: string; - routingBudget: number; + amount: number; + advancedOptions: boolean; + useCustomBudget: boolean; + routingBudgetUnit: 'PPM' | 'Sats'; + routingBudgetPPM: number; + routingBudgetSats: number; badInvoice: string; useLnproxy: boolean; - lnproxyServer: string; - lnproxyBudget: number; + lnproxyInvoice: string; + lnproxyServer: number; + lnproxyBudgetUnit: 'PPM' | 'Sats'; + lnproxyBudgetPPM: number; + lnproxyBudgetSats: number; badLnproxy: string; } export const defaultLightning: LightningForm = { invoice: '', - routingBudget: 0, + amount: 0, + advancedOptions: false, + useCustomBudget: false, + routingBudgetUnit: 'PPM', + routingBudgetPPM: 1000, + routingBudgetSats: 0, badInvoice: '', useLnproxy: false, - lnproxyServer: '', - lnproxyBudget: 0, + lnproxyInvoice: '', + lnproxyServer: 0, + lnproxyBudgetUnit: 'PPM', + lnproxyBudgetPPM: 0, + lnproxyBudgetSats: 0, badLnproxy: '', }; @@ -42,46 +74,221 @@ export const LightningPayoutForm = ({ setLightning, }: LightningPayoutFormProps): JSX.Element => { const { t } = useTranslation(); + const theme = useTheme(); + + const computeInvoiceAmount = function () { + return ( + order.trade_satoshis - + Math.round((order.trade_satoshis / 1000000) * lightning.routingBudgetPPM) + ); + }; + + useEffect(() => { + setLightning({ + ...lightning, + amount: computeInvoiceAmount(), + }); + }, []); + + const fetchLnproxy = function () { + apiClient + .get( + `http://${lnproxies[lightning.lnproxyServer].mainnetOnion}`, + `/api/${lightning.lnproxyInvoice}?routing_msat=${lightning.lnproxyBudgetSats * 1000}`, + ) + .then((data) => { + setLightning({ ...lightning, invoice: String(data) }); + }) + .catch(() => { + setLightning({ ...lightning, badLnproxy: 'Lnproxy error' }); + }); + }; + return ( - + {t('Submit a valid invoice for {{amountSats}} Satoshis.', { - amountSats: pn(order.invoice_amount), + amountSats: pn(lightning.amount), })} - + - - + {' '} + {t('Advanced options')} + { + const checked = e.target.checked; + setLightning({ + ...lightning, + advancedOptions: checked, + useLnproxy: checked ? lightning.useLnproxy : false, + useCustomBudget: checked ? lightning.useCustomBudget : false, + }); }} - multiline - minRows={4} - maxRows={8} - onChange={(e) => setLightning({ ...lightning, invoice: e.target.value ?? '' })} /> + - - onClickSubmit(lightning.invoice)} - variant='outlined' - color='primary' + + + + + + + + + setLightning({ + ...lightning, + useLnproxy: e.target.checked, + invoice: e.target.checked ? '' : lightning.invoice, + }) + } + checked={lightning.useLnproxy} + control={} + label={ + + {t('Use Lnproxy')} + + } + /> + + + + + + {t('Submit')} + + + + + + + + + + - {t('Submit')} - + + + + + setLightning({ ...lightning, useCustomBudget: e.target.checked }) + } + control={} + label={ + + {t('Use custom routing budget')} + + } + /> + + + + + setLightning({ ...lightning, invoice: e.target.value ?? '' })} + /> + + + onClickSubmit(lightning.invoice)} + variant='outlined' + color='primary' + > + {t('Submit')} + + + + ); diff --git a/frontend/src/components/TradeBox/Prompts/RoutingFailed.tsx b/frontend/src/components/TradeBox/Prompts/RoutingFailed.tsx index 3bcc43f0b..831d3c88c 100644 --- a/frontend/src/components/TradeBox/Prompts/RoutingFailed.tsx +++ b/frontend/src/components/TradeBox/Prompts/RoutingFailed.tsx @@ -28,6 +28,7 @@ const FailureReason = ({ failureReason }: FailureReasonProps): JSX.Element => { backgroundColor: theme.palette.background.paper, borderRadius: '0.3em', border: `1px solid ${theme.palette.text.secondary}`, + padding: '0.5em', }} > diff --git a/frontend/src/components/TradeBox/index.tsx b/frontend/src/components/TradeBox/index.tsx index 835752176..6b505693f 100644 --- a/frontend/src/components/TradeBox/index.tsx +++ b/frontend/src/components/TradeBox/index.tsx @@ -134,6 +134,7 @@ const TradeBox = ({ | 'submit_statement' | 'rate_platform'; invoice?: string; + routing_budget?: number; address?: string; mining_fee_rate?: number; statement?: string; @@ -143,6 +144,7 @@ const TradeBox = ({ const submitAction = function ({ action, invoice, + routing_budget, address, mining_fee_rate, statement, @@ -152,6 +154,7 @@ const TradeBox = ({ .post(baseUrl, '/api/order/?order_id=' + order.id, { action, invoice, + routing_budget, address, mining_fee_rate, statement, @@ -201,7 +204,7 @@ const TradeBox = ({ const updateInvoice = function (invoice: string) { setLoadingButtons({ ...noLoadingButtons, submitInvoice: true }); - submitAction({ action: 'update_invoice', invoice }); + submitAction({ action: 'update_invoice', invoice, routing_budget: lightning.routingBudgetPPM }); }; const updateAddress = function () { @@ -252,7 +255,7 @@ const TradeBox = ({ setWaitingWebln(true); setOpen({ ...open, webln: true }); webln - .makeInvoice(order.trade_satoshis) + .makeInvoice(lightning.amount) .then((invoice: any) => { if (invoice) { updateInvoice(invoice.paymentRequest); @@ -278,7 +281,7 @@ const TradeBox = ({ }, [order.status]); const statusToContract = function (order: Order) { - const status = order.status; + const status = 6; const isBuyer = order.is_buyer; const isMaker = order.is_maker; diff --git a/frontend/src/models/Coordinator.model.ts b/frontend/src/models/Coordinator.model.ts index 5a7403603..d4c356e07 100644 --- a/frontend/src/models/Coordinator.model.ts +++ b/frontend/src/models/Coordinator.model.ts @@ -1,5 +1,6 @@ export interface Coordinator { alias: string; + enabled: boolean; description: string | undefined; coverLetter: string | undefined; logo: string; diff --git a/frontend/static/federation.json b/frontend/static/federation.json index 854890c1f..6c084a119 100644 --- a/frontend/static/federation.json +++ b/frontend/static/federation.json @@ -1,9 +1,10 @@ [ { "alias": "Inception", + "enabled": "true", "description": "RoboSats original and experimental coordinator", "coverLetter": "N/A", - "contact_methods": { + "contact": { "email": "robosats@protonmail.com", "telegram": "@robosats", "twitter": "@robosats", diff --git a/frontend/static/lnproxies.json b/frontend/static/lnproxies.json new file mode 100644 index 000000000..5458e2e70 --- /dev/null +++ b/frontend/static/lnproxies.json @@ -0,0 +1,7 @@ +[ + { + "name": "↬ Lnproxy Dev", + "mainnetOnion": "rdq6tvulanl7aqtupmoboyk2z3suzkdwurejwyjyjf4itr3zhxrm2lad.onion", + "mainnetClearnet": "lnproxy.org" + } +] From d28765c821c966919b9ddb9006603c9bb77b170e Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Tue, 22 Nov 2022 04:16:43 -0800 Subject: [PATCH 2/6] Complete amount calcs --- .../OrderDetails/LinearDeterminate.tsx | 2 +- .../src/components/OrderDetails/index.tsx | 19 +- .../TradeBox/Forms/LightningPayout.tsx | 449 ++++++++++++++---- .../components/TradeBox/Prompts/Payout.tsx | 4 +- 4 files changed, 357 insertions(+), 117 deletions(-) diff --git a/frontend/src/components/OrderDetails/LinearDeterminate.tsx b/frontend/src/components/OrderDetails/LinearDeterminate.tsx index ff7ab2dd6..9bed73c46 100644 --- a/frontend/src/components/OrderDetails/LinearDeterminate.tsx +++ b/frontend/src/components/OrderDetails/LinearDeterminate.tsx @@ -29,7 +29,7 @@ const LinearDeterminate = ({ expiresAt, totalSecsExp }: Props): JSX.Element => { sx={{ height: '0.4em' }} variant='determinate' value={progress} - color={progress < 20 ? 'secondary' : 'primary'} + color={progress < 25 ? 'secondary' : 'primary'} /> ); diff --git a/frontend/src/components/OrderDetails/index.tsx b/frontend/src/components/OrderDetails/index.tsx index 91ca6b29a..0b062056c 100644 --- a/frontend/src/components/OrderDetails/index.tsx +++ b/frontend/src/components/OrderDetails/index.tsx @@ -12,6 +12,7 @@ import { Grid, Collapse, useTheme, + Typography, } from '@mui/material'; import Countdown, { CountdownRenderProps, zeroPad } from 'react-countdown'; @@ -86,25 +87,25 @@ const OrderDetails = ({ // Render a completed state return {t('The order has expired')}; } else { - let col = 'inherit'; + let color = 'inherit'; const fraction_left = total / 1000 / order.total_secs_exp; // Make orange at 25% of time left if (fraction_left < 0.25) { - col = 'orange'; + color = theme.palette.warning.main; } // Make red at 10% of time left if (fraction_left < 0.1) { - col = 'red'; + color = theme.palette.error.main; } // Render a countdown, bold when less than 25% return fraction_left < 0.25 ? ( - - - {`${hours}h ${zeroPad(minutes)}m ${zeroPad(seconds)}s `} - - + + {`${hours}h ${zeroPad(minutes)}m ${zeroPad(seconds)}s `} + ) : ( - {`${hours}h ${zeroPad(minutes)}m ${zeroPad(seconds)}s `} + + {`${hours}h ${zeroPad(minutes)}m ${zeroPad(seconds)}s `} + ); } }; diff --git a/frontend/src/components/TradeBox/Forms/LightningPayout.tsx b/frontend/src/components/TradeBox/Forms/LightningPayout.tsx index f2afa3811..d5800ef61 100644 --- a/frontend/src/components/TradeBox/Forms/LightningPayout.tsx +++ b/frontend/src/components/TradeBox/Forms/LightningPayout.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Box, @@ -11,6 +11,12 @@ import { useTheme, Collapse, Switch, + MenuItem, + Select, + InputAdornment, + Button, + FormControl, + InputLabel, } from '@mui/material'; import { Order } from '../../../models'; import WalletsButton from '../WalletsButton'; @@ -29,10 +35,11 @@ export interface LightningForm { useCustomBudget: boolean; routingBudgetUnit: 'PPM' | 'Sats'; routingBudgetPPM: number; - routingBudgetSats: number; + routingBudgetSats: number | undefined; badInvoice: string; useLnproxy: boolean; lnproxyInvoice: string; + lnproxyAmount: number; lnproxyServer: number; lnproxyBudgetUnit: 'PPM' | 'Sats'; lnproxyBudgetPPM: number; @@ -47,12 +54,13 @@ export const defaultLightning: LightningForm = { useCustomBudget: false, routingBudgetUnit: 'PPM', routingBudgetPPM: 1000, - routingBudgetSats: 0, + routingBudgetSats: undefined, badInvoice: '', useLnproxy: false, lnproxyInvoice: '', + lnproxyAmount: 0, lnproxyServer: 0, - lnproxyBudgetUnit: 'PPM', + lnproxyBudgetUnit: 'Sats', lnproxyBudgetPPM: 0, lnproxyBudgetSats: 0, badLnproxy: '', @@ -76,48 +84,109 @@ export const LightningPayoutForm = ({ const { t } = useTranslation(); const theme = useTheme(); + const [loadingLnproxy, setLoadingLnproxy] = useState(false); + const computeInvoiceAmount = function () { - return ( - order.trade_satoshis - - Math.round((order.trade_satoshis / 1000000) * lightning.routingBudgetPPM) - ); + //const tradeAmount = order.trade_satoshis + const tradeAmount = 10000; + return tradeAmount - Math.round((tradeAmount / 1000000) * lightning.routingBudgetPPM); }; useEffect(() => { + const amount = computeInvoiceAmount(); setLightning({ ...lightning, - amount: computeInvoiceAmount(), + amount, + lnproxyAmount: amount - lightning.lnproxyBudgetSats, + routingBudgetSats: + lightning.routingBudgetSats == undefined + ? (amount / 1000000) * lightning.routingBudgetPPM + : lightning.routingBudgetSats, }); - }, []); + }, [lightning.routingBudgetPPM]); + + const lnproxyUrl = function () { + return `http://${lnproxies[lightning.lnproxyServer].mainnetOnion}`; + }; const fetchLnproxy = function () { + setLoadingLnproxy(true); apiClient .get( - `http://${lnproxies[lightning.lnproxyServer].mainnetOnion}`, + lnproxyUrl(), `/api/${lightning.lnproxyInvoice}?routing_msat=${lightning.lnproxyBudgetSats * 1000}`, ) .then((data) => { - setLightning({ ...lightning, invoice: String(data) }); + const response = String(data); + if (response.includes('lnproxy error')) { + setLightning({ ...lightning, badLnproxy: response }); + } else { + setLightning({ ...lightning, invoice: String(data), badLnproxy: '' }); + } }) - .catch(() => { - setLightning({ ...lightning, badLnproxy: 'Lnproxy error' }); + // .catch(() => { + // setLightning({ ...lightning, badLnproxy: 'Lnproxy server uncaught error' }); + // }) + .finally(() => { + setLoadingLnproxy(false); }); }; - return ( - - - - {t('Submit a valid invoice for {{amountSats}} Satoshis.', { - amountSats: pn(lightning.amount), - })} - - + const onProxyBudgetChange = function (e) { + if (isFinite(e.target.value)) { + if (lightning.lnproxyBudgetUnit === 'Sats') { + const lnproxyBudgetSats = Math.floor(e.target.value); + const lnproxyBudgetPPM = Math.round((lnproxyBudgetSats * 1000000) / lightning.amount); + const lnproxyAmount = lightning.amount - lnproxyBudgetSats; + setLightning({ ...lightning, lnproxyBudgetSats, lnproxyBudgetPPM, lnproxyAmount }); + } else { + const lnproxyBudgetPPM = e.target.value; + const lnproxyBudgetSats = Math.round((lightning.amount / 1000000) * lnproxyBudgetPPM); + const lnproxyAmount = lightning.amount - lnproxyBudgetSats; + setLightning({ ...lightning, lnproxyBudgetSats, lnproxyBudgetPPM, lnproxyAmount }); + } + } + }; - - - + const onRoutingBudgetChange = function (e) { + if (isFinite(e.target.value)) { + if (lightning.routingBudgetUnit === 'Sats') { + const routingBudgetSats = Math.floor(e.target.value); + const routingBudgetPPM = Math.round((routingBudgetSats * 1000000) / lightning.amount); + const amount = lightning.amount - routingBudgetSats; + setLightning({ ...lightning, routingBudgetSats, routingBudgetPPM, amount }); + } else { + const routingBudgetPPM = e.target.value; + const routingBudgetSats = Math.round((lightning.amount / 1000000) * routingBudgetPPM); + const amount = lightning.amount - routingBudgetSats; + setLightning({ ...lightning, routingBudgetSats, routingBudgetPPM, amount }); + } + } + }; + + const lnProxyBudgetHelper = function () { + let text = ''; + if (lightning.lnproxyBudgetSats < 0) { + text = 'Must be positive'; + } else if (lightning.lnproxyBudgetPPM > 10000) { + text = 'Too high! (That is more than 1%)'; + } + return text; + }; + const routingBudgetHelper = function () { + let text = ''; + if (lightning.routingBudgetSats < 0) { + text = 'Must be positive'; + } else if (lightning.routingBudgetPPM > 10000) { + text = 'Too high! (That is more than 1%)'; + } + return text; + }; + + return ( + +
- {' '} - {t('Advanced options')} + {t('Advanced options')} - + - - - - - - setLightning({ - ...lightning, - useLnproxy: e.target.checked, - invoice: e.target.checked ? '' : lightning.invoice, - }) - } - checked={lightning.useLnproxy} - control={} - label={ - - {t('Use Lnproxy')} - - } - /> - - - + - - {t('Submit')} - +
+ + setLightning({ + ...lightning, + useLnproxy: e.target.checked, + invoice: e.target.checked ? '' : lightning.invoice, + }) + } + checked={lightning.useLnproxy} + control={} + label={ + + {t('Use Lnproxy')} + + } + /> +
-
-
-
+ + + + + + {t('Server')} + + + + + + + + + ), + }} + inputProps={{ + style: { + textAlign: 'center', + }, + }} + onChange={onProxyBudgetChange} + /> + + + + + {t('Submit a valid invoice for {{amountSats}} Satoshis.', { + amountSats: pn(lightning.lnproxyAmount), + })} + + + + + + setLightning({ ...lightning, lnproxyInvoice: e.target.value ?? '' }) + } + /> + + + + + {t('Wrap invoice')} + + + + + +
+ + @@ -215,7 +383,7 @@ export const LightningPayoutForm = ({ sx={{ backgroundColor: 'background.paper', border: '1px solid', - borderRadius: '4px', + borderRadius: '0.3em', width: '18em', borderColor: theme.palette.mode === 'dark' ? '#434343' : '#c4c4c4', '&:hover': { @@ -240,38 +408,104 @@ export const LightningPayoutForm = ({ `Set custom routing budget for the payout. If you don't know what this is, simply do not touch.`, )} > - - setLightning({ ...lightning, useCustomBudget: e.target.checked }) - } - control={} - label={ - - {t('Use custom routing budget')} - - } - /> +
+ + setLightning({ + ...lightning, + useCustomBudget: e.target.checked, + routingBudgetSats: defaultLightning.routingBudgetSats, + routingBudgetPPM: defaultLightning.routingBudgetPPM, + }) + } + control={} + label={ + + {t('Use custom routing budget')} + + } + /> +
+ + + + + + ), + }} + inputProps={{ + style: { + textAlign: 'center', + }, + }} + onChange={onRoutingBudgetChange} + /> + + + + + + + {t('Submit a valid invoice for {{amountSats}} Satoshis.', { + amountSats: pn(lightning.amount), + })} + + + + setLightning({ ...lightning, invoice: e.target.value ?? '' })} @@ -280,6 +514,7 @@ export const LightningPayoutForm = ({ onClickSubmit(lightning.invoice)} variant='outlined' color='primary' @@ -290,6 +525,10 @@ export const LightningPayoutForm = ({ + + + + ); }; diff --git a/frontend/src/components/TradeBox/Prompts/Payout.tsx b/frontend/src/components/TradeBox/Prompts/Payout.tsx index fc8db73f7..2d57b8789 100644 --- a/frontend/src/components/TradeBox/Prompts/Payout.tsx +++ b/frontend/src/components/TradeBox/Prompts/Payout.tsx @@ -65,9 +65,9 @@ export const PayoutPrompt = ({ size='small' value={tab} exclusive - onChange={(mouseEvent, value: string) => setTab(value)} + onChange={(mouseEvent, value) => setTab(value == null ? tab : value)} > - +
Date: Wed, 23 Nov 2022 03:42:21 -0800 Subject: [PATCH 3/6] Temporary working solution for lnproxy web only (uses text instead of json) --- .../src/components/Notifications/index.tsx | 2 +- .../TradeBox/Forms/LightningPayout.tsx | 470 ++++++++++-------- 2 files changed, 268 insertions(+), 204 deletions(-) diff --git a/frontend/src/components/Notifications/index.tsx b/frontend/src/components/Notifications/index.tsx index c685fdc01..65ae1b0e4 100644 --- a/frontend/src/components/Notifications/index.tsx +++ b/frontend/src/components/Notifications/index.tsx @@ -134,7 +134,7 @@ const Notifications = ({ title: t('Order has expired'), severity: 'warning', onClick: moveToOrderPage, - sound: undefined, + sound: audio.ding, timeout: 30000, pageTitle: `${t('😪 Expired!')} - ${basePageTitle}`, }, diff --git a/frontend/src/components/TradeBox/Forms/LightningPayout.tsx b/frontend/src/components/TradeBox/Forms/LightningPayout.tsx index d5800ef61..ee81aa1dd 100644 --- a/frontend/src/components/TradeBox/Forms/LightningPayout.tsx +++ b/frontend/src/components/TradeBox/Forms/LightningPayout.tsx @@ -17,16 +17,18 @@ import { Button, FormControl, InputLabel, + IconButton, } from '@mui/material'; import { Order } from '../../../models'; import WalletsButton from '../WalletsButton'; import { LoadingButton } from '@mui/lab'; import { pn } from '../../../utils'; -import { RoundaboutRight, Route, SelfImprovement } from '@mui/icons-material'; +import { ContentCopy, RoundaboutRight, Route, SelfImprovement } from '@mui/icons-material'; import { apiClient } from '../../../services/api'; import lnproxies from '../../../../static/lnproxies.json'; +import { systemClient } from '../../../services/System'; export interface LightningForm { invoice: string; @@ -88,8 +90,17 @@ export const LightningPayoutForm = ({ const computeInvoiceAmount = function () { //const tradeAmount = order.trade_satoshis - const tradeAmount = 10000; - return tradeAmount - Math.round((tradeAmount / 1000000) * lightning.routingBudgetPPM); + const tradeAmount = 1000000; + return tradeAmount - Math.floor((tradeAmount / 1000000) * lightning.routingBudgetPPM); + }; + + const validateInvoice = function (invoice: string, targetAmount: number) { + const invoiceAmount = Number(invoice.substring(4, 5 + Math.floor(Math.log10(targetAmount)))); + if (targetAmount != invoiceAmount) { + return 'Invalid invoice amount'; + } else { + return ''; + } }; useEffect(() => { @@ -100,48 +111,84 @@ export const LightningPayoutForm = ({ lnproxyAmount: amount - lightning.lnproxyBudgetSats, routingBudgetSats: lightning.routingBudgetSats == undefined - ? (amount / 1000000) * lightning.routingBudgetPPM + ? Math.ceil((amount / 1000000) * lightning.routingBudgetPPM) : lightning.routingBudgetSats, }); }, [lightning.routingBudgetPPM]); + useEffect(() => { + if (lightning.invoice != '') { + setLightning({ + ...lightning, + badInvoice: validateInvoice(lightning.invoice, lightning.amount), + }); + } + }, [lightning.invoice, lightning.amount]); + + useEffect(() => { + if (lightning.lnproxyInvoice != '') { + setLightning({ + ...lightning, + badLnproxy: validateInvoice(lightning.lnproxyInvoice, lightning.lnproxyAmount), + }); + } + }, [lightning.lnproxyInvoice, lightning.lnproxyAmount]); + const lnproxyUrl = function () { return `http://${lnproxies[lightning.lnproxyServer].mainnetOnion}`; }; + // const fetchLnproxy = function () { + // setLoadingLnproxy(true); + // apiClient + // .get( + // lnproxyUrl(), + // `/api/${lightning.lnproxyInvoice}${lightning.lnproxyBudgetSats > 0 ? `?routing_msat=${lightning.lnproxyBudgetSats * 1000}` : ''}`, + // ) + // }; + + // Lnproxy API does not return JSON, therefore not compatible with current apiClient service + // Does not work on Android robosats! const fetchLnproxy = function () { setLoadingLnproxy(true); - apiClient - .get( - lnproxyUrl(), - `/api/${lightning.lnproxyInvoice}?routing_msat=${lightning.lnproxyBudgetSats * 1000}`, - ) - .then((data) => { - const response = String(data); - if (response.includes('lnproxy error')) { - setLightning({ ...lightning, badLnproxy: response }); + fetch( + lnproxyUrl() + + `/api/${lightning.lnproxyInvoice}${ + lightning.lnproxyBudgetSats > 0 + ? `?routing_msat=${lightning.lnproxyBudgetSats * 1000}` + : '' + }`, + ) + .then((response) => response.text()) + .then((text) => { + if (text.includes('lnproxy error')) { + setLightning({ ...lightning, badLnproxy: text }); } else { - setLightning({ ...lightning, invoice: String(data), badLnproxy: '' }); + setLightning({ ...lightning, invoice: text, badLnproxy: '' }); } }) - // .catch(() => { - // setLightning({ ...lightning, badLnproxy: 'Lnproxy server uncaught error' }); - // }) + .catch(() => { + setLightning({ ...lightning, badLnproxy: 'Lnproxy server uncaught error' }); + }) .finally(() => { setLoadingLnproxy(false); }); }; const onProxyBudgetChange = function (e) { - if (isFinite(e.target.value)) { + if (isFinite(e.target.value) && e.target.value >= 0) { + let lnproxyBudgetSats; + let lnproxyBudgetPPM; + if (lightning.lnproxyBudgetUnit === 'Sats') { - const lnproxyBudgetSats = Math.floor(e.target.value); - const lnproxyBudgetPPM = Math.round((lnproxyBudgetSats * 1000000) / lightning.amount); - const lnproxyAmount = lightning.amount - lnproxyBudgetSats; - setLightning({ ...lightning, lnproxyBudgetSats, lnproxyBudgetPPM, lnproxyAmount }); + lnproxyBudgetSats = Math.floor(e.target.value); + lnproxyBudgetPPM = Math.round((lnproxyBudgetSats * 1000000) / lightning.amount); } else { - const lnproxyBudgetPPM = e.target.value; - const lnproxyBudgetSats = Math.round((lightning.amount / 1000000) * lnproxyBudgetPPM); + lnproxyBudgetPPM = e.target.value; + lnproxyBudgetSats = Math.ceil((lightning.amount / 1000000) * lnproxyBudgetPPM); + } + + if (lnproxyBudgetPPM < 99999) { const lnproxyAmount = lightning.amount - lnproxyBudgetSats; setLightning({ ...lightning, lnproxyBudgetSats, lnproxyBudgetPPM, lnproxyAmount }); } @@ -149,15 +196,19 @@ export const LightningPayoutForm = ({ }; const onRoutingBudgetChange = function (e) { - if (isFinite(e.target.value)) { + if (isFinite(e.target.value) && e.target.value >= 0) { + let routingBudgetSats; + let routingBudgetPPM; + if (lightning.routingBudgetUnit === 'Sats') { - const routingBudgetSats = Math.floor(e.target.value); - const routingBudgetPPM = Math.round((routingBudgetSats * 1000000) / lightning.amount); - const amount = lightning.amount - routingBudgetSats; - setLightning({ ...lightning, routingBudgetSats, routingBudgetPPM, amount }); + routingBudgetSats = Math.floor(e.target.value); + routingBudgetPPM = Math.round((routingBudgetSats * 1000000) / lightning.amount); } else { - const routingBudgetPPM = e.target.value; - const routingBudgetSats = Math.round((lightning.amount / 1000000) * routingBudgetPPM); + routingBudgetPPM = e.target.value; + routingBudgetSats = Math.ceil((lightning.amount / 1000000) * routingBudgetPPM); + } + + if (routingBudgetPPM < 99999) { const amount = lightning.amount - routingBudgetSats; setLightning({ ...lightning, routingBudgetSats, routingBudgetPPM, amount }); } @@ -204,9 +255,6 @@ export const LightningPayoutForm = ({ setLightning({ ...lightning, advancedOptions: checked, - useLnproxy: checked ? lightning.useLnproxy : false, - invoice: checked ? '' : lightning.invoice, - useCustomBudget: checked ? lightning.useCustomBudget : false, }); }} /> @@ -214,27 +262,24 @@ export const LightningPayoutForm = ({ - - + - + -
- - setLightning({ - ...lightning, - useLnproxy: e.target.checked, - invoice: e.target.checked ? '' : lightning.invoice, - }) - } - checked={lightning.useLnproxy} - control={} - label={ - - {t('Use Lnproxy')} - - } - /> -
+ + + + ), + }} + inputProps={{ + style: { + textAlign: 'center', + }, + }} + onChange={onRoutingBudgetChange} + />
+ + {window.NativeRobosats === undefined ? ( + + +
+ + setLightning({ + ...lightning, + useLnproxy: e.target.checked, + invoice: e.target.checked ? '' : lightning.invoice, + }) + } + checked={lightning.useLnproxy} + control={} + label={ + + {t('Use Lnproxy')} + + } + /> +
+
+
+ ) : ( + <> + )} + - - - - {t('Submit a valid invoice for {{amountSats}} Satoshis.', { - amountSats: pn(lightning.lnproxyAmount), - })} - - - - - - setLightning({ ...lightning, lnproxyInvoice: e.target.value ?? '' }) - } - /> - - - - - {t('Wrap invoice')} - -
- - - + + + +
+ + {t('Submit invoice for {{amountSats}} Sats', { + amountSats: pn( + lightning.useLnproxy ? lightning.lnproxyAmount : lightning.amount, + ), + })} + + + + systemClient.copyToClipboard( + lightning.useLnproxy ? lightning.lnproxyAmount : lightning.amount, + ) + } + > + + + +
+
+ + + {lightning.useLnproxy ? ( + + setLightning({ ...lightning, lnproxyInvoice: e.target.value ?? '' }) + } + /> + ) : ( + <> + )} + setLightning({ ...lightning, invoice: e.target.value ?? '' })} + /> + + + + {lightning.useLnproxy ? ( + + {t('Wrap')} + + ) : ( + <> + )} + onClickSubmit(lightning.invoice)} + variant='outlined' + color='primary' + > + {t('Submit')} + + + + - + {/* - - - - - - ), - }} - inputProps={{ - style: { - textAlign: 'center', - }, - }} - onChange={onRoutingBudgetChange} - /> - - - - - - - {t('Submit a valid invoice for {{amountSats}} Satoshis.', { - amountSats: pn(lightning.amount), - })} - - - - - - setLightning({ ...lightning, invoice: e.target.value ?? '' })} - /> - - - onClickSubmit(lightning.invoice)} - variant='outlined' - color='primary' - > - {t('Submit')} - - - + */} From 84320d57e4823ced64806d4e8cf9bc4d4b5f7752 Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Wed, 23 Nov 2022 15:34:17 -0800 Subject: [PATCH 4/6] Update LNpayment model and logics to use user's routing budget --- api/lightning/node.py | 19 +++++++++----- api/logics.py | 25 ++++++++++++++++--- api/models.py | 13 ++++++++++ api/serializers.py | 8 ++++++ api/tasks.py | 22 +++++++++++----- api/views.py | 5 +++- docker-compose.yml | 2 +- .../src/components/Notifications/index.tsx | 4 +-- .../EncryptedChat/ChatHeader/index.tsx | 2 +- .../EncryptedSocketChat/index.tsx | 1 + .../TradeBox/Forms/LightningPayout.tsx | 14 ++++++----- .../src/components/TradeBox/TradeSummary.tsx | 4 +-- frontend/src/components/TradeBox/index.tsx | 16 +++++++----- frontend/src/models/Order.model.ts | 2 +- 14 files changed, 100 insertions(+), 37 deletions(-) diff --git a/api/lightning/node.py b/api/lightning/node.py index 4b85c4df0..386acb567 100644 --- a/api/lightning/node.py +++ b/api/lightning/node.py @@ -258,7 +258,7 @@ def resetmc(cls): return True @classmethod - def validate_ln_invoice(cls, invoice, num_satoshis): + def validate_ln_invoice(cls, invoice, num_satoshis, routing_budget_ppm): """Checks if the submited LN invoice comforms to expectations""" payout = { @@ -283,10 +283,17 @@ def validate_ln_invoice(cls, invoice, num_satoshis): route_hints = payreq_decoded.route_hints # Max amount RoboSats will pay for routing - max_routing_fee_sats = max( - num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")), - float(config("MIN_FLAT_ROUTING_FEE_LIMIT_REWARD")), - ) + # Start deprecate after v0.3.1 (only else max_routing_fee_sats will remain) + if routing_budget_ppm == 0: + max_routing_fee_sats = max( + num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")), + float(config("MIN_FLAT_ROUTING_FEE_LIMIT_REWARD")), + ) + else: + # End deprecate + max_routing_fee_sats = int( + float(num_satoshis) * float(routing_budget_ppm) / 1000000 + ) if route_hints: routes_cost = [] @@ -306,7 +313,7 @@ def validate_ln_invoice(cls, invoice, num_satoshis): # If the cheapest possible private route is more expensive than what RoboSats is willing to pay if min(routes_cost) >= max_routing_fee_sats: payout["context"] = { - "bad_invoice": "The invoice submitted only has a trick on the routing hints, you might be using an incompatible wallet (probably Muun? Use an onchain address instead!). Check the wallet compatibility guide at wallets.robosats.com" + "bad_invoice": "The invoice hinted private routes are not payable within the submitted routing budget." } return payout diff --git a/api/logics.py b/api/logics.py index d718bd04f..f2b229a7e 100644 --- a/api/logics.py +++ b/api/logics.py @@ -721,7 +721,7 @@ def update_address(cls, order, user, address, mining_fee_rate): return True, None @classmethod - def update_invoice(cls, order, user, invoice): + def update_invoice(cls, order, user, invoice, routing_budget_ppm): # Empty invoice? if not invoice: @@ -754,7 +754,11 @@ def update_invoice(cls, order, user, invoice): cls.cancel_onchain_payment(order) num_satoshis = cls.payout_amount(order, user)[1]["invoice_amount"] - payout = LNNode.validate_ln_invoice(invoice, num_satoshis) + routing_budget_sats = float(num_satoshis) * ( + float(routing_budget_ppm) / 1000000 + ) + num_satoshis = int(num_satoshis - routing_budget_sats) + payout = LNNode.validate_ln_invoice(invoice, num_satoshis, routing_budget_ppm) if not payout["valid"]: return False, payout["context"] @@ -765,6 +769,8 @@ def update_invoice(cls, order, user, invoice): sender=User.objects.get(username=ESCROW_USERNAME), order_paid_LN=order, # In case this user has other payouts, update the one related to this order. receiver=user, + routing_budget_ppm=routing_budget_ppm, + routing_budget_sats=routing_budget_sats, # if there is a LNPayment matching these above, it updates that one with defaults below. defaults={ "invoice": invoice, @@ -1679,7 +1685,9 @@ def summarize_trade(cls, order, user): else: summary["received_sats"] = order.payout.num_satoshis summary["trade_fee_sats"] = round( - order.last_satoshis - summary["received_sats"] + order.last_satoshis + - summary["received_sats"] + - order.payout.routing_budget_sats ) # Only add context for swap costs if the user is the swap recipient. Peer should not know whether it was a swap if users[order_user] == user and order.is_swap: @@ -1716,11 +1724,20 @@ def summarize_trade(cls, order, user): order.contract_finalization_time - order.last_satoshis_time ) if not order.is_swap: + platform_summary["routing_budget_sats"] = order.payout.routing_budget_sats + # Start Deprecated after v0.3.1 platform_summary["routing_fee_sats"] = order.payout.fee + # End Deprecated after v0.3.1 platform_summary["trade_revenue_sats"] = int( order.trade_escrow.num_satoshis - order.payout.num_satoshis - - order.payout.fee + # Start Deprecated after v0.3.1 (will be `- order.payout.routing_budget_sats`) + - ( + order.payout.fee + if order.payout.routing_budget_sats == 0 + else order.payout.routing_budget_sats + ) + # End Deprecated after v0.3.1 ) else: platform_summary["routing_fee_sats"] = 0 diff --git a/api/models.py b/api/models.py index 790d67477..afd852086 100644 --- a/api/models.py +++ b/api/models.py @@ -126,6 +126,19 @@ class FailureReason(models.IntegerChoices): MaxValueValidator(1.5 * MAX_TRADE), ] ) + # Routing budget in PPM + routing_budget = models.PositiveBigIntegerField( + default=0, + null=False, + validators=[ + MinValueValidator(0), + MaxValueValidator(100000), + ], + ) + # Routing budget in Sats. Only for reporting summaries. + routing_budget_sats = models.DecimalField( + max_digits=10, decimal_places=3, default=0, null=False, blank=False + ) # Fee in sats with mSats decimals fee_msat fee = models.DecimalField( max_digits=10, decimal_places=3, default=0, null=False, blank=False diff --git a/api/serializers.py b/api/serializers.py index 0c9cfd086..ded1e039d 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -489,6 +489,14 @@ class UpdateOrderSerializer(serializers.Serializer): invoice = serializers.CharField( max_length=2000, allow_null=True, allow_blank=True, default=None ) + routing_budget_ppm = serializers.IntegerField( + default=0, + min_value=0, + max_value=100000, + allow_null=True, + required=False, + help_text="Max budget to allocate for routing in PPM", + ) address = serializers.CharField( max_length=100, allow_null=True, allow_blank=True, default=None ) diff --git a/api/tasks.py b/api/tasks.py index 4a6fc57b7..700db183d 100644 --- a/api/tasks.py +++ b/api/tasks.py @@ -86,12 +86,23 @@ def follow_send_payment(hash): from api.models import LNPayment, Order lnpayment = LNPayment.objects.get(payment_hash=hash) - fee_limit_sat = int( - max( - lnpayment.num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")), - float(config("MIN_FLAT_ROUTING_FEE_LIMIT")), + # Start deprecate after v0.3.1 (only else max_routing_fee_sats will remain) + if lnpayment.routing_budget_ppm == 0: + fee_limit_sat = int( + max( + lnpayment.num_satoshis + * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")), + float(config("MIN_FLAT_ROUTING_FEE_LIMIT")), + ) + ) # 1000 ppm or 10 sats + else: + # End deprecate + # Defaults is 0ppm. Set by the user over API. Defaults to 1000 ppm on ReactJS frontend. + fee_limit_sat = int( + float(lnpayment.num_satoshis) + * float(lnpayment.routing_budget_ppm) + / 1000000 ) - ) # 1000 ppm or 10 sats timeout_seconds = int(config("PAYOUT_TIMEOUT_SECONDS")) request = LNNode.routerrpc.SendPaymentRequest( @@ -145,7 +156,6 @@ def follow_send_payment(hash): ], "IN_FLIGHT": False, } - print(context) # If failed due to not route, reset mission control. (This won't scale well, just a temporary fix) # ResetMC deactivate temporary for tests diff --git a/api/views.py b/api/views.py index 0d61507a1..457ba9adc 100644 --- a/api/views.py +++ b/api/views.py @@ -501,6 +501,7 @@ def take_update_confirm_dispute_cancel(self, request, format=None): # 5.b)'update_address' 6)'submit_statement' (in dispute), 7)'rate_user' , 8)'rate_platform' action = serializer.data.get("action") invoice = serializer.data.get("invoice") + routing_budget_ppm = serializer.data.get("routing_budget_ppm", 0) address = serializer.data.get("address") mining_fee_rate = serializer.data.get("mining_fee_rate") statement = serializer.data.get("statement") @@ -543,7 +544,9 @@ def take_update_confirm_dispute_cancel(self, request, format=None): # 2) If action is 'update invoice' elif action == "update_invoice": - valid, context = Logics.update_invoice(order, request.user, invoice) + valid, context = Logics.update_invoice( + order, request.user, invoice, routing_budget_ppm + ) if not valid: return Response(context, status.HTTP_400_BAD_REQUEST) diff --git a/docker-compose.yml b/docker-compose.yml index bf02580a1..49e8f4233 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,7 +53,7 @@ services: environment: TOR_PROXY_IP: 127.0.0.1 TOR_PROXY_PORT: 9050 - ROBOSATS_ONION: robotestagw3dcxmd66r4rgksb4nmmr43fh77bzn2ia2eucduyeafnyd.onion + ROBOSATS_ONION: robosats6tkf3eva7x2voqso3a5wcorsnw34jveyxfqi2fu7oyheasid.onion network_mode: service:tor volumes: - ./frontend/static:/usr/src/robosats/static diff --git a/frontend/src/components/Notifications/index.tsx b/frontend/src/components/Notifications/index.tsx index 65ae1b0e4..9d2e9b796 100644 --- a/frontend/src/components/Notifications/index.tsx +++ b/frontend/src/components/Notifications/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { StringIfPlural, useTranslation } from 'react-i18next'; +import { useTranslation } from 'react-i18next'; import { Tooltip, Alert, @@ -262,7 +262,6 @@ const Notifications = ({ } else if (order?.is_seller && status > 7 && oldStatus < 7) { message = Messages.escrowLocked; } else if ([9, 10].includes(status) && oldStatus < 9) { - console.log('yoooo'); message = Messages.chat; } else if (order?.is_seller && [13, 14, 15].includes(status) && oldStatus < 13) { message = Messages.successful; @@ -333,7 +332,6 @@ const Notifications = ({ return ( 60 ? 'left' : 'bottom'} title={ = ({ connected, peerConnected, turtleMode, set > {t('Peer') + ': '} - {peerConnected ? t('connected') : t('disconnected')} + {connected ? (peerConnected ? t('connected') : t('disconnected')) : '...waiting'} diff --git a/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx b/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx index 9f6114546..99347f70a 100644 --- a/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx +++ b/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx @@ -72,6 +72,7 @@ const EncryptedSocketChat: React.FC = ({ useEffect(() => { if (![9, 10].includes(status)) { connection?.close(); + setConnection(undefined); } }, [status]); diff --git a/frontend/src/components/TradeBox/Forms/LightningPayout.tsx b/frontend/src/components/TradeBox/Forms/LightningPayout.tsx index ee81aa1dd..7eca516bf 100644 --- a/frontend/src/components/TradeBox/Forms/LightningPayout.tsx +++ b/frontend/src/components/TradeBox/Forms/LightningPayout.tsx @@ -89,14 +89,13 @@ export const LightningPayoutForm = ({ const [loadingLnproxy, setLoadingLnproxy] = useState(false); const computeInvoiceAmount = function () { - //const tradeAmount = order.trade_satoshis - const tradeAmount = 1000000; - return tradeAmount - Math.floor((tradeAmount / 1000000) * lightning.routingBudgetPPM); + const tradeAmount = order.trade_satoshis; + return Math.floor(tradeAmount - tradeAmount * (lightning.routingBudgetPPM / 1000000)); }; const validateInvoice = function (invoice: string, targetAmount: number) { const invoiceAmount = Number(invoice.substring(4, 5 + Math.floor(Math.log10(targetAmount)))); - if (targetAmount != invoiceAmount) { + if (targetAmount != invoiceAmount && invoice.length > 20) { return 'Invalid invoice amount'; } else { return ''; @@ -196,20 +195,23 @@ export const LightningPayoutForm = ({ }; const onRoutingBudgetChange = function (e) { + const tradeAmount = order.trade_satoshis; if (isFinite(e.target.value) && e.target.value >= 0) { let routingBudgetSats; let routingBudgetPPM; if (lightning.routingBudgetUnit === 'Sats') { routingBudgetSats = Math.floor(e.target.value); - routingBudgetPPM = Math.round((routingBudgetSats * 1000000) / lightning.amount); + routingBudgetPPM = Math.round((routingBudgetSats * 1000000) / tradeAmount); } else { routingBudgetPPM = e.target.value; routingBudgetSats = Math.ceil((lightning.amount / 1000000) * routingBudgetPPM); } if (routingBudgetPPM < 99999) { - const amount = lightning.amount - routingBudgetSats; + const amount = Math.floor( + tradeAmount - tradeAmount * (lightning.routingBudgetPPM / 1000000), + ); setLightning({ ...lightning, routingBudgetSats, routingBudgetPPM, amount }); } } diff --git a/frontend/src/components/TradeBox/TradeSummary.tsx b/frontend/src/components/TradeBox/TradeSummary.tsx index 3f52e8bdb..baba77e09 100644 --- a/frontend/src/components/TradeBox/TradeSummary.tsx +++ b/frontend/src/components/TradeBox/TradeSummary.tsx @@ -273,9 +273,9 @@ const TradeSummary = ({ diff --git a/frontend/src/components/TradeBox/index.tsx b/frontend/src/components/TradeBox/index.tsx index 6b505693f..715f0ebfc 100644 --- a/frontend/src/components/TradeBox/index.tsx +++ b/frontend/src/components/TradeBox/index.tsx @@ -134,7 +134,7 @@ const TradeBox = ({ | 'submit_statement' | 'rate_platform'; invoice?: string; - routing_budget?: number; + routing_budget_ppm?: number; address?: string; mining_fee_rate?: number; statement?: string; @@ -144,7 +144,7 @@ const TradeBox = ({ const submitAction = function ({ action, invoice, - routing_budget, + routing_budget_ppm, address, mining_fee_rate, statement, @@ -154,7 +154,7 @@ const TradeBox = ({ .post(baseUrl, '/api/order/?order_id=' + order.id, { action, invoice, - routing_budget, + routing_budget_ppm, address, mining_fee_rate, statement, @@ -204,7 +204,11 @@ const TradeBox = ({ const updateInvoice = function (invoice: string) { setLoadingButtons({ ...noLoadingButtons, submitInvoice: true }); - submitAction({ action: 'update_invoice', invoice, routing_budget: lightning.routingBudgetPPM }); + submitAction({ + action: 'update_invoice', + invoice, + routing_budget_ppm: lightning.routingBudgetPPM, + }); }; const updateAddress = function () { @@ -255,7 +259,7 @@ const TradeBox = ({ setWaitingWebln(true); setOpen({ ...open, webln: true }); webln - .makeInvoice(lightning.amount) + .makeInvoice(() => lightning.amount) .then((invoice: any) => { if (invoice) { updateInvoice(invoice.paymentRequest); @@ -281,7 +285,7 @@ const TradeBox = ({ }, [order.status]); const statusToContract = function (order: Order) { - const status = 6; + const status = order.status; const isBuyer = order.is_buyer; const isMaker = order.is_maker; diff --git a/frontend/src/models/Order.model.ts b/frontend/src/models/Order.model.ts index 1427509ca..af845c175 100644 --- a/frontend/src/models/Order.model.ts +++ b/frontend/src/models/Order.model.ts @@ -15,7 +15,7 @@ export interface TradeCoordinatorSummary { contract_timestamp: Date; contract_total_time: number; contract_exchange_rate: number; - routing_fee_sats: number; + routing_budget_sats: number; trade_revenue_sats: number; } From 8a37ebf2d3eabec9e3f2fffaeac4b6909ee96e7f Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Thu, 24 Nov 2022 01:27:21 -0800 Subject: [PATCH 5/6] Add handle lnproxyserver networks (i2p, tor, clearnet) / (mainnet,testnet) --- frontend/src/basic/Main.tsx | 9 +++- frontend/src/basic/OrderPage/index.tsx | 5 ++- .../TradeBox/Forms/LightningPayout.tsx | 42 +++++++++++++++++-- .../components/TradeBox/Prompts/Payout.tsx | 5 ++- frontend/src/components/TradeBox/index.tsx | 5 ++- frontend/static/lnproxies.json | 8 +++- 6 files changed, 63 insertions(+), 11 deletions(-) diff --git a/frontend/src/basic/Main.tsx b/frontend/src/basic/Main.tsx index b2bd1d8dd..28e03a697 100644 --- a/frontend/src/basic/Main.tsx +++ b/frontend/src/basic/Main.tsx @@ -181,7 +181,7 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => { return await data; }; - const fetchInfo = function () { + const fetchInfo = function (setNetwork?: boolean) { setInfo({ ...info, loading: true }); apiClient.get(baseUrl, '/api/info/').then((data: Info) => { const versionInfo: any = checkVer(data.version.major, data.version.minor, data.version.patch); @@ -192,12 +192,16 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => { clientVersion: versionInfo.clientVersion, loading: false, }); + // Sets Setting network from coordinator API param if accessing via web + if (setNetwork) { + setSettings({ ...settings, network: data.network }); + } }); }; useEffect(() => { if (open.stats || open.coordinator || info.coordinatorVersion == 'v?.?.?') { - fetchInfo(); + fetchInfo(info.coordinatorVersion == 'v?.?.?'); } }, [open.stats, open.coordinator]); @@ -424,6 +428,7 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => { void; setCurrentOrder: (state: number) => void; fetchOrder: () => void; @@ -27,6 +28,7 @@ interface OrderPageProps { const OrderPage = ({ windowSize, order, + settings, setOrder, setCurrentOrder, badOrder, @@ -128,6 +130,7 @@ const OrderPage = ({ > void; onClickSubmit: (invoice: string) => void; + settings: Settings; } export const LightningPayoutForm = ({ @@ -82,11 +84,13 @@ export const LightningPayoutForm = ({ onClickSubmit, lightning, setLightning, + settings, }: LightningPayoutFormProps): JSX.Element => { const { t } = useTranslation(); const theme = useTheme(); const [loadingLnproxy, setLoadingLnproxy] = useState(false); + const [badLnproxyServer, setBadLnproxyServer] = useState(''); const computeInvoiceAmount = function () { const tradeAmount = order.trade_satoshis; @@ -134,9 +138,32 @@ export const LightningPayoutForm = ({ }, [lightning.lnproxyInvoice, lightning.lnproxyAmount]); const lnproxyUrl = function () { - return `http://${lnproxies[lightning.lnproxyServer].mainnetOnion}`; + const bitcoinNetwork = settings.network ?? 'mainnet'; + let internetNetwork: 'Clearnet' | 'I2P' | 'TOR' = 'Clearnet'; + if (settings.host?.includes('.i2p')) { + internetNetwork = 'I2P'; + } else if (settings.host?.includes('.onion') || window.NativeRobosats != undefined) { + internetNetwork = 'TOR'; + } + + const url = lnproxies[lightning.lnproxyServer][`${bitcoinNetwork}${internetNetwork}`]; + if (url != 'undefined') { + return url; + } else { + setBadLnproxyServer( + t(`Server not available for {{bitcoinNetwork}} bitcoin over {{internetNetwork}}`, { + bitcoinNetwork, + internetNetwork: t(internetNetwork), + }), + ); + } }; + useEffect(() => { + setBadLnproxyServer(''); + lnproxyUrl(); + }, [lightning.lnproxyServer]); + // const fetchLnproxy = function () { // setLoadingLnproxy(true); // apiClient @@ -375,7 +402,7 @@ export const LightningPayoutForm = ({ spacing={1} > - + {t('Server')} + {badLnproxyServer != '' ? ( + {t(badLnproxyServer)} + ) : ( + <> + )} @@ -509,7 +541,9 @@ export const LightningPayoutForm = ({ {lightning.useLnproxy ? ( void; loadingOnchain: boolean; + settings: Settings; } export const PayoutPrompt = ({ @@ -31,6 +32,7 @@ export const PayoutPrompt = ({ loadingOnchain, onchain, setOnchain, + settings, }: PayoutPrompProps): JSX.Element => { const { t } = useTranslation(); const currencyCode: string = currencies[`${order.currency}`]; @@ -97,6 +99,7 @@ export const PayoutPrompt = ({ void; onRenewOrder: () => void; onStartAgain: () => void; + settings: Settings; baseUrl: string; } const TradeBox = ({ order, setOrder, + settings, baseUrl, setBadOrder, onRenewOrder, @@ -384,6 +386,7 @@ const TradeBox = ({ return ( Date: Thu, 24 Nov 2022 02:59:27 -0800 Subject: [PATCH 6/6] Small fixes --- api/models.py | 2 +- frontend/src/basic/OrderPage/index.tsx | 1 + frontend/src/components/TradeBox/Forms/LightningPayout.tsx | 3 ++- frontend/src/components/TradeBox/Prompts/RoutingFailed.tsx | 5 ++++- frontend/src/components/TradeBox/TradeSummary.tsx | 2 +- frontend/src/components/TradeBox/index.tsx | 2 ++ 6 files changed, 11 insertions(+), 4 deletions(-) diff --git a/api/models.py b/api/models.py index afd852086..a4da1f719 100644 --- a/api/models.py +++ b/api/models.py @@ -127,7 +127,7 @@ class FailureReason(models.IntegerChoices): ] ) # Routing budget in PPM - routing_budget = models.PositiveBigIntegerField( + routing_budget_ppm = models.PositiveBigIntegerField( default=0, null=False, validators=[ diff --git a/frontend/src/basic/OrderPage/index.tsx b/frontend/src/basic/OrderPage/index.tsx index 5eedeeaa2..5f0a0c2d2 100644 --- a/frontend/src/basic/OrderPage/index.tsx +++ b/frontend/src/basic/OrderPage/index.tsx @@ -173,6 +173,7 @@ const OrderPage = ({
void; + settings: Settings; } interface FailureReasonProps { @@ -47,6 +48,7 @@ export const RoutingFailedPrompt = ({ loadingLightning, lightning, setLightning, + settings, }: RoutingFailedPromptProps): JSX.Element => { const { t } = useTranslation(); @@ -96,6 +98,7 @@ export const RoutingFailedPrompt = ({ diff --git a/frontend/src/components/TradeBox/index.tsx b/frontend/src/components/TradeBox/index.tsx index e5f414949..f0b503d11 100644 --- a/frontend/src/components/TradeBox/index.tsx +++ b/frontend/src/components/TradeBox/index.tsx @@ -434,6 +434,7 @@ const TradeBox = ({ return (