mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-14 11:26:24 +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'))
|
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"
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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():
|
||||||
|
@ -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>
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user