Add frontend input address components

This commit is contained in:
Reckless_Satoshi 2022-06-11 06:12:09 -07:00
parent b1d68a39f7
commit dc9d5e5e2a
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
6 changed files with 168 additions and 73 deletions

View File

@ -504,7 +504,7 @@ class Logics:
MIN_POINT = float(config('MIN_POINT')) MIN_POINT = float(config('MIN_POINT'))
MAX_SWAP_FEE = float(config('MAX_SWAP_FEE')) MAX_SWAP_FEE = float(config('MAX_SWAP_FEE'))
MAX_POINT = float(config('MAX_POINT')) 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 swap_fee_rate = MIN_SWAP_FEE
else: else:
slope = (MAX_SWAP_FEE - MIN_SWAP_FEE) / (MAX_POINT - MIN_POINT) 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')) MIN_SWAP_FEE = float(config('MIN_SWAP_FEE'))
MAX_SWAP_FEE = float(config('MAX_SWAP_FEE')) MAX_SWAP_FEE = float(config('MAX_SWAP_FEE'))
SWAP_LAMBDA = float(config('SWAP_LAMBDA')) 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 @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. Creates an empty OnchainPayment for order.payout_tx.
It sets the fees to be applied to this order if onchain Swap is used. 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. 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)) # Compute a safer available onchain liquidity: (confirmed_utxos - reserve - pending_outgoing_txs))
# Accounts for already committed outgoing TX for previous users. # Accounts for already committed outgoing TX for previous users.
confirmed = onchain_payment.balance.onchain_confirmed confirmed = onchain_payment.balance.onchain_confirmed
reserve = 0.01 * onchain_payment.balance.total # We assume a reserve of 1% 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'] 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 available_onchain = confirmed - reserve - pending_txs
if preliminary_amount > available_onchain: # Not enough onchain balance to commit for this swap. if preliminary_amount > available_onchain: # Not enough onchain balance to commit for this swap.
return False return False
onchain_payment.suggested_mining_fee_rate = LNNode.estimate_fee(amount_sats=preliminary_amount) 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.preliminary_amount)
# 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() onchain_payment.save()
order.payout_tx = onchain_payment order.payout_tx = onchain_payment
@ -577,7 +590,7 @@ class Logics:
if order.payout_tx == None: if order.payout_tx == None:
# Creates the OnchainPayment object and checks node balance # 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: if not valid:
context["swap_allowed"] = False context["swap_allowed"] = False
context["swap_failure_reason"] = "Not enough onchain liquidity available to offer swaps" context["swap_failure_reason"] = "Not enough onchain liquidity available to offer swaps"

View File

@ -188,7 +188,7 @@ class OnchainPayment(models.Model):
default=Concepts.PAYBUYER) default=Concepts.PAYBUYER)
status = models.PositiveSmallIntegerField(choices=Status.choices, status = models.PositiveSmallIntegerField(choices=Status.choices,
null=False, null=False,
default=Status.VALID) default=Status.CREAT)
# payment info # payment info
address = models.CharField(max_length=100, address = models.CharField(max_length=100,
@ -203,10 +203,11 @@ class OnchainPayment(models.Model):
default=None, default=None,
blank=True) blank=True)
num_satoshis = models.PositiveBigIntegerField(validators=[ num_satoshis = models.PositiveBigIntegerField(null=True,
MinValueValidator(0.7 * MIN_SWAP_AMOUNT), validators=[
MaxValueValidator(1.5 * MAX_TRADE), MinValueValidator(0.7 * MIN_SWAP_AMOUNT),
]) MaxValueValidator(1.5 * MAX_TRADE),
])
# fee in sats/vbyte with mSats decimals fee_msat # fee in sats/vbyte with mSats decimals fee_msat
suggested_mining_fee_rate = models.DecimalField(max_digits=6, 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, swap_fee_rate = models.DecimalField(max_digits=4,
decimal_places=2, decimal_places=2,
default=2, default=float(config("MIN_SWAP_FEE"))*100,
null=False, null=False,
blank=False) blank=False)
@ -454,6 +455,8 @@ class Order(models.Model):
default=None, default=None,
blank=True, 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 # buyer payment LN invoice
payout = models.OneToOneField( payout = models.OneToOneField(
LNPayment, LNPayment,
@ -463,15 +466,15 @@ class Order(models.Model):
default=None, default=None,
blank=True, blank=True,
) )
# buyer payment address
# payout_tx = models.OneToOneField( payout_tx = models.OneToOneField(
# OnchainPayment, OnchainPayment,
# related_name="order_paid_TX", related_name="order_paid_TX",
# on_delete=models.SET_NULL, on_delete=models.SET_NULL,
# null=True, null=True,
# default=None, default=None,
# blank=True, blank=True,
# ) )
# ratings # ratings
maker_rated = models.BooleanField(default=False, null=False) maker_rated = models.BooleanField(default=False, null=False)

View File

@ -337,7 +337,7 @@ class OrderView(viewsets.ViewSet):
elif data["is_buyer"] and (order.status == Order.Status.WF2 elif data["is_buyer"] and (order.status == Order.Status.WF2
or order.status == Order.Status.WFI): 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 == if (order.maker_bond.status == order.taker_bond.status ==
LNPayment.Status.LOCKED): LNPayment.Status.LOCKED):
valid, context = Logics.payout_amount(order, request.user) valid, context = Logics.payout_amount(order, request.user)

View File

@ -78,7 +78,7 @@ class BalanceLog(models.Model):
def get_total(): def get_total():
return LNNode.wallet_balance()['total_balance'] + LNNode.channel_balance()['local_balance'] return LNNode.wallet_balance()['total_balance'] + LNNode.channel_balance()['local_balance']
def get_frac(): 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(): def get_oc_total():
return LNNode.wallet_balance()['total_balance'] return LNNode.wallet_balance()['total_balance']
def get_oc_conf(): def get_oc_conf():

View File

@ -1,6 +1,6 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { withTranslation, Trans} from "react-i18next"; 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 QRCode from "react-qr-code";
import Countdown, { zeroPad} from 'react-countdown'; import Countdown, { zeroPad} from 'react-countdown';
import Chat from "./EncryptedChat" import Chat from "./EncryptedChat"
@ -18,6 +18,8 @@ import BalanceIcon from '@mui/icons-material/Balance';
import ContentCopy from "@mui/icons-material/ContentCopy"; import ContentCopy from "@mui/icons-material/ContentCopy";
import PauseCircleIcon from '@mui/icons-material/PauseCircle'; import PauseCircleIcon from '@mui/icons-material/PauseCircle';
import PlayCircleIcon from '@mui/icons-material/PlayCircle'; 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 AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet';
import { NewTabIcon } from "./Icons"; import { NewTabIcon } from "./Icons";
@ -33,6 +35,7 @@ class TradeBox extends Component {
openConfirmFiatReceived: false, openConfirmFiatReceived: false,
openConfirmDispute: false, openConfirmDispute: false,
openEnableTelegram: false, openEnableTelegram: false,
receiveTab: 0,
badInvoice: false, badInvoice: false,
badStatement: false, badStatement: false,
qrscanner: false, qrscanner: false,
@ -599,57 +602,133 @@ class TradeBox extends Component {
{/* Make confirmation sound for HTLC received. */} {/* Make confirmation sound for HTLC received. */}
{this.Sound("locked-invoice")} {this.Sound("locked-invoice")}
<Typography color="primary" variant="subtitle1"> <Typography color="primary" variant="subtitle1">
<b> {t("Submit an invoice for {{amountSats}} Sats",{amountSats: pn(this.props.data.invoice_amount)})} <b> {t("Submit payout info for {{amountSats}} Sats",{amountSats: pn(this.props.data.invoice_amount)})}
</b> {" " + this.stepXofY()} </b> {" " + this.stepXofY()}
</Typography> </Typography>
</Grid> </Grid>
<List dense={true}>
<Divider/>
<ListItem>
<Typography variant="body2">
{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})}
</Typography>
</ListItem>
<ListItem>
<Paper elevation={2}>
<Tabs value={this.state.receiveTab} variant="fullWidth">
<Tab disableRipple={true} label={<div style={{display:'flex', alignItems:'center', justifyContent:'center', flexWrap:'wrap'}}><BoltIcon/> Lightning</div>} onClick={() => this.setState({receiveTab:0})}/>
<Tab label={<div style={{display:'flex', alignItems:'center', justifyContent:'center', flexWrap:'wrap'}}><LinkIcon/> Onchain</div>} disabled={!this.props.data.swap_allowed} onClick={() => this.setState({receiveTab:1})} />
</Tabs>
</Paper>
{/* LIGHTNING PAYOUT TAB */}
<div style={{display: this.state.receiveTab == 0 ? '':'none'}}>
<div style={{height:15}}/>
<Grid container spacing={1}>
<Grid item xs={12} align="center">
<Typography variant="body2">
{t("Submit a valid invoice for {{amountSats}} Satoshis.",
{amountSats: pn(this.props.data.invoice_amount)})}
</Typography>
</Grid>
<Grid item xs={12} align="center">
{this.compatibleWalletsButton()}
</Grid>
<Grid item xs={12} align="center">
<TextField
error={this.state.badInvoice}
helperText={this.state.badInvoice ? t(this.state.badInvoice) : "" }
label={t("Payout Lightning Invoice")}
required
value={this.state.invoice}
inputProps={{
style: {textAlign:"center"},
maxHeight: 200,
}}
multiline
minRows={4}
maxRows={this.state.qrscanner ? 4 : 8}
onChange={this.handleInputInvoiceChanged}
/>
</Grid>
{this.state.qrscanner ?
<Grid item xs={12} align="center">
<QrReader
delay={300}
onError={this.handleError}
onScan={this.handleScan}
style={{ width: '75%' }}
/>
</Grid>
: null }
<Grid item xs={12} align="center">
<IconButton><QrCodeScannerIcon onClick={this.handleQRbutton}/></IconButton>
<Button onClick={this.handleClickSubmitInvoiceButton} variant='contained' color='primary'>{t("Submit")}</Button>
</Grid>
</Grid>
</div>
<Grid item xs={12} align="left"> {/* ONCHAIN PAYOUT TAB */}
<Typography variant="body2"> <div style={{display: this.state.receiveTab == 1 ? '':'none'}}>
{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.", <div style={{height:15}}/>
{amountFiat: parseFloat(parseFloat(this.props.data.amount).toFixed(4)), <Grid container spacing={1}>
currencyCode: this.props.data.currencyCode, <Grid item xs={12} align="left">
amountSats: pn(this.props.data.invoice_amount)} <Typography variant="body2">
) {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,
</Typography> swapAllowed: this.props.data.swap_allowed,
</Grid> swapFee: this.props.data.swap_fee_rate ,
suggestedMiningFee: this.props.data.suggested_mining_fee_rate}
)
}
</Typography>
</Grid>
<Grid item xs={12} align="left">
<Typography variant="body2">
{t("Swap fee: {{swapFeeSats}}Sats ({{swapFeeRate}}%)",
{swapFeeSats: this.props.data.invoice_amount * this.state.swap_fee_rate/100,
swapFeeRate: this.state.swap_fee_rate})
}
</Typography>
<Typography variant="body2">
{t("Mining fee: {{miningFee}}Sats ({{swapFeeRate}}%)",
{miningFee: this.props.data.suggestedMiningFee * 141})}
</Typography>
<Typography variant="body2">
{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)))})}
</Typography>
</Grid>
<Grid item xs={12} align="center">
<TextField
error={this.state.badAddress}
helperText={this.state.badAddress ? t(this.state.badAddress) : "" }
label={t("Payout Lightning Invoice")}
required
value={this.state.invoice}
inputProps={{
style: {textAlign:"center"},
maxHeight: 240,
}}
onChange={null}
/>
</Grid>
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
{this.compatibleWalletsButton()} <Button onClick={null} variant='contained' color='primary'>{t("Submit")}</Button>
</Grid> </Grid>
<Grid item xs={12} align="center"> </Grid>
<TextField </div>
error={this.state.badInvoice} </ListItem>
helperText={this.state.badInvoice ? t(this.state.badInvoice) : "" } <Divider/>
label={t("Payout Lightning Invoice")} </List>
required
value={this.state.invoice}
inputProps={{
style: {textAlign:"center"},
maxHeight: 200,
}}
multiline
minRows={5}
maxRows={this.state.qrscanner ? 5 : 10}
onChange={this.handleInputInvoiceChanged}
/>
</Grid>
{this.state.qrscanner ?
<Grid item xs={12} align="center">
<QrReader
delay={300}
onError={this.handleError}
onScan={this.handleScan}
style={{ width: '75%' }}
/>
</Grid>
: null }
<Grid item xs={12} align="center">
<IconButton><QrCodeScannerIcon onClick={this.handleQRbutton}/></IconButton>
<Button onClick={this.handleClickSubmitInvoiceButton} variant='contained' color='primary'>{t("Submit")}</Button>
</Grid>
{this.showBondIsLocked()} {this.showBondIsLocked()}
</Grid> </Grid>

View File

@ -128,7 +128,7 @@ class UserGenPage extends Component {
handleChangeToken=(e)=>{ handleChangeToken=(e)=>{
this.setState({ this.setState({
token: e.target.value, token: e.target.value.split(' ').join(''),
tokenHasChanged: true, tokenHasChanged: true,
}) })
} }