mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-13 19:06:26 +00:00
Add frontend input address components
This commit is contained in:
parent
b1d68a39f7
commit
dc9d5e5e2a
@ -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"
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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():
|
||||
|
@ -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")}
|
||||
<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()}
|
||||
</Typography>
|
||||
</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">
|
||||
<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. 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)}
|
||||
)
|
||||
}
|
||||
</Typography>
|
||||
</Grid>
|
||||
{/* ONCHAIN PAYOUT TAB */}
|
||||
<div style={{display: this.state.receiveTab == 1 ? '':'none'}}>
|
||||
<div style={{height:15}}/>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} align="left">
|
||||
<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,
|
||||
swapAllowed: this.props.data.swap_allowed,
|
||||
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">
|
||||
{this.compatibleWalletsButton()}
|
||||
</Grid>
|
||||
<Grid item xs={12} align="center">
|
||||
<Button onClick={null} variant='contained' color='primary'>{t("Submit")}</Button>
|
||||
</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={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>
|
||||
</Grid>
|
||||
</div>
|
||||
</ListItem>
|
||||
<Divider/>
|
||||
</List>
|
||||
|
||||
{this.showBondIsLocked()}
|
||||
</Grid>
|
||||
|
@ -128,7 +128,7 @@ class UserGenPage extends Component {
|
||||
|
||||
handleChangeToken=(e)=>{
|
||||
this.setState({
|
||||
token: e.target.value,
|
||||
token: e.target.value.split(' ').join(''),
|
||||
tokenHasChanged: true,
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user