mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 02:21:35 +00:00
Add advanced options to LN payout form (#326)
* Add advanced options to LN payout form * Complete amount calcs * Temporary working solution for lnproxy web only (uses text instead of json) * Update LNpayment model and logics to use user's routing budget * Add handle lnproxyserver networks (i2p, tor, clearnet) / (mainnet,testnet) * Small fixes
This commit is contained in:
parent
6b2dedce13
commit
86e6bed37c
@ -258,7 +258,7 @@ class LNNode:
|
||||
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 @@ class LNNode:
|
||||
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 @@ class LNNode:
|
||||
# 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
|
||||
|
||||
|
@ -721,7 +721,7 @@ class Logics:
|
||||
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 @@ class Logics:
|
||||
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 @@ class Logics:
|
||||
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 @@ class Logics:
|
||||
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 @@ class Logics:
|
||||
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
|
||||
|
@ -126,6 +126,19 @@ class LNPayment(models.Model):
|
||||
MaxValueValidator(1.5 * MAX_TRADE),
|
||||
]
|
||||
)
|
||||
# Routing budget in PPM
|
||||
routing_budget_ppm = 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
|
||||
|
@ -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
|
||||
)
|
||||
|
22
api/tasks.py
22
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
|
||||
|
@ -501,6 +501,7 @@ class OrderView(viewsets.ViewSet):
|
||||
# 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 @@ class OrderView(viewsets.ViewSet):
|
||||
|
||||
# 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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 => {
|
||||
<OrderPage
|
||||
baseUrl={baseUrl}
|
||||
order={order}
|
||||
settings={settings}
|
||||
setOrder={setOrder}
|
||||
setCurrentOrder={setCurrentOrder}
|
||||
badOrder={badOrder}
|
||||
|
@ -7,12 +7,13 @@ import TradeBox from '../../components/TradeBox';
|
||||
import OrderDetails from '../../components/OrderDetails';
|
||||
|
||||
import { Page } from '../NavBar';
|
||||
import { Order } from '../../models';
|
||||
import { Order, Settings } from '../../models';
|
||||
import { apiClient } from '../../services/api';
|
||||
|
||||
interface OrderPageProps {
|
||||
windowSize: { width: number; height: number };
|
||||
order: Order;
|
||||
settings: Settings;
|
||||
setOrder: (state: Order) => 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 = ({
|
||||
>
|
||||
<TradeBox
|
||||
order={order}
|
||||
settings={settings}
|
||||
setOrder={setOrder}
|
||||
setBadOrder={setBadOrder}
|
||||
baseUrl={baseUrl}
|
||||
@ -170,6 +173,7 @@ const OrderPage = ({
|
||||
<div style={{ display: tab == 'contract' ? '' : 'none' }}>
|
||||
<TradeBox
|
||||
order={order}
|
||||
settings={settings}
|
||||
setOrder={setOrder}
|
||||
setBadOrder={setBadOrder}
|
||||
baseUrl={baseUrl}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { StringIfPlural, useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Tooltip,
|
||||
Alert,
|
||||
@ -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}`,
|
||||
},
|
||||
@ -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 (
|
||||
<StyledTooltip
|
||||
open={show}
|
||||
style={{ padding: 0, backgroundColor: 'black' }}
|
||||
placement={windowWidth > 60 ? 'left' : 'bottom'}
|
||||
title={
|
||||
<Alert
|
||||
|
@ -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'}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
@ -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 <span> {t('The order has expired')}</span>;
|
||||
} 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 ? (
|
||||
<b>
|
||||
<span style={{ color: col }}>
|
||||
{`${hours}h ${zeroPad(minutes)}m ${zeroPad(seconds)}s `}
|
||||
</span>
|
||||
</b>
|
||||
<Typography color={color}>
|
||||
<b>{`${hours}h ${zeroPad(minutes)}m ${zeroPad(seconds)}s `}</b>
|
||||
</Typography>
|
||||
) : (
|
||||
<span style={{ color: col }}>{`${hours}h ${zeroPad(minutes)}m ${zeroPad(seconds)}s `}</span>
|
||||
<Typography color={color}>
|
||||
{`${hours}h ${zeroPad(minutes)}m ${zeroPad(seconds)}s `}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -77,7 +77,7 @@ const ChatHeader: React.FC<Props> = ({ connected, peerConnected, turtleMode, set
|
||||
>
|
||||
<Typography align='center' variant='caption' sx={{ color: connectedTextColor }}>
|
||||
{t('Peer') + ': '}
|
||||
{peerConnected ? t('connected') : t('disconnected')}
|
||||
{connected ? (peerConnected ? t('connected') : t('disconnected')) : '...waiting'}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
|
@ -72,6 +72,7 @@ const EncryptedSocketChat: React.FC<Props> = ({
|
||||
useEffect(() => {
|
||||
if (![9, 10].includes(status)) {
|
||||
connection?.close();
|
||||
setConnection(undefined);
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
|
@ -1,28 +1,71 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Grid, Typography, TextField } from '@mui/material';
|
||||
import { Order } from '../../../models';
|
||||
import {
|
||||
Box,
|
||||
Grid,
|
||||
Typography,
|
||||
TextField,
|
||||
Tooltip,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
useTheme,
|
||||
Collapse,
|
||||
Switch,
|
||||
MenuItem,
|
||||
Select,
|
||||
InputAdornment,
|
||||
Button,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
IconButton,
|
||||
FormHelperText,
|
||||
} from '@mui/material';
|
||||
import { Order, Settings } from '../../../models';
|
||||
import WalletsButton from '../WalletsButton';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { pn } from '../../../utils';
|
||||
|
||||
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;
|
||||
routingBudget: number;
|
||||
amount: number;
|
||||
advancedOptions: boolean;
|
||||
useCustomBudget: boolean;
|
||||
routingBudgetUnit: 'PPM' | 'Sats';
|
||||
routingBudgetPPM: number;
|
||||
routingBudgetSats: number | undefined;
|
||||
badInvoice: string;
|
||||
useLnproxy: boolean;
|
||||
lnproxyServer: string;
|
||||
lnproxyBudget: number;
|
||||
lnproxyInvoice: string;
|
||||
lnproxyAmount: number;
|
||||
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: undefined,
|
||||
badInvoice: '',
|
||||
useLnproxy: false,
|
||||
lnproxyServer: '',
|
||||
lnproxyBudget: 0,
|
||||
lnproxyInvoice: '',
|
||||
lnproxyAmount: 0,
|
||||
lnproxyServer: 0,
|
||||
lnproxyBudgetUnit: 'Sats',
|
||||
lnproxyBudgetPPM: 0,
|
||||
lnproxyBudgetSats: 0,
|
||||
badLnproxy: '',
|
||||
};
|
||||
|
||||
@ -32,6 +75,7 @@ interface LightningPayoutFormProps {
|
||||
lightning: LightningForm;
|
||||
setLightning: (state: LightningForm) => void;
|
||||
onClickSubmit: (invoice: string) => void;
|
||||
settings: Settings;
|
||||
}
|
||||
|
||||
export const LightningPayoutForm = ({
|
||||
@ -40,48 +84,551 @@ export const LightningPayoutForm = ({
|
||||
onClickSubmit,
|
||||
lightning,
|
||||
setLightning,
|
||||
settings,
|
||||
}: LightningPayoutFormProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
||||
const [loadingLnproxy, setLoadingLnproxy] = useState<boolean>(false);
|
||||
const [badLnproxyServer, setBadLnproxyServer] = useState<string>('');
|
||||
|
||||
const computeInvoiceAmount = function () {
|
||||
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 && invoice.length > 20) {
|
||||
return 'Invalid invoice amount';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const amount = computeInvoiceAmount();
|
||||
setLightning({
|
||||
...lightning,
|
||||
amount,
|
||||
lnproxyAmount: amount - lightning.lnproxyBudgetSats,
|
||||
routingBudgetSats:
|
||||
lightning.routingBudgetSats == undefined
|
||||
? 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 () {
|
||||
console.log(settings);
|
||||
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
|
||||
// .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);
|
||||
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: text, badLnproxy: '' });
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setLightning({ ...lightning, badLnproxy: 'Lnproxy server uncaught error' });
|
||||
})
|
||||
.finally(() => {
|
||||
setLoadingLnproxy(false);
|
||||
});
|
||||
};
|
||||
|
||||
const onProxyBudgetChange = function (e) {
|
||||
if (isFinite(e.target.value) && e.target.value >= 0) {
|
||||
let lnproxyBudgetSats;
|
||||
let lnproxyBudgetPPM;
|
||||
|
||||
if (lightning.lnproxyBudgetUnit === 'Sats') {
|
||||
lnproxyBudgetSats = Math.floor(e.target.value);
|
||||
lnproxyBudgetPPM = Math.round((lnproxyBudgetSats * 1000000) / lightning.amount);
|
||||
} else {
|
||||
lnproxyBudgetPPM = e.target.value;
|
||||
lnproxyBudgetSats = Math.ceil((lightning.amount / 1000000) * lnproxyBudgetPPM);
|
||||
}
|
||||
|
||||
if (lnproxyBudgetPPM < 99999) {
|
||||
const lnproxyAmount = lightning.amount - lnproxyBudgetSats;
|
||||
setLightning({ ...lightning, lnproxyBudgetSats, lnproxyBudgetPPM, lnproxyAmount });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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) / tradeAmount);
|
||||
} else {
|
||||
routingBudgetPPM = e.target.value;
|
||||
routingBudgetSats = Math.ceil((lightning.amount / 1000000) * routingBudgetPPM);
|
||||
}
|
||||
|
||||
if (routingBudgetPPM < 99999) {
|
||||
const amount = Math.floor(
|
||||
tradeAmount - tradeAmount * (lightning.routingBudgetPPM / 1000000),
|
||||
);
|
||||
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 (
|
||||
<Grid container direction='column' justifyContent='flex-start' alignItems='center' spacing={1}>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant='body2'>
|
||||
{t('Submit a valid invoice for {{amountSats}} Satoshis.', {
|
||||
amountSats: pn(order.invoice_amount),
|
||||
})}
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<WalletsButton />
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth={true}
|
||||
error={lightning.badInvoice != ''}
|
||||
helperText={lightning.badInvoice ? t(lightning.badInvoice) : ''}
|
||||
label={t('Payout Lightning Invoice')}
|
||||
required
|
||||
value={lightning.invoice}
|
||||
inputProps={{
|
||||
style: { textAlign: 'center', maxHeight: '14.28em' },
|
||||
<div style={{ height: '0.3em' }} />
|
||||
<Grid
|
||||
item
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
height: '1.1em',
|
||||
}}
|
||||
>
|
||||
<Typography color='text.primary'>{t('Advanced options')}</Typography>
|
||||
<Switch
|
||||
size='small'
|
||||
checked={lightning.advancedOptions}
|
||||
onChange={(e) => {
|
||||
const checked = e.target.checked;
|
||||
setLightning({
|
||||
...lightning,
|
||||
advancedOptions: checked,
|
||||
});
|
||||
}}
|
||||
multiline
|
||||
minRows={4}
|
||||
maxRows={8}
|
||||
onChange={(e) => setLightning({ ...lightning, invoice: e.target.value ?? '' })}
|
||||
/>
|
||||
<SelfImprovement sx={{ color: 'text.primary' }} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
onClick={() => onClickSubmit(lightning.invoice)}
|
||||
variant='outlined'
|
||||
color='primary'
|
||||
|
||||
<Grid item>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: 'background.paper',
|
||||
border: '1px solid',
|
||||
width: '18em',
|
||||
borderRadius: '0.3em',
|
||||
borderColor: theme.palette.mode === 'dark' ? '#434343' : '#c4c4c4',
|
||||
padding: '1em',
|
||||
}}
|
||||
>
|
||||
{t('Submit')}
|
||||
</LoadingButton>
|
||||
<Grid
|
||||
container
|
||||
direction='column'
|
||||
justifyContent='flex-start'
|
||||
alignItems='center'
|
||||
spacing={0.5}
|
||||
>
|
||||
<Collapse in={lightning.advancedOptions}>
|
||||
<Grid
|
||||
container
|
||||
direction='column'
|
||||
justifyContent='flex-start'
|
||||
alignItems='center'
|
||||
spacing={0.5}
|
||||
padding={0.5}
|
||||
>
|
||||
<Grid item>
|
||||
<TextField
|
||||
sx={{ width: '14em' }}
|
||||
disabled={!lightning.advancedOptions}
|
||||
error={routingBudgetHelper() != ''}
|
||||
helperText={routingBudgetHelper()}
|
||||
label={t('Routing Budget')}
|
||||
required={true}
|
||||
value={
|
||||
lightning.routingBudgetUnit == 'PPM'
|
||||
? lightning.routingBudgetPPM
|
||||
: lightning.routingBudgetSats
|
||||
}
|
||||
variant='outlined'
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position='end'>
|
||||
<Button
|
||||
variant='text'
|
||||
onClick={() => {
|
||||
setLightning({
|
||||
...lightning,
|
||||
routingBudgetUnit:
|
||||
lightning.routingBudgetUnit == 'PPM' ? 'Sats' : 'PPM',
|
||||
});
|
||||
}}
|
||||
>
|
||||
{lightning.routingBudgetUnit}
|
||||
</Button>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
inputProps={{
|
||||
style: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
}}
|
||||
onChange={onRoutingBudgetChange}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{window.NativeRobosats === undefined ? (
|
||||
<Grid item>
|
||||
<Tooltip
|
||||
enterTouchDelay={0}
|
||||
leaveTouchDelay={4000}
|
||||
placement='top'
|
||||
title={t(
|
||||
`Wrap this invoice using a Lnproxy server to protect your privacy (hides the receiving wallet).`,
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<FormControlLabel
|
||||
onChange={(e) =>
|
||||
setLightning({
|
||||
...lightning,
|
||||
useLnproxy: e.target.checked,
|
||||
invoice: e.target.checked ? '' : lightning.invoice,
|
||||
})
|
||||
}
|
||||
checked={lightning.useLnproxy}
|
||||
control={<Checkbox />}
|
||||
label={
|
||||
<Typography color={lightning.useLnproxy ? 'primary' : 'text.secondary'}>
|
||||
{t('Use Lnproxy')}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<Grid item>
|
||||
<Collapse in={lightning.useLnproxy}>
|
||||
<Grid
|
||||
container
|
||||
direction='column'
|
||||
justifyContent='flex-start'
|
||||
alignItems='center'
|
||||
spacing={1}
|
||||
>
|
||||
<Grid item>
|
||||
<FormControl error={badLnproxyServer != ''}>
|
||||
<InputLabel id='select-label'>{t('Server')}</InputLabel>
|
||||
<Select
|
||||
sx={{ width: '14em' }}
|
||||
label={t('Server')}
|
||||
labelId='select-label'
|
||||
value={lightning.lnproxyServer}
|
||||
onChange={(e) =>
|
||||
setLightning({ ...lightning, lnproxyServer: Number(e.target.value) })
|
||||
}
|
||||
>
|
||||
{lnproxies.map((lnproxyServer, index) => (
|
||||
<MenuItem key={index} value={index}>
|
||||
<Typography>{lnproxyServer.name}</Typography>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{badLnproxyServer != '' ? (
|
||||
<FormHelperText>{t(badLnproxyServer)}</FormHelperText>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</FormControl>
|
||||
</Grid>
|
||||
|
||||
<Grid item>
|
||||
<TextField
|
||||
sx={{ width: '14em' }}
|
||||
disabled={!lightning.useLnproxy}
|
||||
error={lnProxyBudgetHelper() != ''}
|
||||
helperText={lnProxyBudgetHelper()}
|
||||
label={t('Proxy Budget')}
|
||||
value={
|
||||
lightning.lnproxyBudgetUnit == 'PPM'
|
||||
? lightning.lnproxyBudgetPPM
|
||||
: lightning.lnproxyBudgetSats
|
||||
}
|
||||
variant='outlined'
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position='end'>
|
||||
<Button
|
||||
variant='text'
|
||||
onClick={() => {
|
||||
setLightning({
|
||||
...lightning,
|
||||
lnproxyBudgetUnit:
|
||||
lightning.lnproxyBudgetUnit == 'PPM' ? 'Sats' : 'PPM',
|
||||
});
|
||||
}}
|
||||
>
|
||||
{lightning.lnproxyBudgetUnit}
|
||||
</Button>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
inputProps={{
|
||||
style: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
}}
|
||||
onChange={onProxyBudgetChange}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Collapse>
|
||||
|
||||
<Grid item>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Typography align='center' variant='body2'>
|
||||
{t('Submit invoice for {{amountSats}} Sats', {
|
||||
amountSats: pn(
|
||||
lightning.useLnproxy ? lightning.lnproxyAmount : lightning.amount,
|
||||
),
|
||||
})}
|
||||
</Typography>
|
||||
<Tooltip disableHoverListener enterTouchDelay={0} title={t('Copied!')}>
|
||||
<IconButton
|
||||
sx={{ height: '0.5em' }}
|
||||
onClick={() =>
|
||||
systemClient.copyToClipboard(
|
||||
lightning.useLnproxy ? lightning.lnproxyAmount : lightning.amount,
|
||||
)
|
||||
}
|
||||
>
|
||||
<ContentCopy sx={{ width: '0.8em' }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Grid>
|
||||
|
||||
<Grid item>
|
||||
{lightning.useLnproxy ? (
|
||||
<TextField
|
||||
fullWidth={true}
|
||||
disabled={!lightning.useLnproxy}
|
||||
error={lightning.badLnproxy != ''}
|
||||
helperText={lightning.badLnproxy ? t(lightning.badLnproxy) : ''}
|
||||
label={t('Invoice to wrap')}
|
||||
required
|
||||
value={lightning.lnproxyInvoice}
|
||||
inputProps={{
|
||||
style: { textAlign: 'center' },
|
||||
}}
|
||||
variant='outlined'
|
||||
onChange={(e) =>
|
||||
setLightning({ ...lightning, lnproxyInvoice: e.target.value ?? '' })
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<TextField
|
||||
fullWidth={true}
|
||||
sx={lightning.useLnproxy ? { borderRadius: 0 } : {}}
|
||||
disabled={lightning.useLnproxy}
|
||||
error={lightning.badInvoice != ''}
|
||||
helperText={lightning.badInvoice ? t(lightning.badInvoice) : ''}
|
||||
label={lightning.useLnproxy ? t('Wrapped invoice') : t('Payout Lightning Invoice')}
|
||||
required
|
||||
value={lightning.invoice}
|
||||
inputProps={{
|
||||
style: { textAlign: 'center', maxHeight: '8em' },
|
||||
}}
|
||||
variant={lightning.useLnproxy ? 'filled' : 'standard'}
|
||||
multiline={lightning.useLnproxy ? false : true}
|
||||
minRows={3}
|
||||
maxRows={5}
|
||||
onChange={(e) => setLightning({ ...lightning, invoice: e.target.value ?? '' })}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item>
|
||||
{lightning.useLnproxy ? (
|
||||
<LoadingButton
|
||||
loading={loadingLnproxy}
|
||||
disabled={
|
||||
lightning.lnproxyInvoice.length < 20 || badLnproxyServer || lightning.badLnproxy
|
||||
}
|
||||
onClick={fetchLnproxy}
|
||||
variant='outlined'
|
||||
color='primary'
|
||||
>
|
||||
{t('Wrap')}
|
||||
</LoadingButton>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
disabled={lightning.invoice == ''}
|
||||
onClick={() => onClickSubmit(lightning.invoice)}
|
||||
variant='outlined'
|
||||
color='primary'
|
||||
>
|
||||
{t('Submit')}
|
||||
</LoadingButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
{/* <Grid item>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: 'background.paper',
|
||||
border: '1px solid',
|
||||
borderRadius: '0.3em',
|
||||
width: '18em',
|
||||
borderColor: theme.palette.mode === 'dark' ? '#434343' : '#c4c4c4',
|
||||
'&:hover': {
|
||||
borderColor: theme.palette.mode === 'dark' ? '#ffffff' : '#2f2f2f',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
container
|
||||
direction='column'
|
||||
justifyContent='flex-start'
|
||||
alignItems='center'
|
||||
spacing={0.5}
|
||||
padding={0.5}
|
||||
>
|
||||
<Collapse in={lightning.advancedOptions}>
|
||||
<Tooltip
|
||||
enterTouchDelay={0}
|
||||
leaveTouchDelay={4000}
|
||||
placement='top'
|
||||
title={t(
|
||||
`Set custom routing budget for the payout. If you don't know what this is, simply do not touch.`,
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<FormControlLabel
|
||||
checked={lightning.useCustomBudget}
|
||||
onChange={(e) =>
|
||||
setLightning({
|
||||
...lightning,
|
||||
useCustomBudget: e.target.checked,
|
||||
routingBudgetSats: defaultLightning.routingBudgetSats,
|
||||
routingBudgetPPM: defaultLightning.routingBudgetPPM,
|
||||
})
|
||||
}
|
||||
control={<Checkbox />}
|
||||
label={
|
||||
<Typography
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
color={lightning.useCustomBudget ? 'primary' : 'text.secondary'}
|
||||
>
|
||||
{t('Use custom routing budget')}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Collapse>
|
||||
|
||||
</Grid>
|
||||
</Box>
|
||||
</Grid> */}
|
||||
|
||||
<Grid item>
|
||||
<WalletsButton />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
@ -4,7 +4,7 @@ import { Grid, Typography, ToggleButtonGroup, ToggleButton } from '@mui/material
|
||||
|
||||
import currencies from '../../../../static/assets/currencies.json';
|
||||
|
||||
import { Order } from '../../../models';
|
||||
import { Order, Settings } from '../../../models';
|
||||
import { pn } from '../../../utils';
|
||||
import { Bolt, Link } from '@mui/icons-material';
|
||||
import { LightningPayoutForm, LightningForm, OnchainPayoutForm, OnchainForm } from '../Forms';
|
||||
@ -19,6 +19,7 @@ interface PayoutPrompProps {
|
||||
onchain: OnchainForm;
|
||||
setOnchain: (state: OnchainForm) => 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}`];
|
||||
@ -65,9 +67,9 @@ export const PayoutPrompt = ({
|
||||
size='small'
|
||||
value={tab}
|
||||
exclusive
|
||||
onChange={(mouseEvent, value: string) => setTab(value)}
|
||||
onChange={(mouseEvent, value) => setTab(value == null ? tab : value)}
|
||||
>
|
||||
<ToggleButton value='lightning' disableRipple={true}>
|
||||
<ToggleButton value='lightning'>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
@ -97,6 +99,7 @@ export const PayoutPrompt = ({
|
||||
<Grid item style={{ display: tab == 'lightning' ? '' : 'none' }}>
|
||||
<LightningPayoutForm
|
||||
order={order}
|
||||
settings={settings}
|
||||
loading={loadingLightning}
|
||||
lightning={lightning}
|
||||
setLightning={setLightning}
|
||||
|
@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Box, CircularProgress, Grid, Typography, useTheme } from '@mui/material';
|
||||
import Countdown, { CountdownRenderProps, zeroPad } from 'react-countdown';
|
||||
|
||||
import { Order } from '../../../models';
|
||||
import { Order, Settings } from '../../../models';
|
||||
import { LightningForm, LightningPayoutForm } from '../Forms';
|
||||
|
||||
interface RoutingFailedPromptProps {
|
||||
@ -12,6 +12,7 @@ interface RoutingFailedPromptProps {
|
||||
lightning: LightningForm;
|
||||
loadingLightning: boolean;
|
||||
setLightning: (state: LightningForm) => void;
|
||||
settings: Settings;
|
||||
}
|
||||
|
||||
interface FailureReasonProps {
|
||||
@ -28,6 +29,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',
|
||||
}}
|
||||
>
|
||||
<Typography variant='body2' align='center'>
|
||||
@ -46,6 +48,7 @@ export const RoutingFailedPrompt = ({
|
||||
loadingLightning,
|
||||
lightning,
|
||||
setLightning,
|
||||
settings,
|
||||
}: RoutingFailedPromptProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -95,6 +98,7 @@ export const RoutingFailedPrompt = ({
|
||||
<Grid item>
|
||||
<LightningPayoutForm
|
||||
order={order}
|
||||
settings={settings}
|
||||
loading={loadingLightning}
|
||||
lightning={lightning}
|
||||
setLightning={setLightning}
|
||||
|
@ -263,7 +263,7 @@ const TradeSummary = ({
|
||||
primary={t('{{revenueSats}} Sats', {
|
||||
revenueSats: pn(platformSummary.trade_revenue_sats),
|
||||
})}
|
||||
secondary={t('Platform trade revenue')}
|
||||
secondary={t('Coordinator trade revenue')}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
@ -273,9 +273,9 @@ const TradeSummary = ({
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t('{{routingFeeSats}} MiliSats', {
|
||||
routingFeeSats: pn(platformSummary.routing_fee_sats),
|
||||
routingFeeSats: pn(platformSummary.routing_budget_sats),
|
||||
})}
|
||||
secondary={t('Platform covered routing fee')}
|
||||
secondary={t('Routing budget')}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
|
@ -44,7 +44,7 @@ import {
|
||||
defaultDispute,
|
||||
} from './Forms';
|
||||
|
||||
import { Order } from '../../models';
|
||||
import { Order, Settings } from '../../models';
|
||||
import { EncryptedChatMessage } from './EncryptedChat';
|
||||
import { systemClient } from '../../services/System';
|
||||
import CollabCancelAlert from './CollabCancelAlert';
|
||||
@ -96,12 +96,14 @@ interface TradeBoxProps {
|
||||
setBadOrder: (state: string | undefined) => void;
|
||||
onRenewOrder: () => void;
|
||||
onStartAgain: () => void;
|
||||
settings: Settings;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
const TradeBox = ({
|
||||
order,
|
||||
setOrder,
|
||||
settings,
|
||||
baseUrl,
|
||||
setBadOrder,
|
||||
onRenewOrder,
|
||||
@ -134,6 +136,7 @@ const TradeBox = ({
|
||||
| 'submit_statement'
|
||||
| 'rate_platform';
|
||||
invoice?: string;
|
||||
routing_budget_ppm?: number;
|
||||
address?: string;
|
||||
mining_fee_rate?: number;
|
||||
statement?: string;
|
||||
@ -143,6 +146,7 @@ const TradeBox = ({
|
||||
const submitAction = function ({
|
||||
action,
|
||||
invoice,
|
||||
routing_budget_ppm,
|
||||
address,
|
||||
mining_fee_rate,
|
||||
statement,
|
||||
@ -152,6 +156,7 @@ const TradeBox = ({
|
||||
.post(baseUrl, '/api/order/?order_id=' + order.id, {
|
||||
action,
|
||||
invoice,
|
||||
routing_budget_ppm,
|
||||
address,
|
||||
mining_fee_rate,
|
||||
statement,
|
||||
@ -201,7 +206,11 @@ const TradeBox = ({
|
||||
|
||||
const updateInvoice = function (invoice: string) {
|
||||
setLoadingButtons({ ...noLoadingButtons, submitInvoice: true });
|
||||
submitAction({ action: 'update_invoice', invoice });
|
||||
submitAction({
|
||||
action: 'update_invoice',
|
||||
invoice,
|
||||
routing_budget_ppm: lightning.routingBudgetPPM,
|
||||
});
|
||||
};
|
||||
|
||||
const updateAddress = function () {
|
||||
@ -252,7 +261,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);
|
||||
@ -377,6 +386,7 @@ const TradeBox = ({
|
||||
return (
|
||||
<PayoutPrompt
|
||||
order={order}
|
||||
settings={settings}
|
||||
onClickSubmitInvoice={updateInvoice}
|
||||
loadingLightning={loadingButtons.submitInvoice}
|
||||
lightning={lightning}
|
||||
@ -424,6 +434,7 @@ const TradeBox = ({
|
||||
return (
|
||||
<PayoutPrompt
|
||||
order={order}
|
||||
settings={settings}
|
||||
onClickSubmitInvoice={updateInvoice}
|
||||
loadingLightning={loadingButtons.submitInvoice}
|
||||
lightning={lightning}
|
||||
@ -549,6 +560,7 @@ const TradeBox = ({
|
||||
return (
|
||||
<RoutingFailedPrompt
|
||||
order={order}
|
||||
settings={settings}
|
||||
onClickSubmitInvoice={updateInvoice}
|
||||
loadingLightning={loadingButtons.submitInvoice}
|
||||
lightning={lightning}
|
||||
|
@ -1,5 +1,6 @@
|
||||
export interface Coordinator {
|
||||
alias: string;
|
||||
enabled: boolean;
|
||||
description: string | undefined;
|
||||
coverLetter: string | undefined;
|
||||
logo: string;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
11
frontend/static/lnproxies.json
Normal file
11
frontend/static/lnproxies.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"name": "↬ Lnproxy Dev",
|
||||
"mainnetClearnet": "lnproxy.org",
|
||||
"mainnetTOR": "rdq6tvulanl7aqtupmoboyk2z3suzkdwurejwyjyjf4itr3zhxrm2lad.onion",
|
||||
"mainnetI2P": "undefined",
|
||||
"testnetClearnet": "undefined",
|
||||
"testnetTOR": "undefined",
|
||||
"testnetI2P": "undefined"
|
||||
}
|
||||
]
|
Loading…
Reference in New Issue
Block a user