diff --git a/package-lock.json b/package-lock.json index 0c0e53c..e3c43ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@types/react-router": "^5.1.13", "@types/react-router-dom": "^5.1.7", "axios": "^0.21.1", + "bignumber.js": "^9.0.1", "feather-icons": "^4.28.0", "material-ui-dropzone": "^3.5.0", "qrcode.react": "^1.0.1", @@ -4252,6 +4253,14 @@ "node": "*" } }, + "node_modules/bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -25127,6 +25136,11 @@ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true }, + "bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==" + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", diff --git a/package.json b/package.json index 8c2c70c..191659f 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@types/react-router": "^5.1.13", "@types/react-router-dom": "^5.1.7", "axios": "^0.21.1", + "bignumber.js": "^9.0.1", "feather-icons": "^4.28.0", "material-ui-dropzone": "^3.5.0", "qrcode.react": "^1.0.1", diff --git a/src/components/DepositModal.tsx b/src/components/DepositModal.tsx deleted file mode 100644 index ed12007..0000000 --- a/src/components/DepositModal.tsx +++ /dev/null @@ -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 ( -
- - - - Deposit Funds - - Specify the amount you would like to deposit to your node. - setAmount(BigInt(e.target.value))} - /> - - - - - - -
- ) -} diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx deleted file mode 100644 index 5851bd3..0000000 --- a/src/components/SearchBar.tsx +++ /dev/null @@ -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 ( -
- - - - - - -
- ) -} diff --git a/src/components/WDModal.tsx b/src/components/WDModal.tsx new file mode 100644 index 0000000..fda8948 --- /dev/null +++ b/src/components/WDModal.tsx @@ -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(null) + const [amountError, setAmountError] = useState(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) => { + 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 ( +
+ + + + {label} + + {dialogMessage} + + {amountError && ( + + Please provide valid BZZ amount (max 16 decimals). Error: {amountError.message} + + )} + + + + + + +
+ ) +} diff --git a/src/components/WithdrawlModal.tsx b/src/components/WithdrawlModal.tsx deleted file mode 100644 index c65a97d..0000000 --- a/src/components/WithdrawlModal.tsx +++ /dev/null @@ -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 ( -
- - - - Withdraw Funds - - Specify the amount you would like to withdraw from your node. - setAmount(BigInt(e.target.value))} - /> - - - - - - -
- ) -} diff --git a/src/containers/DepositModal.tsx b/src/containers/DepositModal.tsx new file mode 100644 index 0000000..5d73b93 --- /dev/null +++ b/src/containers/DepositModal.tsx @@ -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 ( + + ) +} diff --git a/src/containers/WithdrawModal.tsx b/src/containers/WithdrawModal.tsx new file mode 100644 index 0000000..b606b86 --- /dev/null +++ b/src/containers/WithdrawModal.tsx @@ -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 ( + + ) +} diff --git a/src/models/Token.test.ts b/src/models/Token.test.ts new file mode 100644 index 0000000..55abe41 --- /dev/null +++ b/src/models/Token.test.ts @@ -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) + }) + }) + }) +}) diff --git a/src/models/Token.ts b/src/models/Token.ts new file mode 100644 index 0000000..3afbdd1 --- /dev/null +++ b/src/models/Token.ts @@ -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)) + } +} diff --git a/src/pages/accounting/AccountCard.tsx b/src/pages/accounting/AccountCard.tsx index 3a92811..99f56bb 100644 --- a/src/pages/accounting/AccountCard.tsx +++ b/src/pages/accounting/AccountCard.tsx @@ -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 {

Accounting

- +
@@ -68,28 +68,28 @@ function AccountCard(props: Props): ReactElement { - Total Balance + Total Balance (BZZ) - {ConvertBalanceToBZZ(props.chequebookBalance.totalBalance)} + {fromBZZbaseUnit(props.chequebookBalance.totalBalance)} - Available Balance + Available Balance (BZZ) - {ConvertBalanceToBZZ(props.chequebookBalance.availableBalance)} + {fromBZZbaseUnit(props.chequebookBalance.availableBalance)} - Total Sent / Received + Total Sent / Received (BZZ) - {ConvertBalanceToBZZ(props.settlements?.totalsent || 0)} /{' '} - {ConvertBalanceToBZZ(props.settlements?.totalreceived || 0)} + {fromBZZbaseUnit(props.settlements?.totalsent || 0)} /{' '} + {fromBZZbaseUnit(props.settlements?.totalreceived || 0)} ( - {ConvertBalanceToBZZ( + {fromBZZbaseUnit( (props.settlements && props.settlements?.totalsent - props.settlements?.totalreceived) || 0, )} ) diff --git a/src/pages/accounting/BalancesTable.tsx b/src/pages/accounting/BalancesTable.tsx index ac4b0c5..c1ebbcd 100644 --- a/src/pages/accounting/BalancesTable.tsx +++ b/src/pages/accounting/BalancesTable.tsx @@ -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 { {peerBalance.peer} 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()} diff --git a/src/pages/accounting/ChequebookTable.tsx b/src/pages/accounting/ChequebookTable.tsx index 3e35926..4c81982 100644 --- a/src/pages/accounting/ChequebookTable.tsx +++ b/src/pages/accounting/ChequebookTable.tsx @@ -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 {

{peerCheque.lastreceived?.payout - ? `${ConvertBalanceToBZZ(peerCheque.lastreceived?.payout).toFixed(7).toLocaleString()} from` + ? `${fromBZZbaseUnit(peerCheque.lastreceived?.payout).toFixed(7).toLocaleString()} from` : '-'} {peerCheque.lastreceived ? ( @@ -97,7 +97,7 @@ function ChequebookTable(props: Props): ReactElement {

{peerCheque.lastsent?.payout - ? `${ConvertBalanceToBZZ(peerCheque.lastsent?.payout).toFixed(7).toLocaleString()} to` + ? `${fromBZZbaseUnit(peerCheque.lastsent?.payout).toFixed(7).toLocaleString()} to` : '-'} {peerCheque.lastsent ? ( diff --git a/src/pages/accounting/PeerDetailDrawer.tsx b/src/pages/accounting/PeerDetailDrawer.tsx index c2c405d..babcefd 100644 --- a/src/pages/accounting/PeerDetailDrawer.tsx +++ b/src/pages/accounting/PeerDetailDrawer.tsx @@ -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: {' '} - {peerCheque?.lastsent?.payout ? ConvertBalanceToBZZ(peerCheque?.lastsent?.payout) : '-'} + {peerCheque?.lastsent?.payout ? fromBZZbaseUnit(peerCheque?.lastsent?.payout) : '-'}

@@ -105,7 +105,7 @@ export default function Index(props: Props): ReactElement { Payout: {' '} - {peerCheque?.lastreceived?.payout ? ConvertBalanceToBZZ(peerCheque?.lastreceived?.payout) : '-'} + {peerCheque?.lastreceived?.payout ? fromBZZbaseUnit(peerCheque?.lastreceived?.payout) : '-'}

@@ -124,14 +124,14 @@ export default function Index(props: Props): ReactElement {

Cumulative Payout: - {peerCashout?.cumulativePayout ? ConvertBalanceToBZZ(peerCashout?.cumulativePayout) : '-'} + {peerCashout?.cumulativePayout ? fromBZZbaseUnit(peerCashout?.cumulativePayout) : '-'}

Last Payout: {' '} - {peerCashout?.result.lastPayout ? ConvertBalanceToBZZ(peerCashout?.result.lastPayout) : '-'} + {peerCashout?.result.lastPayout ? fromBZZbaseUnit(peerCashout?.result.lastPayout) : '-'} {peerCashout?.result.bounced ? 'Bounced' : ''}

diff --git a/src/pages/accounting/SettlementsTable.tsx b/src/pages/accounting/SettlementsTable.tsx index 1718334..b12f479 100644 --- a/src/pages/accounting/SettlementsTable.tsx +++ b/src/pages/accounting/SettlementsTable.tsx @@ -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 { {item.peer} - {item.received > 0 ? ConvertBalanceToBZZ(item.received).toFixed(7).toLocaleString() : item.received} + {item.received > 0 ? fromBZZbaseUnit(item.received).toFixed(7).toLocaleString() : item.received} - {item.sent > 0 ? ConvertBalanceToBZZ(item.sent).toFixed(7).toLocaleString() : item.sent} + {item.sent > 0 ? fromBZZbaseUnit(item.sent).toFixed(7).toLocaleString() : item.sent} ))} diff --git a/src/pages/status/SetupSteps/ChequebookDeployFund.tsx b/src/pages/status/SetupSteps/ChequebookDeployFund.tsx index 4b0455c..ef28b58 100644 --- a/src/pages/status/SetupSteps/ChequebookDeployFund.tsx +++ b/src/pages/status/SetupSteps/ChequebookDeployFund.tsx @@ -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' diff --git a/src/utils/common.tsx b/src/utils/common.tsx deleted file mode 100644 index 0bf8696..0000000 --- a/src/utils/common.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export const ConvertBalanceToBZZ = (amount: number): number => { - return amount / 10 ** 16 -} diff --git a/src/utils/index.test.ts b/src/utils/index.test.ts new file mode 100644 index 0000000..bf9dc45 --- /dev/null +++ b/src/utils/index.test.ts @@ -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() + }) + }) + }) +}) diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..5ea2dca --- /dev/null +++ b/src/utils/index.ts @@ -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') +}