fix: withdraw/deposit converts from BZZ (10^16 base units) (#66)
* refactor: added toBZZbaseUnit function * feat: added utility for attesting value is BZZ convertible to base units * fix: conversion from 15 to 16 decimal places, added unsafe versions * refactor: withdraw modal uses the safe conversion from BZZ * refactor: added BigNumber and Token class to handle BZZ digits correctly * refactor: extract deposit and withdraw functionality into single modal * test: added tests for Token * chore: removed unused component * chore: addressed PR review, token decimal is now integer 0-18 * chore: added comment to clarify the value restriction on token amount
This commit is contained in:
@@ -1,80 +0,0 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import Input from '@material-ui/core/Input'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import { Snackbar } from '@material-ui/core'
|
||||
|
||||
import { beeDebugApi } from '../services/bee'
|
||||
|
||||
export default function DepositModal(): ReactElement {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [amount, setAmount] = React.useState(BigInt(0))
|
||||
const [showToast, setToastVisibility] = React.useState(false)
|
||||
const [toastContent, setToastContent] = React.useState('')
|
||||
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const handleWithdraw = () => {
|
||||
if (amount > 0) {
|
||||
beeDebugApi.chequebook
|
||||
.deposit(amount)
|
||||
.then(res => {
|
||||
setOpen(false)
|
||||
handleToast(`Successful Deposit. Transaction ${res.transactionHash}`)
|
||||
})
|
||||
.catch(() => {
|
||||
handleToast('Error with Deposit')
|
||||
})
|
||||
} else {
|
||||
handleToast('Must be amount of greater than 0')
|
||||
}
|
||||
}
|
||||
|
||||
const handleToast = (text: string) => {
|
||||
setToastContent(text)
|
||||
setToastVisibility(true)
|
||||
setTimeout(() => setToastVisibility(false), 7000)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button variant="outlined" color="primary" onClick={handleClickOpen} style={{ marginLeft: '7px' }}>
|
||||
Deposit
|
||||
</Button>
|
||||
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={showToast} message={toastContent} />
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Deposit Funds</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>Specify the amount you would like to deposit to your node.</DialogContentText>
|
||||
<Input
|
||||
autoFocus
|
||||
margin="dense"
|
||||
id="name"
|
||||
type="number"
|
||||
placeholder="Amount"
|
||||
fullWidth
|
||||
onChange={e => setAmount(BigInt(e.target.value))}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleWithdraw} color="primary">
|
||||
Deposit
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
||||
import { Paper, InputBase, IconButton } from '@material-ui/core'
|
||||
import { Search } from '@material-ui/icons'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
padding: '2px 4px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: 400,
|
||||
},
|
||||
input: {
|
||||
marginLeft: theme.spacing(1),
|
||||
flex: 1,
|
||||
},
|
||||
iconButton: {
|
||||
padding: 10,
|
||||
},
|
||||
divider: {
|
||||
height: 28,
|
||||
margin: 4,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
export default function SearchBar() {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Paper component="form" className={classes.root}>
|
||||
<InputBase
|
||||
className={classes.input}
|
||||
placeholder="Enter hash e.g. 0773a91efd6547c754fc1d95fb1c62c7d1b47f959c2caa685dfec8736da95c1c"
|
||||
inputProps={{ 'aria-label': 'search google maps' }}
|
||||
/>
|
||||
<IconButton type="submit" className={classes.iconButton} aria-label="search">
|
||||
<Search />
|
||||
</IconButton>
|
||||
</Paper>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import { ReactElement, useState } from 'react'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import Input from '@material-ui/core/Input'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import { FormHelperText, Snackbar } from '@material-ui/core'
|
||||
import { Token } from '../models/Token'
|
||||
import type { BigNumber } from 'bignumber.js'
|
||||
|
||||
interface Props {
|
||||
successMessage: string
|
||||
errorMessage: string
|
||||
dialogMessage: string
|
||||
label: string
|
||||
max?: BigNumber
|
||||
min?: BigNumber
|
||||
action: (amount: bigint) => Promise<{ transactionHash: string }>
|
||||
}
|
||||
|
||||
export default function WithdrawModal({
|
||||
successMessage,
|
||||
errorMessage,
|
||||
dialogMessage,
|
||||
min,
|
||||
max,
|
||||
label,
|
||||
action,
|
||||
}: Props): ReactElement {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [amount, setAmount] = useState('')
|
||||
const [amountToken, setAmountToken] = useState<Token | null>(null)
|
||||
const [amountError, setAmountError] = useState<Error | null>(null)
|
||||
const [showToast, setToastVisibility] = useState(false)
|
||||
const [toastContent, setToastContent] = useState('')
|
||||
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const handleAction = async () => {
|
||||
if (amountToken === null) return
|
||||
|
||||
try {
|
||||
const { transactionHash } = await action(amountToken.toBigInt)
|
||||
setOpen(false)
|
||||
handleToast(`${successMessage} Transaction ${transactionHash}`)
|
||||
} catch (e) {
|
||||
handleToast(`${errorMessage} Error: ${e.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
const handleToast = (text: string) => {
|
||||
setToastContent(text)
|
||||
setToastVisibility(true)
|
||||
setTimeout(() => setToastVisibility(false), 7000)
|
||||
}
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
||||
const value = e.target.value
|
||||
setAmount(value)
|
||||
setAmountError(null)
|
||||
try {
|
||||
const t = Token.fromDecimal(value)
|
||||
setAmountToken(t)
|
||||
|
||||
if (min && t.toDecimal.isLessThan(min)) setAmountError(new Error(`Needs to be more than ${min}`))
|
||||
|
||||
if (max && t.toDecimal.isGreaterThan(max)) setAmountError(new Error(`Needs to be less than ${max}`))
|
||||
} catch (e) {
|
||||
setAmountError(e)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
|
||||
{label}
|
||||
</Button>
|
||||
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={showToast} message={toastContent} />
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">{label}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>{dialogMessage}</DialogContentText>
|
||||
<Input
|
||||
autoFocus
|
||||
margin="dense"
|
||||
id="name"
|
||||
type="text"
|
||||
placeholder="Amount"
|
||||
fullWidth
|
||||
value={amount}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{amountError && (
|
||||
<FormHelperText error>
|
||||
Please provide valid BZZ amount (max 16 decimals). Error: {amountError.message}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleAction} color="primary">
|
||||
{label}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import { ReactElement, useState } from 'react'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import Input from '@material-ui/core/Input'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import { Snackbar } from '@material-ui/core'
|
||||
|
||||
import { beeDebugApi } from '../services/bee'
|
||||
|
||||
export default function WithdrawlModal(): ReactElement {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [amount, setAmount] = useState(BigInt(0))
|
||||
const [showToast, setToastVisibility] = useState(false)
|
||||
const [toastContent, setToastContent] = useState('')
|
||||
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const handleWithdraw = () => {
|
||||
if (amount > 0) {
|
||||
beeDebugApi.chequebook
|
||||
.withdraw(amount)
|
||||
.then(res => {
|
||||
setOpen(false)
|
||||
handleToast(`Successful withdrawl. Transaction ${res.transactionHash}`)
|
||||
})
|
||||
.catch(() => {
|
||||
// FIXME: should probably detail the error
|
||||
handleToast('Error with withdrawing')
|
||||
})
|
||||
} else {
|
||||
handleToast('Must be amount of greater than 0')
|
||||
}
|
||||
}
|
||||
|
||||
const handleToast = (text: string) => {
|
||||
setToastContent(text)
|
||||
setToastVisibility(true)
|
||||
setTimeout(() => setToastVisibility(false), 7000)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
|
||||
Withdraw
|
||||
</Button>
|
||||
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={showToast} message={toastContent} />
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Withdraw Funds</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>Specify the amount you would like to withdraw from your node.</DialogContentText>
|
||||
<Input
|
||||
autoFocus
|
||||
margin="dense"
|
||||
id="name"
|
||||
type="number"
|
||||
placeholder="Amount"
|
||||
fullWidth
|
||||
onChange={e => setAmount(BigInt(e.target.value))}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleWithdraw} color="primary">
|
||||
Withdraw
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { ReactElement } from 'react'
|
||||
import { beeDebugApi } from '../services/bee'
|
||||
|
||||
import WDModal from '../components/WDModal'
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
|
||||
export default function DepositModal(): ReactElement {
|
||||
return (
|
||||
<WDModal
|
||||
successMessage="Successful deposit."
|
||||
errorMessage="Error with depositing"
|
||||
dialogMessage="Specify the amount of BZZ you would like to withdraw from your node."
|
||||
label="Deposit"
|
||||
min={new BigNumber(0)}
|
||||
action={beeDebugApi.chequebook.deposit}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { ReactElement } from 'react'
|
||||
import { beeDebugApi } from '../services/bee'
|
||||
|
||||
import WDModal from '../components/WDModal'
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
|
||||
export default function WithdrawModal(): ReactElement {
|
||||
return (
|
||||
<WDModal
|
||||
successMessage="Successful withdrawl."
|
||||
errorMessage="Error with withdrawing."
|
||||
dialogMessage="Specify the amount of BZZ you would like to withdraw from your node."
|
||||
label="Withdraw"
|
||||
min={new BigNumber(0)}
|
||||
action={beeDebugApi.chequebook.withdraw}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { Token } from './Token'
|
||||
|
||||
describe('models/Token', () => {
|
||||
describe('Token.fromDecimal', () => {
|
||||
const values = [
|
||||
{ bzz: '0', baseUnits: '0' },
|
||||
{ bzz: '0.1', baseUnits: BigInt('1000000000000000') },
|
||||
{ bzz: '9.9', baseUnits: BigInt('99000000000000000') },
|
||||
]
|
||||
|
||||
// Test with default 16 decimal places
|
||||
values.forEach(({ bzz, baseUnits }) => {
|
||||
test(`converting ${bzz} => ${baseUnits}`, () => {
|
||||
expect(Token.fromDecimal(bzz).toBigNumber.eq(baseUnits.toString())).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
// Test with 0 decimal places
|
||||
values.forEach(({ baseUnits }) => {
|
||||
test(`converting ${baseUnits} => ${baseUnits} with 0 decimals`, () => {
|
||||
expect(Token.fromDecimal(baseUnits, 0).toBigNumber.eq(baseUnits.toString())).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('new Token', () => {
|
||||
const cs = ['0', '1234567890', '99000000000000000']
|
||||
const correctValues = [...cs, ...cs.map(BigInt), ...cs.map(v => new BigNumber(v))]
|
||||
|
||||
correctValues.forEach(v => {
|
||||
test(`New Token ${v} of type ${typeof v}`, () => {
|
||||
const t = new Token(v)
|
||||
|
||||
expect(t.toBigNumber.eq(v.toString())).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,56 @@
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
import { isInteger, makeBigNumber } from '../utils'
|
||||
|
||||
const POSSIBLE_DECIMALS = [18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
|
||||
type digits = typeof POSSIBLE_DECIMALS[number]
|
||||
|
||||
const BZZ_DECIMALS = 16
|
||||
|
||||
export class Token {
|
||||
private amount: BigNumber // Represented in the base units, so it is always an integer value
|
||||
private readonly decimals: digits
|
||||
|
||||
constructor(amount: BigNumber | string | bigint, decimals: digits = BZZ_DECIMALS) {
|
||||
const a = makeBigNumber(amount)
|
||||
|
||||
if (!isInteger(a) || !POSSIBLE_DECIMALS.includes(decimals)) throw new TypeError('Not a valid token values')
|
||||
|
||||
this.amount = a
|
||||
this.decimals = decimals
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct new Token from a digit representation
|
||||
*
|
||||
* @param amount Amount of a token in the digits (1 token = 10^decimals)
|
||||
* @param decimals Number of decimals for the token (must be integer)
|
||||
*
|
||||
* @throws {TypeError} If the decimals is not an integer or the amount after conversion is not an integer
|
||||
*
|
||||
* @returns new Token
|
||||
*/
|
||||
static fromDecimal(amount: BigNumber | string | bigint, decimals: digits = BZZ_DECIMALS): Token | never {
|
||||
const a = makeBigNumber(amount)
|
||||
|
||||
// No need to do any validation here, it is done when the new token is created
|
||||
const t = a.multipliedBy(new BigNumber(10).pow(decimals))
|
||||
|
||||
return new Token(t, decimals)
|
||||
}
|
||||
|
||||
get toBigInt(): bigint {
|
||||
return BigInt(this.amount.toFixed(0))
|
||||
}
|
||||
|
||||
get toString(): string {
|
||||
return this.amount.toFixed(0)
|
||||
}
|
||||
|
||||
get toBigNumber(): BigNumber {
|
||||
return new BigNumber(this.amount)
|
||||
}
|
||||
|
||||
get toDecimal(): BigNumber {
|
||||
return this.amount.dividedBy(new BigNumber(10).pow(this.decimals))
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,11 @@ import { ReactElement } from 'react'
|
||||
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
||||
import { Card, CardContent, Typography, Grid } from '@material-ui/core/'
|
||||
import { Skeleton } from '@material-ui/lab'
|
||||
import WithdrawlModal from '../../components/WithdrawlModal'
|
||||
import DepositModal from '../../components/DepositModal'
|
||||
import WithdrawModal from '../../containers/WithdrawModal'
|
||||
import DepositModal from '../../containers/DepositModal'
|
||||
import CashoutModal from '../../components/CashoutModal'
|
||||
|
||||
import { ConvertBalanceToBZZ } from '../../utils/common'
|
||||
import { fromBZZbaseUnit } from '../../utils'
|
||||
|
||||
import type { AllSettlements, ChequebookAddressResponse } from '@ethersphere/bee-js'
|
||||
|
||||
@@ -55,7 +55,7 @@ function AccountCard(props: Props): ReactElement {
|
||||
<div style={{ justifyContent: 'space-between', display: 'flex' }}>
|
||||
<h2 style={{ marginTop: '0px' }}>Accounting</h2>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<WithdrawlModal />
|
||||
<WithdrawModal />
|
||||
<DepositModal />
|
||||
<CashoutModal />
|
||||
</div>
|
||||
@@ -68,28 +68,28 @@ function AccountCard(props: Props): ReactElement {
|
||||
<Grid container spacing={5}>
|
||||
<Grid item>
|
||||
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
||||
Total Balance
|
||||
Total Balance (BZZ)
|
||||
</Typography>
|
||||
<Typography component="p" variant="h5">
|
||||
{ConvertBalanceToBZZ(props.chequebookBalance.totalBalance)}
|
||||
{fromBZZbaseUnit(props.chequebookBalance.totalBalance)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
||||
Available Balance
|
||||
Available Balance (BZZ)
|
||||
</Typography>
|
||||
<Typography component="p" variant="h5">
|
||||
{ConvertBalanceToBZZ(props.chequebookBalance.availableBalance)}
|
||||
{fromBZZbaseUnit(props.chequebookBalance.availableBalance)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
||||
Total Sent / Received
|
||||
Total Sent / Received (BZZ)
|
||||
</Typography>
|
||||
<Typography component="div" variant="h5">
|
||||
<span style={{ marginRight: '7px' }}>
|
||||
{ConvertBalanceToBZZ(props.settlements?.totalsent || 0)} /{' '}
|
||||
{ConvertBalanceToBZZ(props.settlements?.totalreceived || 0)}
|
||||
{fromBZZbaseUnit(props.settlements?.totalsent || 0)} /{' '}
|
||||
{fromBZZbaseUnit(props.settlements?.totalreceived || 0)}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
@@ -100,7 +100,7 @@ function AccountCard(props: Props): ReactElement {
|
||||
}}
|
||||
>
|
||||
(
|
||||
{ConvertBalanceToBZZ(
|
||||
{fromBZZbaseUnit(
|
||||
(props.settlements && props.settlements?.totalsent - props.settlements?.totalreceived) || 0,
|
||||
)}
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
CircularProgress,
|
||||
} from '@material-ui/core'
|
||||
|
||||
import { ConvertBalanceToBZZ } from '../../utils/common'
|
||||
import { fromBZZbaseUnit } from '../../utils'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
table: {
|
||||
@@ -59,12 +59,12 @@ function BalancesTable(props: Props): ReactElement {
|
||||
<TableCell>{peerBalance.peer}</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
color: ConvertBalanceToBZZ(peerBalance.balance) > 0 ? '#32c48d' : '#c9201f',
|
||||
color: fromBZZbaseUnit(peerBalance.balance) > 0 ? '#32c48d' : '#c9201f',
|
||||
textAlign: 'right',
|
||||
fontFamily: 'monospace, monospace',
|
||||
}}
|
||||
>
|
||||
{ConvertBalanceToBZZ(peerBalance.balance).toFixed(7).toLocaleString()}
|
||||
{fromBZZbaseUnit(peerBalance.balance).toFixed(7).toLocaleString()}
|
||||
</TableCell>
|
||||
<TableCell align="right"></TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import type { ReactElement } from 'react'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import {
|
||||
Table,
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
CircularProgress,
|
||||
} from '@material-ui/core'
|
||||
|
||||
import { ConvertBalanceToBZZ } from '../../utils/common'
|
||||
import { fromBZZbaseUnit } from '../../utils'
|
||||
import EthereumAddress from '../../components/EthereumAddress'
|
||||
import ClipboardCopy from '../../components/ClipboardCopy'
|
||||
import PeerDetailDrawer from './PeerDetailDrawer'
|
||||
@@ -80,7 +80,7 @@ function ChequebookTable(props: Props): ReactElement {
|
||||
<p style={{ marginBottom: '0px', fontFamily: 'monospace, monospace', display: 'flex' }}>
|
||||
<span style={{ whiteSpace: 'nowrap', marginRight: '12px', paddingTop: '3px' }}>
|
||||
{peerCheque.lastreceived?.payout
|
||||
? `${ConvertBalanceToBZZ(peerCheque.lastreceived?.payout).toFixed(7).toLocaleString()} from`
|
||||
? `${fromBZZbaseUnit(peerCheque.lastreceived?.payout).toFixed(7).toLocaleString()} from`
|
||||
: '-'}
|
||||
</span>
|
||||
{peerCheque.lastreceived ? (
|
||||
@@ -97,7 +97,7 @@ function ChequebookTable(props: Props): ReactElement {
|
||||
<p style={{ marginBottom: '0px', fontFamily: 'monospace, monospace', display: 'flex' }}>
|
||||
<span style={{ whiteSpace: 'nowrap', marginRight: '12px', paddingTop: '3px' }}>
|
||||
{peerCheque.lastsent?.payout
|
||||
? `${ConvertBalanceToBZZ(peerCheque.lastsent?.payout).toFixed(7).toLocaleString()} to`
|
||||
? `${fromBZZbaseUnit(peerCheque.lastsent?.payout).toFixed(7).toLocaleString()} to`
|
||||
: '-'}
|
||||
</span>
|
||||
{peerCheque.lastsent ? (
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Paper, Container, Drawer, Button, Typography, CircularProgress, Grid }
|
||||
import ClipboardCopy from '../../components/ClipboardCopy'
|
||||
import { beeDebugApi } from '../../services/bee'
|
||||
import EthereumAddress from '../../components/EthereumAddress'
|
||||
import { ConvertBalanceToBZZ } from '../../utils/common'
|
||||
import { fromBZZbaseUnit } from '../../utils'
|
||||
import { LastCashoutActionResponse, LastChequesForPeerResponse } from '@ethersphere/bee-js'
|
||||
|
||||
function truncStringPortion(str: string, firstCharCount = 10, endCharCount = 10) {
|
||||
@@ -87,7 +87,7 @@ export default function Index(props: Props): ReactElement {
|
||||
Payout:
|
||||
<span style={{ marginBottom: '0px', fontFamily: 'monospace, monospace' }}>
|
||||
{' '}
|
||||
{peerCheque?.lastsent?.payout ? ConvertBalanceToBZZ(peerCheque?.lastsent?.payout) : '-'}
|
||||
{peerCheque?.lastsent?.payout ? fromBZZbaseUnit(peerCheque?.lastsent?.payout) : '-'}
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
@@ -105,7 +105,7 @@ export default function Index(props: Props): ReactElement {
|
||||
Payout:
|
||||
<span style={{ marginBottom: '0px', fontFamily: 'monospace, monospace' }}>
|
||||
{' '}
|
||||
{peerCheque?.lastreceived?.payout ? ConvertBalanceToBZZ(peerCheque?.lastreceived?.payout) : '-'}
|
||||
{peerCheque?.lastreceived?.payout ? fromBZZbaseUnit(peerCheque?.lastreceived?.payout) : '-'}
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
@@ -124,14 +124,14 @@ export default function Index(props: Props): ReactElement {
|
||||
<p>
|
||||
Cumulative Payout:
|
||||
<span style={{ marginBottom: '0px', fontFamily: 'monospace, monospace' }}>
|
||||
{peerCashout?.cumulativePayout ? ConvertBalanceToBZZ(peerCashout?.cumulativePayout) : '-'}
|
||||
{peerCashout?.cumulativePayout ? fromBZZbaseUnit(peerCashout?.cumulativePayout) : '-'}
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
Last Payout:
|
||||
<span style={{ marginBottom: '0px', fontFamily: 'monospace, monospace' }}>
|
||||
{' '}
|
||||
{peerCashout?.result.lastPayout ? ConvertBalanceToBZZ(peerCashout?.result.lastPayout) : '-'}
|
||||
{peerCashout?.result.lastPayout ? fromBZZbaseUnit(peerCashout?.result.lastPayout) : '-'}
|
||||
</span>
|
||||
<span> {peerCashout?.result.bounced ? 'Bounced' : ''}</span>
|
||||
</p>
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
CircularProgress,
|
||||
} from '@material-ui/core'
|
||||
|
||||
import { ConvertBalanceToBZZ } from '../../utils/common'
|
||||
import { fromBZZbaseUnit } from '../../utils'
|
||||
|
||||
import type { AllSettlements, Settlements } from '@ethersphere/bee-js'
|
||||
|
||||
@@ -51,10 +51,10 @@ function SettlementsTable(props: Props): ReactElement {
|
||||
<TableRow key={item.peer}>
|
||||
<TableCell>{item.peer}</TableCell>
|
||||
<TableCell style={{ fontFamily: 'monospace, monospace' }}>
|
||||
{item.received > 0 ? ConvertBalanceToBZZ(item.received).toFixed(7).toLocaleString() : item.received}
|
||||
{item.received > 0 ? fromBZZbaseUnit(item.received).toFixed(7).toLocaleString() : item.received}
|
||||
</TableCell>
|
||||
<TableCell style={{ fontFamily: 'monospace, monospace' }}>
|
||||
{item.sent > 0 ? ConvertBalanceToBZZ(item.sent).toFixed(7).toLocaleString() : item.sent}
|
||||
{item.sent > 0 ? fromBZZbaseUnit(item.sent).toFixed(7).toLocaleString() : item.sent}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Typography } from '@material-ui/core/'
|
||||
import EthereumAddress from '../../../components/EthereumAddress'
|
||||
import DepositModal from '../../../components/DepositModal'
|
||||
import DepositModal from '../../../containers/DepositModal'
|
||||
import CodeBlockTabs from '../../../components/CodeBlockTabs'
|
||||
import type { ReactElement } from 'react'
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export const ConvertBalanceToBZZ = (amount: number): number => {
|
||||
return amount / 10 ** 16
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { fromBZZbaseUnit, isInteger, makeBigNumber } from './index'
|
||||
|
||||
describe('utils', () => {
|
||||
describe('fromBZZbaseUnit', () => {
|
||||
const values = [
|
||||
{ bzz: 0, baseUnits: 0 },
|
||||
{ bzz: 0.1, baseUnits: 1e15 },
|
||||
{ bzz: 0.9, baseUnits: 9e15 },
|
||||
]
|
||||
|
||||
values.forEach(({ bzz, baseUnits }) => {
|
||||
test(`converting ${bzz} => ${baseUnits}`, () => {
|
||||
expect(fromBZZbaseUnit(baseUnits)).toBe(bzz)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('isInteger', () => {
|
||||
const correctValues = [
|
||||
BigInt(0),
|
||||
BigInt(1),
|
||||
BigInt(-1),
|
||||
new BigNumber('1'),
|
||||
new BigNumber('0'),
|
||||
new BigNumber('-1'),
|
||||
]
|
||||
const wrongValues = ['1', new BigNumber('-0.1'), new BigNumber(NaN), new BigNumber(Infinity)]
|
||||
|
||||
correctValues.forEach(v => {
|
||||
test(`testing ${v}`, () => {
|
||||
expect(isInteger(v)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
wrongValues.forEach(v => {
|
||||
test(`testing ${v}`, () => {
|
||||
expect(isInteger(v)).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('makeBigNumber', () => {
|
||||
const correctValues = [
|
||||
BigInt(0),
|
||||
BigInt(1),
|
||||
BigInt(-1),
|
||||
'1',
|
||||
'0',
|
||||
'-1',
|
||||
'0.1',
|
||||
new BigNumber('1'),
|
||||
new BigNumber('0'),
|
||||
new BigNumber('-1'),
|
||||
]
|
||||
const wrongValues = [new Function(), 0, 1]
|
||||
|
||||
correctValues.forEach(v => {
|
||||
test(`testing ${v}`, () => {
|
||||
expect(BigNumber.isBigNumber(makeBigNumber(v))).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
wrongValues.forEach(v => {
|
||||
test(`testing ${v}`, () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
expect(() => makeBigNumber((v as unknown) as any)).toThrow()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,48 @@
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
|
||||
/**
|
||||
* @deprecated Should be removed in favour of Token class
|
||||
*/
|
||||
const BZZ_BASE_UNIT = 1e16
|
||||
|
||||
/**
|
||||
* Convert from base units of BZZ token to BZZ
|
||||
* @deprecated This should only be used for displaying values, it's unsafe and should be replaced with Token class
|
||||
*
|
||||
* @param amount Amount in base units of BZZ token
|
||||
*
|
||||
* @returns amount in BZZ
|
||||
*/
|
||||
export const fromBZZbaseUnit = (amount: number): number => {
|
||||
return amount / BZZ_BASE_UNIT
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if value is an integer
|
||||
*
|
||||
* @param value Value to be tested if it is an integer
|
||||
*
|
||||
* @returns True if the passed in value is integer
|
||||
*/
|
||||
export function isInteger(value: unknown): value is BigNumber | bigint {
|
||||
return (BigNumber.isBigNumber(value) && value.isInteger()) || typeof value === 'bigint'
|
||||
}
|
||||
|
||||
/**
|
||||
*Convert value into a BigNumber if not already
|
||||
*
|
||||
* @param value Value to be converted
|
||||
*
|
||||
* @throws {TypeError} if the value is not convertible to a BigNumber
|
||||
*
|
||||
* @returns BigNumber - but it may still be NaN or Infinite
|
||||
*/
|
||||
export function makeBigNumber(value: BigNumber | bigint | string): BigNumber | never {
|
||||
if (BigNumber.isBigNumber(value)) return value
|
||||
|
||||
if (typeof value === 'string') return new BigNumber(value)
|
||||
|
||||
if (typeof value === 'bigint') return new BigNumber(value.toString())
|
||||
|
||||
throw new TypeError('Not a BigNumber or BigNumber convertible value')
|
||||
}
|
||||
Reference in New Issue
Block a user