From dc9d5e5e2ad9fad7d35392649073fe4dd040987a Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Sat, 11 Jun 2022 06:12:09 -0700 Subject: [PATCH] Add frontend input address components --- api/logics.py | 31 +++-- api/models.py | 33 ++--- api/views.py | 2 +- control/models.py | 2 +- frontend/src/components/TradeBox.js | 171 ++++++++++++++++++------- frontend/src/components/UserGenPage.js | 2 +- 6 files changed, 168 insertions(+), 73 deletions(-) diff --git a/api/logics.py b/api/logics.py index 75880c78..09908954 100644 --- a/api/logics.py +++ b/api/logics.py @@ -504,7 +504,7 @@ class Logics: MIN_POINT = float(config('MIN_POINT')) MAX_SWAP_FEE = float(config('MAX_SWAP_FEE')) MAX_POINT = float(config('MAX_POINT')) - if balance.onchain_fraction > MIN_POINT: + if float(balance.onchain_fraction) > MIN_POINT: swap_fee_rate = MIN_SWAP_FEE else: slope = (MAX_SWAP_FEE - MIN_SWAP_FEE) / (MAX_POINT - MIN_POINT) @@ -514,31 +514,44 @@ class Logics: MIN_SWAP_FEE = float(config('MIN_SWAP_FEE')) MAX_SWAP_FEE = float(config('MAX_SWAP_FEE')) SWAP_LAMBDA = float(config('SWAP_LAMBDA')) - swap_fee_rate = MIN_SWAP_FEE + (MAX_SWAP_FEE - MIN_SWAP_FEE) * math.exp(-SWAP_LAMBDA * balance.onchain_fraction) + swap_fee_rate = MIN_SWAP_FEE + (MAX_SWAP_FEE - MIN_SWAP_FEE) * math.exp(-SWAP_LAMBDA * float(balance.onchain_fraction)) + print("MIN_SWAP_FEE",MIN_SWAP_FEE) + print("MAX_SWAP_FEE",MAX_SWAP_FEE) + print("SWAP_LAMBDA",SWAP_LAMBDA) + print("swap_fee_rate",swap_fee_rate) - return swap_fee_rate + return swap_fee_rate * 100 @classmethod - def create_onchain_payment(cls, order, preliminary_amount): + def create_onchain_payment(cls, order, user, preliminary_amount): ''' Creates an empty OnchainPayment for order.payout_tx. It sets the fees to be applied to this order if onchain Swap is used. If the user submits a LN invoice instead. The returned OnchainPayment goes unused. ''' - onchain_payment = OnchainPayment.objects.create() + onchain_payment = OnchainPayment.objects.create(receiver=user) # Compute a safer available onchain liquidity: (confirmed_utxos - reserve - pending_outgoing_txs)) # Accounts for already committed outgoing TX for previous users. confirmed = onchain_payment.balance.onchain_confirmed reserve = 0.01 * onchain_payment.balance.total # We assume a reserve of 1% pending_txs = OnchainPayment.objects.filter(status=OnchainPayment.Status.VALID).aggregate(Sum('num_satoshis'))['num_satoshis__sum'] - + + if pending_txs == None: + pending_txs = 0 + available_onchain = confirmed - reserve - pending_txs if preliminary_amount > available_onchain: # Not enough onchain balance to commit for this swap. return False - onchain_payment.suggested_mining_fee_rate = LNNode.estimate_fee(amount_sats=preliminary_amount) - onchain_payment.swap_fee_rate = cls.compute_swap_fee_rate(onchain_payment.preliminary_amount) + suggested_mining_fee_rate = LNNode.estimate_fee(amount_sats=preliminary_amount)["mining_fee_rate"] + + # Hardcap mining fee suggested at 50 sats/vbyte + if suggested_mining_fee_rate > 50: + suggested_mining_fee_rate = 50 + + onchain_payment.suggested_mining_fee_rate = LNNode.estimate_fee(amount_sats=preliminary_amount)["mining_fee_rate"] + onchain_payment.swap_fee_rate = cls.compute_swap_fee_rate(onchain_payment.balance) onchain_payment.save() order.payout_tx = onchain_payment @@ -577,7 +590,7 @@ class Logics: if order.payout_tx == None: # Creates the OnchainPayment object and checks node balance - valid, _ = cls.create_onchain_payment(order, preliminary_amount=context["invoice_amount"]) + valid = cls.create_onchain_payment(order, user, preliminary_amount=context["invoice_amount"]) if not valid: context["swap_allowed"] = False context["swap_failure_reason"] = "Not enough onchain liquidity available to offer swaps" diff --git a/api/models.py b/api/models.py index b9ac9698..9d3d7f1a 100644 --- a/api/models.py +++ b/api/models.py @@ -188,7 +188,7 @@ class OnchainPayment(models.Model): default=Concepts.PAYBUYER) status = models.PositiveSmallIntegerField(choices=Status.choices, null=False, - default=Status.VALID) + default=Status.CREAT) # payment info address = models.CharField(max_length=100, @@ -203,10 +203,11 @@ class OnchainPayment(models.Model): default=None, blank=True) - num_satoshis = models.PositiveBigIntegerField(validators=[ - MinValueValidator(0.7 * MIN_SWAP_AMOUNT), - MaxValueValidator(1.5 * MAX_TRADE), - ]) + num_satoshis = models.PositiveBigIntegerField(null=True, + validators=[ + MinValueValidator(0.7 * MIN_SWAP_AMOUNT), + MaxValueValidator(1.5 * MAX_TRADE), + ]) # fee in sats/vbyte with mSats decimals fee_msat suggested_mining_fee_rate = models.DecimalField(max_digits=6, @@ -232,7 +233,7 @@ class OnchainPayment(models.Model): swap_fee_rate = models.DecimalField(max_digits=4, decimal_places=2, - default=2, + default=float(config("MIN_SWAP_FEE"))*100, null=False, blank=False) @@ -454,6 +455,8 @@ class Order(models.Model): default=None, blank=True, ) + # is buyer payout a LN invoice (false) or on chain address (true) + is_swap = models.BooleanField(default=False, null=False) # buyer payment LN invoice payout = models.OneToOneField( LNPayment, @@ -463,15 +466,15 @@ class Order(models.Model): default=None, blank=True, ) - - # payout_tx = models.OneToOneField( - # OnchainPayment, - # related_name="order_paid_TX", - # on_delete=models.SET_NULL, - # null=True, - # default=None, - # blank=True, - # ) + # buyer payment address + payout_tx = models.OneToOneField( + OnchainPayment, + related_name="order_paid_TX", + on_delete=models.SET_NULL, + null=True, + default=None, + blank=True, + ) # ratings maker_rated = models.BooleanField(default=False, null=False) diff --git a/api/views.py b/api/views.py index 4425f0e2..0068c59a 100644 --- a/api/views.py +++ b/api/views.py @@ -337,7 +337,7 @@ class OrderView(viewsets.ViewSet): elif data["is_buyer"] and (order.status == Order.Status.WF2 or order.status == Order.Status.WFI): - # If the two bonds are locked, reply with an AMOUNT so he can send the buyer invoice. + # If the two bonds are locked, reply with an AMOUNT and onchain swap cost so he can send the buyer invoice/address if (order.maker_bond.status == order.taker_bond.status == LNPayment.Status.LOCKED): valid, context = Logics.payout_amount(order, request.user) diff --git a/control/models.py b/control/models.py index ba6ac415..7222e04a 100755 --- a/control/models.py +++ b/control/models.py @@ -78,7 +78,7 @@ class BalanceLog(models.Model): def get_total(): return LNNode.wallet_balance()['total_balance'] + LNNode.channel_balance()['local_balance'] def get_frac(): - return (LNNode.wallet_balance()['total_balance'] + LNNode.channel_balance()['local_balance']) / LNNode.wallet_balance()['total_balance'] + return LNNode.wallet_balance()['total_balance'] / (LNNode.wallet_balance()['total_balance'] + LNNode.channel_balance()['local_balance']) def get_oc_total(): return LNNode.wallet_balance()['total_balance'] def get_oc_conf(): diff --git a/frontend/src/components/TradeBox.js b/frontend/src/components/TradeBox.js index d162e9ff..2a0572e1 100644 --- a/frontend/src/components/TradeBox.js +++ b/frontend/src/components/TradeBox.js @@ -1,6 +1,6 @@ import React, { Component } from "react"; import { withTranslation, Trans} from "react-i18next"; -import { IconButton, Box, Link, Paper, Rating, Button, Tooltip, CircularProgress, Grid, Typography, TextField, List, ListItem, ListItemText, Divider, ListItemIcon, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from "@mui/material" +import { Tabs, Tab, IconButton, Box, Link, Paper, Rating, Button, Tooltip, CircularProgress, Grid, Typography, TextField, List, ListItem, ListItemText, Divider, ListItemIcon, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from "@mui/material" import QRCode from "react-qr-code"; import Countdown, { zeroPad} from 'react-countdown'; import Chat from "./EncryptedChat" @@ -18,6 +18,8 @@ import BalanceIcon from '@mui/icons-material/Balance'; import ContentCopy from "@mui/icons-material/ContentCopy"; import PauseCircleIcon from '@mui/icons-material/PauseCircle'; import PlayCircleIcon from '@mui/icons-material/PlayCircle'; +import BoltIcon from '@mui/icons-material/Bolt'; +import LinkIcon from '@mui/icons-material/Link'; import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet'; import { NewTabIcon } from "./Icons"; @@ -33,6 +35,7 @@ class TradeBox extends Component { openConfirmFiatReceived: false, openConfirmDispute: false, openEnableTelegram: false, + receiveTab: 0, badInvoice: false, badStatement: false, qrscanner: false, @@ -599,57 +602,133 @@ class TradeBox extends Component { {/* Make confirmation sound for HTLC received. */} {this.Sound("locked-invoice")} - {t("Submit an invoice for {{amountSats}} Sats",{amountSats: pn(this.props.data.invoice_amount)})} + {t("Submit payout info for {{amountSats}} Sats",{amountSats: pn(this.props.data.invoice_amount)})} {" " + this.stepXofY()} + + + + + + {t("The taker is committed! Before letting you send {{amountFiat}} {{currencyCode}}, we want to make sure you are able to receive the BTC.", + {amountFiat: parseFloat(parseFloat(this.props.data.amount).toFixed(4)), + currencyCode: this.props.data.currencyCode})} + + + + + + + Lightning} onClick={() => this.setState({receiveTab:0})}/> + Onchain} disabled={!this.props.data.swap_allowed} onClick={() => this.setState({receiveTab:1})} /> + + + + {/* LIGHTNING PAYOUT TAB */} +
+
+ + + + {t("Submit a valid invoice for {{amountSats}} Satoshis.", + {amountSats: pn(this.props.data.invoice_amount)})} + + + + + {this.compatibleWalletsButton()} + + + + + + {this.state.qrscanner ? + + + + : null } + + + + + +
- - - {t("The taker is committed! Before letting you send {{amountFiat}} {{currencyCode}}, we want to make sure you are able to receive the BTC. Please provide a valid invoice for {{amountSats}} Satoshis.", - {amountFiat: parseFloat(parseFloat(this.props.data.amount).toFixed(4)), - currencyCode: this.props.data.currencyCode, - amountSats: pn(this.props.data.invoice_amount)} - ) - } - - + {/* ONCHAIN PAYOUT TAB */} +
+
+ + + + {t("RoboSats will perform an on-the-fly reverse submarine swap and send to an onchain address for a fee. Submit onchain address. Preliminary {{amountSats}} Sats, swap allowed {{swapAllowed}}, swap fee {{swapFee}}%, mining fee suggested {{suggestedMiningFee}} Sats/vbyte", + {amountSats: this.props.data.invoice_amount, + swapAllowed: this.props.data.swap_allowed, + swapFee: this.props.data.swap_fee_rate , + suggestedMiningFee: this.props.data.suggested_mining_fee_rate} + ) + } + + + + + {t("Swap fee: {{swapFeeSats}}Sats ({{swapFeeRate}}%)", + {swapFeeSats: this.props.data.invoice_amount * this.state.swap_fee_rate/100, + swapFeeRate: this.state.swap_fee_rate}) + } + + + {t("Mining fee: {{miningFee}}Sats ({{swapFeeRate}}%)", + {miningFee: this.props.data.suggestedMiningFee * 141})} + + + {t("You receive: {{onchainAmount}}Sats)", + {onchainAmount: pn(parseInt(this.props.data.invoice_amount - (this.props.data.suggestedMiningFee * 141) - (this.props.data.invoice_amount * this.state.swap_fee_rate/100)))})} + + + + + - - {this.compatibleWalletsButton()} - + + + - - - - {this.state.qrscanner ? - - - - : null } - - - - + +
+ + + {this.showBondIsLocked()} diff --git a/frontend/src/components/UserGenPage.js b/frontend/src/components/UserGenPage.js index b810cdac..62bf0113 100644 --- a/frontend/src/components/UserGenPage.js +++ b/frontend/src/components/UserGenPage.js @@ -128,7 +128,7 @@ class UserGenPage extends Component { handleChangeToken=(e)=>{ this.setState({ - token: e.target.value, + token: e.target.value.split(' ').join(''), tokenHasChanged: true, }) }