feat: bee-js revamp (#690)

* chore: initial commit

* refactor: remove unnecessary wrappers

* style: add missing newline

* chore: bump bee-js

* chore: ignore any cast in fdp

* fix: remove cid import

* fix: make TextEncoder and TextDecoder available in jest

* refactor: dedupe stamp ttl second conversion

* refactor: use convenience methods from bee-js

* feat: update to bee-js for restored ens support

* fix: bump bee-js to get download fix

* fix: resolve feed before downloading reference

* fix: fix token displays

* fix: fix token input modal error message

* refactor: remove wallet balance provider

* chore: remove dead code

* refactor: upcoming bee js 0.15.0 (#692)

* chore: initial commit

* fix: do not break page when duration seconds is 0

* ci: remove cache

* chore: upgrade bee-js

* feat: bee-js and bee v2.6 compatibility

* chore: switch upcoming/bee-js to ethersphere/bee-js
This commit is contained in:
Cafe137
2025-07-16 17:10:14 +02:00
committed by GitHub
parent 082a8f52ef
commit 1249c0df71
62 changed files with 675 additions and 16303 deletions
-11
View File
@@ -28,18 +28,7 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
## Try getting the node modules from cache, if failed npm ci
- uses: actions/cache@v2
id: cache-npm
with:
path: node_modules
key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.OS }}-node-${{ matrix.node-version }}-${{ env.cache-name }}-
${{ runner.OS }}-node-${{ matrix.node-version }}-
- name: Install npm deps
if: steps.cache-npm.outputs.cache-hit != 'true'
run: npm ci
- name: Commit linting
-13
View File
@@ -25,21 +25,8 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
## Try getting the node modules from cache, if failed npm ci
- uses: actions/cache@v2
id: cache-npm
with:
path: node_modules
key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.OS }}-node-${{ matrix.node-version }}-${{ env.cache-name }}-
${{ runner.OS }}-node-${{ matrix.node-version }}-
- name: Install npm deps
if: steps.cache-npm.outputs.cache-hit != 'true'
run: npm ci
- name: Run tests
run: npm run test
+193 -15298
View File
File diff suppressed because it is too large Load Diff
+1 -3
View File
@@ -26,8 +26,7 @@
"url": "https://github.com/ethersphere/bee-dashboard.git"
},
"dependencies": {
"@ethersphere/bee-js": "^8.3.1",
"@ethersphere/swarm-cid": "^0.1.0",
"@ethersphere/bee-js": "^9.6.0",
"@fairdatasociety/fdp-storage": "^0.19.0",
"@material-ui/core": "4.12.3",
"@material-ui/icons": "4.11.2",
@@ -43,7 +42,6 @@
"formik": "2.2.9",
"formik-material-ui": "3.0.1",
"jszip": "^3.10.1",
"mantaray-js": "^1.0.3",
"material-ui-dropzone": "3.5.0",
"notistack": "^3.0.1",
"opener": "1.5.2",
+18 -21
View File
@@ -12,7 +12,6 @@ import { Provider as PlatformProvider } from './providers/Platform'
import { Provider as SettingsProvider } from './providers/Settings'
import { Provider as StampsProvider } from './providers/Stamps'
import { Provider as TopUpProvider } from './providers/TopUp'
import { Provider as BalanceProvider } from './providers/WalletBalance'
import BaseRouter from './routes'
import { theme } from './theme'
@@ -45,26 +44,24 @@ const App = ({
>
<TopUpProvider>
<BeeProvider>
<BalanceProvider>
<StampsProvider>
<FileProvider>
<FeedsProvider>
<PlatformProvider>
<SnackbarProvider preventDuplicate anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}>
<Router>
<>
<CssBaseline />
<Dashboard errorReporting={errorReporting}>
<BaseRouter />
</Dashboard>
</>
</Router>
</SnackbarProvider>
</PlatformProvider>
</FeedsProvider>
</FileProvider>
</StampsProvider>
</BalanceProvider>
<StampsProvider>
<FileProvider>
<FeedsProvider>
<PlatformProvider>
<SnackbarProvider preventDuplicate anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}>
<Router>
<>
<CssBaseline />
<Dashboard errorReporting={errorReporting}>
<BaseRouter />
</Dashboard>
</>
</Router>
</SnackbarProvider>
</PlatformProvider>
</FeedsProvider>
</FileProvider>
</StampsProvider>
</BeeProvider>
</TopUpProvider>
</SettingsProvider>
+3 -8
View File
@@ -9,7 +9,6 @@ import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react'
import Zap from 'remixicon-react/FlashlightLineIcon'
import { Context as SettingsContext } from '../providers/Settings'
import EthereumAddress from './EthereumAddress'
interface Props {
peerId: string
@@ -37,13 +36,9 @@ export default function CheckoutModal({ peerId, uncashedAmount }: Props): ReactE
.cashoutLastCheque(peerId)
.then(res => {
setOpen(false)
enqueueSnackbar(
<span>
Successfully cashed out cheque. Transaction
<EthereumAddress hideBlockie transaction address={res} />
</span>,
{ variant: 'success' },
)
enqueueSnackbar(<span>Successfully cashed out cheque. Transaction {res.toHex()}</span>, {
variant: 'success',
})
})
.catch((e: Error) => {
console.error(e) // eslint-disable-line
+5 -5
View File
@@ -1,5 +1,5 @@
import { Utils } from '@ethersphere/bee-js'
import { Typography } from '@material-ui/core/'
import { EthAddress } from '@ethersphere/bee-js'
import { ReactElement } from 'react'
import Identicon from 'react-identicons'
import { BLOCKCHAIN_EXPLORER_URL } from '../constants'
@@ -8,7 +8,7 @@ import { Flex } from './Flex'
import QRCodeModal from './QRCodeModal'
interface Props {
address: string | undefined
address: EthAddress | undefined
hideBlockie?: boolean
transaction?: boolean
truncate?: boolean
@@ -21,7 +21,7 @@ export default function EthereumAddress(props: Props): ReactElement {
<Flex>
{props.hideBlockie ? null : (
<div style={{ paddingTop: '5px', marginRight: '10px' }}>
<Identicon size={20} string={Utils.capitalizeAddressERC55(props.address)} />
<Identicon size={20} string={props.address.toChecksum()} />
</div>
)}
<div>
@@ -45,8 +45,8 @@ export default function EthereumAddress(props: Props): ReactElement {
{props.address}
</a>
</div>
<QRCodeModal value={Utils.capitalizeAddressERC55(props.address)} label={'Ethereum Address'} />
<ClipboardCopy value={Utils.capitalizeAddressERC55(props.address)} />
<QRCodeModal value={props.address.toChecksum()} label={'Ethereum Address'} />
<ClipboardCopy value={props.address.toChecksum()} />
</Flex>
) : (
'-'
+1 -1
View File
@@ -1,6 +1,6 @@
import { BeeModes } from '@ethersphere/bee-js'
import { Box, Divider, Drawer, Grid, List, Link as MUILink, Typography } from '@material-ui/core'
import { Theme, createStyles, makeStyles } from '@material-ui/core/styles'
import { BeeModes } from '@ethersphere/bee-js'
import { ReactElement, useContext } from 'react'
import { Link } from 'react-router-dom'
import FilesIcon from 'remixicon-react/ArrowUpDownLineIcon'
+3 -3
View File
@@ -1,4 +1,3 @@
import { Bee } from '@ethersphere/bee-js'
import { Box } from '@material-ui/core'
import Button from '@material-ui/core/Button'
import Dialog from '@material-ui/core/Dialog'
@@ -6,6 +5,7 @@ import DialogActions from '@material-ui/core/DialogActions'
import DialogContent from '@material-ui/core/DialogContent'
import DialogTitle from '@material-ui/core/DialogTitle'
import Input from '@material-ui/core/Input'
import { BatchId, Bee } from '@ethersphere/bee-js'
import { useSnackbar } from 'notistack'
import { ReactElement, ReactNode, useState } from 'react'
@@ -13,14 +13,14 @@ interface Props {
type: 'Topup' | 'Dilute'
icon: ReactNode
bee: Bee
stamp: string
stamp: BatchId
}
export default function StampExtensionModal({ type, icon, bee, stamp }: Props): ReactElement {
const [open, setOpen] = useState(false)
const [amount, setAmount] = useState('')
const { enqueueSnackbar } = useSnackbar()
const label = `${type} ${stamp.substring(0, 8)}`
const label = `${type} ${stamp.toHex().substring(0, 8)}`
const handleClickOpen = (e: React.MouseEvent<HTMLButtonElement>) => {
setOpen(true)
+11 -12
View File
@@ -1,24 +1,23 @@
import { ReactElement, ReactNode, 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 from '@material-ui/core/FormHelperText'
import { Token } from '../models/Token'
import type { BigNumber } from 'bignumber.js'
import Input from '@material-ui/core/Input'
import { BZZ, TransactionId } from '@ethersphere/bee-js'
import { useSnackbar } from 'notistack'
import { ReactElement, ReactNode, useState } from 'react'
interface Props {
successMessage: string
errorMessage: string
dialogMessage: string
label: string
max?: BigNumber
min?: BigNumber
action: (amount: bigint) => Promise<string>
max?: BZZ
min?: BZZ
action: (amount: BZZ) => Promise<TransactionId>
icon?: ReactNode
}
@@ -34,7 +33,7 @@ export default function WithdrawDepositModal({
}: Props): ReactElement {
const [open, setOpen] = useState(false)
const [amount, setAmount] = useState('')
const [amountToken, setAmountToken] = useState<Token | null>(null)
const [amountToken, setAmountToken] = useState<BZZ | null>(null)
const [amountError, setAmountError] = useState<Error | null>(null)
const { enqueueSnackbar } = useSnackbar()
@@ -51,7 +50,7 @@ export default function WithdrawDepositModal({
if (amountToken === null) return
try {
const transactionHash = await action(amountToken.toBigInt as bigint)
const transactionHash = await action(amountToken)
setOpen(false)
enqueueSnackbar(`${successMessage} Transaction ${transactionHash}`, { variant: 'success' })
} catch (e) {
@@ -65,12 +64,12 @@ export default function WithdrawDepositModal({
setAmount(value)
setAmountError(null)
try {
const t = Token.fromDecimal(value)
const t = BZZ.fromDecimalString(value)
setAmountToken(t)
if (min && t.toDecimal.isLessThan(min)) setAmountError(new Error(`Needs to be more than ${min}`))
if (min && t.lt(min)) setAmountError(new Error(`Needs to be more than ${min.toSignificantDigits(4)}`))
if (max && t.toDecimal.isGreaterThan(max)) setAmountError(new Error(`Needs to be less than ${max}`))
if (max && t.gt(max)) setAmountError(new Error(`Needs to be less than ${max.toSignificantDigits(4)}`))
} catch (e) {
setAmountError(e as Error)
}
+4 -4
View File
@@ -1,4 +1,4 @@
import { BigNumber } from 'bignumber.js'
import { BZZ } from '@ethersphere/bee-js'
import { ReactElement, useContext } from 'react'
import Download from 'remixicon-react/DownloadLineIcon'
import WithdrawDepositModal from '../components/WithdrawDepositModal'
@@ -16,13 +16,13 @@ export default function DepositModal(): ReactElement {
dialogMessage="Amount of xBZZ to deposit to the checkbook, from your node."
label="Deposit"
icon={<Download size="1rem" />}
min={new BigNumber(0)}
action={async (amount: bigint) => {
min={BZZ.fromPLUR('1')}
action={async (amount: BZZ) => {
if (!beeApi) {
throw new Error('Bee URL is not valid')
}
const transactionHash = await beeApi.depositTokens(amount.toString())
const transactionHash = await beeApi.depositTokens(amount)
refresh()
return transactionHash
+6 -6
View File
@@ -1,4 +1,4 @@
import { BigNumber } from 'bignumber.js'
import { BZZ } from '@ethersphere/bee-js'
import { ReactElement, useContext } from 'react'
import Download from 'remixicon-react/DownloadLineIcon'
import WithdrawDepositModal from '../components/WithdrawDepositModal'
@@ -21,8 +21,8 @@ export default function StakeModal({ onStarted, onFinished }: Props): ReactEleme
dialogMessage="Specify the amount of xBZZ you would like to stake. Your first stake must be at least 10 xBZZ. This will lock your tokens."
label="Stake"
icon={<Download size="1rem" />}
min={new BigNumber(0)}
action={async (amount: bigint) => {
min={BZZ.fromPLUR('1')}
action={async (amount: BZZ) => {
if (!beeApi) {
throw new Error('Bee URL is not valid')
}
@@ -30,13 +30,13 @@ export default function StakeModal({ onStarted, onFinished }: Props): ReactEleme
onStarted()
try {
await beeApi.depositStake(amount.toString())
const transactionHash = await beeApi.depositStake(amount)
return transactionHash
} finally {
refresh()
onFinished()
}
return 'unknown'
}}
/>
)
+4 -4
View File
@@ -1,4 +1,4 @@
import { BigNumber } from 'bignumber.js'
import { BZZ } from '@ethersphere/bee-js'
import { ReactElement, useContext } from 'react'
import Upload from 'remixicon-react/UploadLineIcon'
import WithdrawDepositModal from '../components/WithdrawDepositModal'
@@ -16,13 +16,13 @@ export default function WithdrawModal(): ReactElement {
dialogMessage="Amount of xBZZ to withdraw from the checkbook to your node."
label="Withdraw"
icon={<Upload size="1rem" />}
min={new BigNumber(0)}
action={async (amount: bigint) => {
min={BZZ.fromPLUR('1')}
action={async (amount: BZZ) => {
if (!beeApi) {
throw new Error('Bee URL is not valid')
}
const transactionHash = await beeApi.withdrawTokens(amount.toString())
const transactionHash = await beeApi.withdrawTokens(amount)
refresh()
return transactionHash
+23 -25
View File
@@ -1,22 +1,20 @@
import { Bee, LastCashoutActionResponse } from '@ethersphere/bee-js'
import { AllSettlements, Bee, BZZ, LastCashoutActionResponse, PeerBalance, Settlements } from '@ethersphere/bee-js'
import { useEffect, useState } from 'react'
import { Token } from '../models/Token'
import { Balance, Settlement, Settlements } from '../types'
import { makeRetriablePromise, unwrapPromiseSettlements } from '../utils'
interface UseAccountingHook {
isLoadingUncashed: boolean
totalUncashed: Token
totalUncashed: BZZ
accounting: Accounting[] | null
}
export interface Accounting {
peer: string
uncashedAmount: Token
balance: Token
received: Token
sent: Token
total: Token
uncashedAmount: BZZ
balance: BZZ
received: BZZ
sent: BZZ
total: BZZ
}
/**
@@ -29,8 +27,8 @@ export interface Accounting {
* @returns
*/
function mergeAccounting(
balances: Balance[] | null,
settlements?: Settlement[],
balances: PeerBalance[] | null,
settlements?: Settlements[],
uncashedAmounts?: LastCashoutActionResponse[],
): Accounting[] | null {
// Settlements or balances are still loading or there is an error -> return null
@@ -44,9 +42,9 @@ function mergeAccounting(
(accounting[peer] = {
peer,
balance,
sent: new Token('0'),
received: new Token('0'),
uncashedAmount: new Token('0'),
sent: BZZ.fromPLUR('0'),
received: BZZ.fromPLUR('0'),
uncashedAmount: BZZ.fromPLUR('0'),
total: balance,
}),
)
@@ -57,7 +55,7 @@ function mergeAccounting(
...accounting[peer],
sent,
received,
total: new Token(accounting[peer].balance.toBigNumber.plus(received.toBigNumber).minus(sent.toBigNumber)),
total: accounting[peer].balance.plus(received).minus(sent),
}),
)
@@ -65,14 +63,16 @@ function mergeAccounting(
if (!uncashedAmounts) return Object.values(accounting).sort((a, b) => (a.peer < b.peer ? -1 : 1))
uncashedAmounts?.forEach(({ peer, uncashedAmount }) => {
accounting[peer].uncashedAmount = new Token(uncashedAmount)
accounting[peer].uncashedAmount = uncashedAmount
})
// Return sorted by the uncashed amount first and then by the peer id
return Object.values(accounting).sort((a, b) => {
const diff = b.uncashedAmount.toBigNumber.minus(a.uncashedAmount.toBigNumber).toNumber()
const diff = Number(b.uncashedAmount.minus(a.uncashedAmount))
if (diff !== 0) return diff
if (diff !== 0) {
return diff
}
return a.peer < b.peer ? -1 : 1
})
@@ -80,8 +80,8 @@ function mergeAccounting(
export const useAccounting = (
beeApi: Bee | null,
settlements: Settlements | null,
balances: Balance[] | null,
settlements: AllSettlements | null,
balances: PeerBalance[] | null,
): UseAccountingHook => {
const [isLoadingUncashed, setIsloadingUncashed] = useState<boolean>(false)
const [uncashedAmounts, setUncashedAmounts] = useState<LastCashoutActionResponse[] | undefined>(undefined)
@@ -92,7 +92,7 @@ export const useAccounting = (
setIsloadingUncashed(true)
const promises = settlements.settlements
.filter(({ received }) => received.toBigNumber.gt('0'))
.filter(({ received }) => received.gt(BZZ.fromPLUR('0')))
.map(({ peer }) => makeRetriablePromise(() => beeApi.getLastCashoutAction(peer)))
Promise.allSettled(promises).then(settlements => {
@@ -104,10 +104,8 @@ export const useAccounting = (
const accounting = mergeAccounting(balances, settlements?.settlements, uncashedAmounts)
let totalUncashed: Token = new Token('0')
accounting?.forEach(
({ uncashedAmount }) => (totalUncashed = new Token(totalUncashed.toBigNumber.plus(uncashedAmount.toBigNumber))),
)
let totalUncashed = BZZ.fromPLUR('0')
accounting?.forEach(({ uncashedAmount }) => (totalUncashed = totalUncashed.plus(uncashedAmount)))
return {
isLoadingUncashed,
-14
View File
@@ -1,14 +0,0 @@
import { BigNumber } from 'bignumber.js'
import { Token } from './Token'
export const BZZ_DECIMAL_PLACES = 16
export class BzzToken extends Token {
constructor(value: BigNumber | string | bigint) {
super(value, BZZ_DECIMAL_PLACES)
}
static fromDecimal(value: BigNumber | string | bigint): BzzToken {
return Token.fromDecimal(value, BZZ_DECIMAL_PLACES)
}
}
-14
View File
@@ -1,14 +0,0 @@
import { BigNumber } from 'bignumber.js'
import { Token } from './Token'
const DAI_DECIMAL_PLACES = 18
export class DaiToken extends Token {
constructor(value: BigNumber | string | bigint) {
super(value, DAI_DECIMAL_PLACES)
}
static fromDecimal(value: BigNumber | string | bigint): DaiToken {
return Token.fromDecimal(value, DAI_DECIMAL_PLACES)
}
}
-39
View File
@@ -1,39 +0,0 @@
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)
})
})
})
})
-98
View File
@@ -1,98 +0,0 @@
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: ${amount} ${decimals}`)
}
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))
}
toFixedDecimal(digits = 7): string {
return this.toDecimal.toFixed(digits)
}
toSignificantDigits(digits = 4): string {
const asString = this.toDecimal.toFixed(this.decimals)
let indexOfSignificantDigit = -1
let reachedDecimalPoint = false
for (let i = 0; i < asString.length; i++) {
const char = asString[i]
if (char === '.') {
reachedDecimalPoint = true
indexOfSignificantDigit = i + 1
} else if (reachedDecimalPoint && char !== '0') {
indexOfSignificantDigit = i
break
}
}
return asString.slice(0, indexOfSignificantDigit + digits)
}
minusBaseUnits(amount: string | BigNumber | bigint): Token {
const baseUnits = makeBigNumber(amount)
return new Token(
this.toBigNumber.minus(baseUnits.multipliedBy(new BigNumber(10).pow(this.decimals))),
this.decimals,
)
}
plusBaseUnits(amount: string | BigNumber | bigint): Token {
const baseUnits = makeBigNumber(amount)
return new Token(this.toBigNumber.plus(baseUnits.multipliedBy(new BigNumber(10).pow(this.decimals))), this.decimals)
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { BeeModes } from '@ethersphere/bee-js'
import { createStyles, makeStyles, Tab, Tabs, Theme } from '@material-ui/core'
import { BeeModes } from '@ethersphere/bee-js'
import { ReactElement, useContext } from 'react'
import { useNavigate } from 'react-router-dom'
import { Context } from '../../providers/Bee'
@@ -1,4 +1,3 @@
import { Utils } from '@ethersphere/bee-js'
import { Box } from '@material-ui/core'
import { ReactElement, useContext } from 'react'
import ExpandableList from '../../../components/ExpandableList'
@@ -35,20 +34,20 @@ export function AccountChequebook(): ReactElement {
<ExpandableList label="Chequebook" defaultOpen>
<ExpandableListItem
label="Total Balance"
value={`${chequebookBalance?.totalBalance.toFixedDecimal()} xBZZ`}
value={`${chequebookBalance?.totalBalance.toSignificantDigits(4)} xBZZ`}
/>
<ExpandableListItem
label="Available Uncommitted Balance"
value={`${chequebookBalance?.availableBalance.toFixedDecimal()} xBZZ`}
value={`${chequebookBalance?.availableBalance.toSignificantDigits(4)} xBZZ`}
/>
<ExpandableListItem
label="Total Cheques Amount Sent"
value={`${settlements?.totalSent.toFixedDecimal()} xBZZ`}
value={`${settlements?.totalSent.toSignificantDigits(4)} xBZZ`}
/>
<Box mb={2}>
<ExpandableListItem
label="Total Cheques Amount Received"
value={`${settlements?.totalReceived.toFixedDecimal()} xBZZ`}
value={`${settlements?.totalReceived.toSignificantDigits(4)} xBZZ`}
/>
</Box>
<ExpandableListItemActions>
@@ -60,7 +59,7 @@ export function AccountChequebook(): ReactElement {
<ExpandableList label="Blockchain" defaultOpen>
<ExpandableListItemKey
label="Ethereum address"
value={nodeAddresses?.ethereum ? Utils.capitalizeAddressERC55(nodeAddresses.ethereum) : ''}
value={nodeAddresses?.ethereum ? nodeAddresses.ethereum.toChecksum() : ''}
/>
<ExpandableListItemKey
label="Chequebook contract address"
+8 -7
View File
@@ -1,16 +1,19 @@
import { Box } from '@material-ui/core'
import { NULL_TOPIC } from '@ethersphere/bee-js'
import { ReactElement, useContext, useState } from 'react'
import Download from 'remixicon-react/Download2LineIcon'
import Info from 'remixicon-react/InformationLineIcon'
import { useNavigate } from 'react-router'
import PlusSquare from 'remixicon-react/AddBoxLineIcon'
import Trash from 'remixicon-react/DeleteBin7LineIcon'
import { useNavigate } from 'react-router'
import Download from 'remixicon-react/Download2LineIcon'
import Info from 'remixicon-react/InformationLineIcon'
import ExpandableList from '../../../components/ExpandableList'
import ExpandableListItem from '../../../components/ExpandableListItem'
import ExpandableListItemActions from '../../../components/ExpandableListItemActions'
import ExpandableListItemKey from '../../../components/ExpandableListItemKey'
import { SwarmButton } from '../../../components/SwarmButton'
import { Context as IdentityContext, Identity } from '../../../providers/Feeds'
import TroubleshootConnectionCard from '../../../components/TroubleshootConnectionCard'
import { Context as BeeContext, CheckState } from '../../../providers/Bee'
import { Identity, Context as IdentityContext } from '../../../providers/Feeds'
import { ROUTES } from '../../../routes'
import { formatEnum } from '../../../utils'
import { persistIdentitiesWithoutUpdate } from '../../../utils/identity'
@@ -19,8 +22,6 @@ import { ExportFeedDialog } from '../../feeds/ExportFeedDialog'
import { ImportFeedDialog } from '../../feeds/ImportFeedDialog'
import { AccountNavigation } from '../AccountNavigation'
import { Header } from '../Header'
import TroubleshootConnectionCard from '../../../components/TroubleshootConnectionCard'
import { CheckState, Context as BeeContext } from '../../../providers/Bee'
export function AccountFeeds(): ReactElement {
const { identities, setIdentities } = useContext(IdentityContext)
@@ -98,7 +99,7 @@ export function AccountFeeds(): ReactElement {
<ExpandableListItem label="Identity type" value={formatEnum(x.type)} />
</ExpandableList>
</Box>
<ExpandableListItemKey label="Topic" value={'00'.repeat(32)} />
<ExpandableListItemKey label="Topic" value={NULL_TOPIC.toHex()} />
{x.feedHash && <ExpandableListItemKey label="Feed hash" value={x.feedHash} />}
<Box mt={0.75}>
<ExpandableListItemActions>
+6 -8
View File
@@ -5,16 +5,14 @@ import ExpandableListItemActions from '../../../components/ExpandableListItemAct
import { Loading } from '../../../components/Loading'
import TroubleshootConnectionCard from '../../../components/TroubleshootConnectionCard'
import StakeModal from '../../../containers/StakeModal'
import { CheckState, Context as BeeContext } from '../../../providers/Bee'
import { Context as BalanceContext } from '../../../providers/WalletBalance'
import { Context as BeeContext, CheckState } from '../../../providers/Bee'
import { AccountNavigation } from '../AccountNavigation'
import { Header } from '../Header'
export function AccountStaking(): ReactElement {
const [loading, setLoading] = useState(false)
const { status, stake } = useContext(BeeContext)
const { balance } = useContext(BalanceContext)
const { status, stake, walletBalance } = useContext(BeeContext)
if (status.all === CheckState.ERROR) return <TroubleshootConnectionCard />
@@ -31,15 +29,15 @@ export function AccountStaking(): ReactElement {
<Header />
<AccountNavigation active="STAKING" />
<div>
{loading || stake?.toDecimal === undefined ? (
{loading || !stake ? (
<Loading />
) : (
<ExpandableList label="Staking" defaultOpen>
<ExpandableListItem label="Staked BZZ" value={`${stake?.toSignificantDigits()} xBZZ`} />
{balance?.bzz ? (
<ExpandableListItem label="Staked BZZ" value={`${stake?.toSignificantDigits(4)} xBZZ`} />
{walletBalance?.bzzBalance ? (
<ExpandableListItem
label="Available xBZZ balance"
value={`${balance?.bzz.toSignificantDigits(4)} xBZZ`}
value={`${walletBalance.bzzBalance.toSignificantDigits(4)} xBZZ`}
/>
) : null}
<ExpandableListItemActions>
+12 -12
View File
@@ -1,5 +1,5 @@
import { BeeModes, Utils } from '@ethersphere/bee-js'
import { Box, Grid, Typography } from '@material-ui/core'
import { BeeModes } from '@ethersphere/bee-js'
import { ReactElement, useContext } from 'react'
import { useNavigate } from 'react-router'
import Download from 'remixicon-react/DownloadLineIcon'
@@ -13,15 +13,13 @@ import { SwarmButton } from '../../../components/SwarmButton'
import TroubleshootConnectionCard from '../../../components/TroubleshootConnectionCard'
import { Context as BeeContext, CheckState } from '../../../providers/Bee'
import { Context as SettingsContext } from '../../../providers/Settings'
import { Context as BalanceProvider } from '../../../providers/WalletBalance'
import { ROUTES } from '../../../routes'
import { AccountNavigation } from '../AccountNavigation'
import { Header } from '../Header'
export function AccountWallet(): ReactElement {
const { nodeAddresses, nodeInfo, status } = useContext(BeeContext)
const { nodeAddresses, nodeInfo, status, walletBalance } = useContext(BeeContext)
const { isDesktop } = useContext(SettingsContext)
const { balance } = useContext(BalanceProvider)
const navigate = useNavigate()
@@ -53,20 +51,22 @@ export function AccountWallet(): ReactElement {
)}
</Grid>
</Box>
{balance && nodeAddresses ? (
{walletBalance && nodeAddresses ? (
<>
<Box mb={0.25}>
<ExpandableListItemKey
label="Node wallet address"
value={Utils.capitalizeAddressERC55(nodeAddresses.ethereum)}
expanded
/>
<ExpandableListItemKey label="Node wallet address" value={nodeAddresses.ethereum.toChecksum()} expanded />
</Box>
<Box mb={0.25}>
<ExpandableListItem label="xDAI balance" value={`${balance.dai.toSignificantDigits(4)} xDAI`} />
<ExpandableListItem
label="xDAI balance"
value={`${walletBalance.nativeTokenBalance.toSignificantDigits(4)} xDAI`}
/>
</Box>
<Box mb={2}>
<ExpandableListItem label="xBZZ balance" value={`${balance.bzz.toSignificantDigits(4)} xBZZ`} />
<ExpandableListItem
label="xBZZ balance"
value={`${walletBalance.bzzBalance.toSignificantDigits(4)} xBZZ`}
/>
</Box>
</>
) : (
+12 -12
View File
@@ -1,3 +1,4 @@
import { BZZ } from '@ethersphere/bee-js'
import type { ReactElement } from 'react'
import CashoutModal from '../../components/CashoutModal'
import ExpandableList from '../../components/ExpandableList'
@@ -5,44 +6,43 @@ import ExpandableListItem from '../../components/ExpandableListItem'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
import { Accounting } from '../../hooks/accounting'
import type { Token } from '../../models/Token'
interface Props {
isLoadingUncashed: boolean
totalUncashed: Token
totalUncashed: BZZ
accounting: Accounting[] | null
}
export default function PeerBalances({ accounting, isLoadingUncashed, totalUncashed }: Props): ReactElement | null {
const uncashedPeers = accounting?.filter(({ uncashedAmount }) => uncashedAmount.toBigNumber.isGreaterThan('0')) || []
const uncashedPeers = accounting?.filter(({ uncashedAmount }) => uncashedAmount.gt(BZZ.fromPLUR('0'))) || []
return (
<ExpandableList
label={`Peers (${uncashedPeers.length})`}
info={`${totalUncashed.toFixedDecimal()} xBZZ (uncashed)`}
info={`${totalUncashed.toSignificantDigits(4)} xBZZ (uncashed)`}
>
<ExpandableListItem label="Uncashed Amount Total" value={`${totalUncashed.toFixedDecimal()} xBZZ`} />
<ExpandableListItem label="Uncashed Amount Total" value={`${totalUncashed.toSignificantDigits(4)} xBZZ`} />
{uncashedPeers.map(({ peer, balance, received, sent, uncashedAmount, total }) => (
<ExpandableList
key={peer}
label={`Peer ${peer.slice(0, 8)}[…]`}
level={1}
info={`${uncashedAmount.toFixedDecimal()} xBZZ (uncashed)`}
info={`${uncashedAmount.toSignificantDigits(4)} xBZZ (uncashed)`}
>
<ExpandableListItemKey label="Peer ID" value={peer} />
<ExpandableListItem label="Outstanding Balance" value={`${balance.toFixedDecimal()} xBZZ`} />
<ExpandableListItem label="Outstanding Balance" value={`${balance.toSignificantDigits(4)} xBZZ`} />
<ExpandableListItem
label="Settlements Sent / Received"
value={`-${sent.toFixedDecimal()} / ${received.toFixedDecimal()} xBZZ`}
value={`-${sent.toSignificantDigits(4)} / ${received.toSignificantDigits(4)} xBZZ`}
/>
<ExpandableListItem label="Total" value={`${total.toFixedDecimal()} xBZZ`} />
<ExpandableListItem label="Total" value={`${total.toSignificantDigits(4)} xBZZ`} />
<ExpandableListItem
label="Uncashed Amount"
value={isLoadingUncashed ? 'loading…' : `${uncashedAmount.toFixedDecimal()} xBZZ`}
value={isLoadingUncashed ? 'loading…' : `${uncashedAmount.toSignificantDigits(4)} xBZZ`}
/>
{uncashedAmount.toBigNumber.isGreaterThan('0') && (
{uncashedAmount.gt(BZZ.fromPLUR('0')) && (
<ExpandableListItemActions>
<CashoutModal uncashedAmount={uncashedAmount.toFixedDecimal()} peerId={peer} />
<CashoutModal uncashedAmount={uncashedAmount.toSignificantDigits(4)} peerId={peer} />
</ExpandableListItemActions>
)}
</ExpandableList>
+13 -7
View File
@@ -1,14 +1,13 @@
import { Bee } from '@ethersphere/bee-js'
import { FdpStorage } from '@fairdatasociety/fdp-storage'
import { Pod } from '@fairdatasociety/fdp-storage/dist/pod/types'
import { CircularProgress, Typography } from '@material-ui/core'
import { Bee, MantarayNode } from '@ethersphere/bee-js'
import { useSnackbar } from 'notistack'
import { ReactElement, useEffect, useState } from 'react'
import ImportIcon from 'remixicon-react/AddBoxLineIcon'
import PlusCircle from 'remixicon-react/AddCircleLineIcon'
import { SwarmButton } from '../../components/SwarmButton'
import { joinUrl } from '../../react-fs/Utility'
import { ManifestJs } from '../../utils/manifest'
import { FdpLogin } from './FdpLogin'
import { FdpPods } from './FdpPods'
import { Horizontal } from './Horizontal'
@@ -25,7 +24,9 @@ async function makeFdp(): Promise<FdpStorage | null> {
return null
}
return new FdpStorage('http://localhost:1633', highestCapacityBatch.batchID, {
// TODO: FDS has bad types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new FdpStorage('http://localhost:1633', highestCapacityBatch.batchID.toHex() as any, {
ensOptions: {
rpcUrl: sepolia,
contractAddresses: {
@@ -120,11 +121,16 @@ export default function FDP(): ReactElement {
}
setCreatingPod(true)
const bee = new Bee('http://localhost:1633')
const manifestJs = new ManifestJs(bee)
const entries = await manifestJs.getHashes(importHash)
const manifest = await MantarayNode.unmarshal(bee, importHash)
await manifest.loadRecursively(bee)
const nodes = manifest.collect()
await fdp.personalStorage.create(name)
for (const [path, hash] of Object.entries(entries)) {
await fdp.file.uploadData(name, joinUrl('/', path), await bee.downloadData(hash))
for (const node of nodes) {
await fdp.file.uploadData(
name,
joinUrl('/', node.fullPathString),
(await bee.downloadData(node.targetAddress)).toUint8Array(),
)
}
const pods = await fdp.personalStorage.list()
setPods(pods.pods)
+2 -1
View File
@@ -1,4 +1,5 @@
import { Box, Grid, Typography } from '@material-ui/core'
import { NULL_TOPIC } from '@ethersphere/bee-js'
import { Form, Formik } from 'formik'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react'
@@ -108,7 +109,7 @@ export default function CreateNewFeed(): ReactElement {
</Box>
{values.type === 'V3' && <SwarmTextInput name="password" label="Password" password formik />}
<Box mt={2}>
<ExpandableListItemKey label="Topic" value={'00'.repeat(32)} />
<ExpandableListItemKey label="Topic" value={NULL_TOPIC.toHex()} />
</Box>
<Box mt={2} sx={{ bgcolor: '#fcf2e8' }} p={2}>
<Grid container justifyContent="space-between">
+1 -1
View File
@@ -122,7 +122,7 @@ export default function UpdateFeed(): ReactElement {
<Grid container>
{stamps ? (
<SwarmSelect
options={stamps.map(x => ({ value: x.batchID, label: x.batchID.slice(0, 8) }))}
options={stamps.map(x => ({ value: x.batchID.toHex(), label: x.batchID.toHex().slice(0, 8) }))}
onChange={onStampChange}
label="Stamp"
/>
+8 -7
View File
@@ -1,18 +1,19 @@
import { Box, Typography } from '@material-ui/core'
import { NULL_TOPIC } from '@ethersphere/bee-js'
import { ReactElement, useContext, useState } from 'react'
import Download from 'remixicon-react/DownloadLineIcon'
import PlusSquare from 'remixicon-react/AddBoxLineIcon'
import Info from 'remixicon-react/InformationLineIcon'
import Trash from 'remixicon-react/DeleteBin7LineIcon'
import { useNavigate } from 'react-router'
import PlusSquare from 'remixicon-react/AddBoxLineIcon'
import Trash from 'remixicon-react/DeleteBin7LineIcon'
import Download from 'remixicon-react/DownloadLineIcon'
import Info from 'remixicon-react/InformationLineIcon'
import ExpandableList from '../../components/ExpandableList'
import ExpandableListItem from '../../components/ExpandableListItem'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
import { SwarmButton } from '../../components/SwarmButton'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { CheckState, Context as BeeContext } from '../../providers/Bee'
import { Context as IdentityContext, Identity } from '../../providers/Feeds'
import { Context as BeeContext, CheckState } from '../../providers/Bee'
import { Identity, Context as IdentityContext } from '../../providers/Feeds'
import { ROUTES } from '../../routes'
import { formatEnum } from '../../utils'
import { persistIdentitiesWithoutUpdate } from '../../utils/identity'
@@ -97,7 +98,7 @@ export default function Feeds(): ReactElement {
<ExpandableListItem label="Identity type" value={formatEnum(x.type)} />
</ExpandableList>
</Box>
<ExpandableListItemKey label="Topic" value={'00'.repeat(32)} />
<ExpandableListItemKey label="Topic" value={NULL_TOPIC.toHex()} />
{x.feedHash && <ExpandableListItemKey label="Feed hash" value={x.feedHash} />}
<Box mt={0.75}>
<ExpandableListItemActions>
+2 -2
View File
@@ -1,5 +1,5 @@
import { Utils } from '@ethersphere/bee-js'
import { Box } from '@material-ui/core'
import { Reference } from '@ethersphere/bee-js'
import { ReactElement } from 'react'
import { DocumentationText } from '../../components/DocumentationText'
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
@@ -11,7 +11,7 @@ interface Props {
}
export function AssetSummary({ isWebsite, reference }: Props): ReactElement {
const isHash = Utils.isHexString(reference) && reference.length === 64
const isHash = Reference.isValid(reference)
return (
<>
+2 -2
View File
@@ -1,9 +1,9 @@
import { Context as SettingsContext } from '../../providers/Settings'
import { Box } from '@material-ui/core'
import { Tag } from '@ethersphere/bee-js'
import { ReactElement, useContext, useEffect, useRef, useState } from 'react'
import { DocumentationText } from '../../components/DocumentationText'
import { LinearProgressWithLabel } from '../../components/ProgressBar'
import { Tag } from '@ethersphere/bee-js'
import { Context as SettingsContext } from '../../providers/Settings'
interface Props {
reference: string
+22 -21
View File
@@ -1,4 +1,4 @@
import { BeeModes, Utils } from '@ethersphere/bee-js'
import { BeeModes, MantarayNode, NULL_ADDRESS, Reference } from '@ethersphere/bee-js'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react'
import { useNavigate } from 'react-router-dom'
@@ -11,7 +11,6 @@ import { Context as SettingsContext } from '../../providers/Settings'
import { ROUTES } from '../../routes'
import { recognizeEnsOrSwarmHash, regexpEns } from '../../utils'
import { HISTORY_KEYS, determineHistoryName, putHistory } from '../../utils/local-storage'
import { ManifestJs } from '../../utils/manifest'
import { FileNavigation } from './FileNavigation'
export function Download(): ReactElement {
@@ -26,41 +25,43 @@ export function Download(): ReactElement {
const navigate = useNavigate()
const validateChange = (value: string) => {
if (
Utils.isHexString(value, 64) ||
Utils.isHexString(value, 128) ||
!value.trim().length ||
regexpEns.test(value)
) {
if (Reference.isValid(value) || regexpEns.test(value)) {
setReferenceError(undefined)
} else {
setReferenceError('Incorrect format of swarm hash. Expected 64 or 128 hexstring characters or ENS domain.')
}
}
// TODO: Test this for feeds, bzz, and bytes
async function onSwarmIdentifier(identifier: string) {
setLoading(true)
if (!beeApi) {
setLoading(false)
return
}
setLoading(true)
try {
const manifestJs = new ManifestJs(beeApi)
const feedIdentifier = await manifestJs.resolveFeedManifest(identifier)
let manifest = await MantarayNode.unmarshal(beeApi, identifier)
await manifest.loadRecursively(beeApi)
if (feedIdentifier) {
identifier = feedIdentifier
}
const isManifest = await manifestJs.isManifest(identifier)
// If the manifest is a feed, resolve it and overwrite the manifest
await manifest.resolveFeed(beeApi).then(
async feed =>
await feed.ifPresentAsync(async feedUpdate => {
manifest = MantarayNode.unmarshalFromData(feedUpdate.payload.toUint8Array(), NULL_ADDRESS)
await manifest.loadRecursively(beeApi)
}),
)
if (!isManifest) {
throw Error('The specified hash does not contain valid content.')
}
const indexDocument = await manifestJs.getIndexDocumentPath(identifier)
putHistory(HISTORY_KEYS.DOWNLOAD_HISTORY, identifier, determineHistoryName(identifier, indexDocument))
const rootMetadata = manifest.getDocsMetadata()
putHistory(
HISTORY_KEYS.DOWNLOAD_HISTORY,
identifier,
determineHistoryName(identifier, rootMetadata.indexDocument),
)
setUploadOrigin(defaultUploadOrigin)
navigate(ROUTES.HASH.replace(':hash', identifier))
} catch (error: unknown) {
+2 -2
View File
@@ -27,7 +27,7 @@ export default function SimpleMenu({ stamps, selectedStamp, setSelected }: Props
<Menu anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
{stamps.map(stamp => (
<MenuItem
key={stamp.batchID}
key={stamp.batchID.toHex()}
onClick={() => {
setSelected(stamp)
handleClose()
@@ -35,7 +35,7 @@ export default function SimpleMenu({ stamps, selectedStamp, setSelected }: Props
selected={stamp.batchID === selectedStamp?.batchID}
>
<ListItemIcon>{stamp.usageText}</ListItemIcon>
<Typography variant="body2">{stamp.batchID.slice(0, 8)}[]</Typography>
<Typography variant="body2">{stamp.batchID.toHex().slice(0, 8)}[]</Typography>
</MenuItem>
))}
</Menu>
+48 -35
View File
@@ -1,4 +1,5 @@
import { Box, Typography } from '@material-ui/core'
import { MantarayNode, NULL_ADDRESS } from '@ethersphere/bee-js'
import { saveAs } from 'file-saver'
import JSZip from 'jszip'
import { useSnackbar } from 'notistack'
@@ -12,11 +13,10 @@ import { Context as BeeContext } from '../../providers/Bee'
import { Context as SettingsContext } from '../../providers/Settings'
import { ROUTES } from '../../routes'
import { determineHistoryName, HISTORY_KEYS, putHistory } from '../../utils/local-storage'
import { ManifestJs } from '../../utils/manifest'
import { AssetPreview } from './AssetPreview'
import { AssetSummary } from './AssetSummary'
import { DownloadActionBar } from './DownloadActionBar'
import { AssetSyncing } from './AssetSyncing'
import { DownloadActionBar } from './DownloadActionBar'
export function Share(): ReactElement {
const { apiUrl, beeApi } = useContext(SettingsContext)
@@ -41,44 +41,57 @@ export function Share(): ReactElement {
return
}
const manifestJs = new ManifestJs(beeApi)
const isManifest = await manifestJs.isManifest(reference)
try {
let manifest = await MantarayNode.unmarshal(beeApi, reference)
await manifest.loadRecursively(beeApi)
if (!isManifest) {
// If the manifest is a feed, resolve it and overwrite the manifest
await manifest.resolveFeed(beeApi).then(
async feed =>
await feed.ifPresentAsync(async feedUpdate => {
manifest = MantarayNode.unmarshalFromData(feedUpdate.payload.toUint8Array(), NULL_ADDRESS)
await manifest.loadRecursively(beeApi)
}),
)
const entries = manifest.collectAndMap()
delete entries[META_FILE_NAME]
setSwarmEntries(entries)
const docsMetadata = manifest.getDocsMetadata()
// needed in catch block, shadows the outer variable
const indexDocument = docsMetadata.indexDocument
setIndexDocument(indexDocument)
try {
const remoteMetadata = await beeApi.downloadFile(reference, META_FILE_NAME)
const formattedMetadata = remoteMetadata.data.toJSON() as Metadata
if (formattedMetadata.isVideo || formattedMetadata.isImage) {
setPreview(`${apiUrl}/bzz/${reference}`)
}
setMetadata({ ...formattedMetadata, hash })
} catch (e) {
// if metadata is not available or invalid go with the default one
const count = Object.keys(entries).length
setMetadata({
hash,
type: count > 1 ? 'folder' : 'unknown',
name: reference,
count,
isWebsite: Boolean(indexDocument && /.*\.html?$/i.test(indexDocument)),
isVideo: Boolean(indexDocument && /.*\.(mp4|webm|ogg|mp3|ogg|wav)$/i.test(indexDocument)),
isImage: Boolean(indexDocument && /.*\.(jpg|jpeg|png|gif|webp|svg)$/i.test(indexDocument)),
// naive assumption based on indexDocument, we don't want to download the whole manifest
})
}
} catch {
setNotFound(true)
enqueueSnackbar('The specified hash does not contain valid content.', { variant: 'error' })
return
}
const entries = await manifestJs.getHashes(reference, { exclude: [META_FILE_NAME] })
setSwarmEntries(entries)
const indexDocument = await manifestJs.getIndexDocumentPath(reference)
setIndexDocument(indexDocument)
try {
const remoteMetadata = await beeApi.downloadFile(reference, META_FILE_NAME)
const formattedMetadata = JSON.parse(remoteMetadata.data.text()) as Metadata
if (formattedMetadata.isVideo || formattedMetadata.isImage) {
setPreview(`${apiUrl}/bzz/${reference}`)
}
setMetadata({ ...formattedMetadata, hash })
} catch (e) {
// if metadata is not available or invalid go with the default one
const count = Object.keys(entries).length
setMetadata({
hash,
type: count > 1 ? 'folder' : 'unknown',
name: reference,
count,
isWebsite: Boolean(indexDocument && /.*\.html?$/i.test(indexDocument)),
isVideo: Boolean(indexDocument && /.*\.(mp4|webm|ogg|mp3|ogg|wav)$/i.test(indexDocument)),
isImage: Boolean(indexDocument && /.*\.(jpg|jpeg|png|gif|webp|svg)$/i.test(indexDocument)),
// naive assumption based on indexDocument, we don't want to donwload the whole manifest
})
}
}
function onOpen() {
@@ -119,7 +132,7 @@ export function Share(): ReactElement {
} else {
const zip = new JSZip()
for (const [path, hash] of Object.entries(swarmEntries)) {
zip.file(path, await beeApi.downloadData(hash))
zip.file(path, (await beeApi.downloadData(hash)).toUint8Array())
}
const content = await zip.generateAsync({ type: 'blob' })
saveAs(content, reference + '.zip')
+3 -3
View File
@@ -114,10 +114,10 @@ export function Upload(): ReactElement {
beeApi
.uploadFiles(stamp.batchID, fls, { indexDocument, deferred: true })
.then(hash => {
putHistory(HISTORY_KEYS.UPLOAD_HISTORY, hash.reference, getAssetNameFromFiles(files))
putHistory(HISTORY_KEYS.UPLOAD_HISTORY, hash.reference.toHex(), getAssetNameFromFiles(files))
if (uploadOrigin.origin === 'UPLOAD') {
navigate(ROUTES.HASH.replace(':hash', hash.reference), { replace: true })
navigate(ROUTES.HASH.replace(':hash', hash.reference.toHex()), { replace: true })
} else {
updateFeed(beeApi, identity as Identity, hash.reference, stamp.batchID, password as string).then(() => {
persistIdentity(identities, identity as Identity)
@@ -164,7 +164,7 @@ export function Upload(): ReactElement {
<>
<Box mb={2}>
{hasAnyStamps && stampMode === 'SELECT' ? (
<PostageStampSelector onSelect={stamp => setStamp(stamp)} defaultValue={stamp?.batchID} />
<PostageStampSelector onSelect={stamp => setStamp(stamp)} defaultValue={stamp?.batchID.toHex()} />
) : (
<PostageStampAdvancedCreation onFinished={() => setStampMode('SELECT')} />
)}
+13 -10
View File
@@ -1,4 +1,5 @@
import { Box, Tooltip, Typography } from '@material-ui/core'
import { BZZ, DAI } from '@ethersphere/bee-js'
import { Wallet } from 'ethers'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react'
@@ -11,20 +12,19 @@ import ExpandableListItemKey from '../../components/ExpandableListItemKey'
import { HistoryHeader } from '../../components/HistoryHeader'
import { Loading } from '../../components/Loading'
import { SwarmButton } from '../../components/SwarmButton'
import { Token } from '../../models/Token'
import { Context as BeeContext } from '../../providers/Bee'
import { Context as SettingsContext } from '../../providers/Settings'
import { Context as TopUpContext } from '../../providers/TopUp'
import { Context as BalanceProvider } from '../../providers/WalletBalance'
import { createGiftWallet } from '../../utils/desktop'
import { ResolvedWallet } from '../../utils/wallet'
const GIFT_WALLET_FUND_DAI_AMOUNT = Token.fromDecimal('0.1', 18)
const GIFT_WALLET_FUND_BZZ_AMOUNT = Token.fromDecimal('0.5', 16)
const GIFT_WALLET_FUND_DAI_AMOUNT = DAI.fromDecimalString('0.1')
const GIFT_WALLET_FUND_BZZ_AMOUNT = BZZ.fromDecimalString('0.5')
export default function Index(): ReactElement {
const { giftWallets, addGiftWallet } = useContext(TopUpContext)
const { rpcProvider, desktopUrl } = useContext(SettingsContext)
const { balance } = useContext(BalanceProvider)
const { walletBalance } = useContext(BeeContext)
const [loading, setLoading] = useState(false)
const [balances, setBalances] = useState<ResolvedWallet[]>([])
@@ -67,13 +67,13 @@ export default function Index(): ReactElement {
navigate(-1)
}
if (!balance) {
if (!walletBalance) {
return <Loading />
}
const notEnoughFundsCheck =
balance.dai.toBigNumber.isLessThanOrEqualTo(GIFT_WALLET_FUND_DAI_AMOUNT.toBigNumber) ||
balance.bzz.toBigNumber.isLessThan(GIFT_WALLET_FUND_BZZ_AMOUNT.toBigNumber)
walletBalance.nativeTokenBalance.lte(GIFT_WALLET_FUND_DAI_AMOUNT) ||
walletBalance.bzzBalance.lt(GIFT_WALLET_FUND_BZZ_AMOUNT)
return (
<>
@@ -86,10 +86,13 @@ export default function Index(): ReactElement {
</Typography>
</Box>
<Box mb={0.25}>
<ExpandableListItem label="xDAI balance" value={`${balance.dai.toSignificantDigits(4)} xDAI`} />
<ExpandableListItem
label="xDAI balance"
value={`${walletBalance.nativeTokenBalance.toSignificantDigits(4)} xDAI`}
/>
</Box>
<Box mb={2}>
<ExpandableListItem label="xBZZ balance" value={`${balance.bzz.toSignificantDigits(4)} xBZZ`} />
<ExpandableListItem label="xBZZ balance" value={`${walletBalance.bzzBalance.toSignificantDigits(4)} xBZZ`} />
</Box>
<Box mb={4}>
{balances.map((x, i) => (
+2 -4
View File
@@ -1,3 +1,4 @@
import { BZZ } from '@ethersphere/bee-js'
import { useContext } from 'react'
import { useNavigate } from 'react-router'
import ExchangeFunds from 'remixicon-react/ExchangeFundsLineIcon'
@@ -9,10 +10,7 @@ export function ChequebookInfoCard() {
const { chequebookBalance } = useContext(BeeContext)
const navigate = useNavigate()
if (
chequebookBalance?.availableBalance !== undefined &&
chequebookBalance?.availableBalance.toBigNumber.isGreaterThan(0)
) {
if (chequebookBalance?.availableBalance !== undefined && chequebookBalance?.availableBalance.gt(BZZ.fromPLUR('0'))) {
return (
<Card
buttonProps={{
+5 -8
View File
@@ -4,21 +4,18 @@ import Upload from 'remixicon-react/UploadLineIcon'
import Wallet from 'remixicon-react/Wallet3LineIcon'
import Card from '../../components/Card'
import { Context as BeeContext } from '../../providers/Bee'
import { Context as BalanceProvider } from '../../providers/WalletBalance'
import { ROUTES } from '../../routes'
export function WalletInfoCard() {
const { nodeInfo } = useContext(BeeContext)
const { balance, error } = useContext(BalanceProvider)
const { nodeInfo, walletBalance } = useContext(BeeContext)
const navigate = useNavigate()
let balanceText = 'Loading...'
if (error) {
balanceText = 'Could not load...'
console.error(error) // eslint-disable-line
} else if (balance) {
balanceText = `${balance.bzz.toSignificantDigits(4)} xBZZ | ${balance.dai.toSignificantDigits(4)} xDAI`
if (walletBalance) {
balanceText = `${walletBalance.bzzBalance.toSignificantDigits(
4,
)} xBZZ | ${walletBalance.nativeTokenBalance.toSignificantDigits(4)} xDAI`
}
if (nodeInfo?.beeMode && ['light', 'full', 'dev'].includes(nodeInfo.beeMode)) {
+4 -2
View File
@@ -13,7 +13,7 @@ import NodeInfoCard from './NodeInfoCard'
import { WalletInfoCard } from './WalletInfoCard'
export default function Status(): ReactElement {
const { beeVersion, status, topology, nodeInfo, chainId } = useContext(BeeContext)
const { beeVersion, status, topology, nodeInfo, walletBalance } = useContext(BeeContext)
const { isDesktop, desktopUrl } = useContext(SettingsContext)
const { beeDesktopVersion } = useBeeDesktop(isDesktop, desktopUrl)
const { newBeeDesktopVersion } = useNewBeeDesktopVersion(isDesktop, desktopUrl, false)
@@ -64,7 +64,9 @@ export default function Status(): ReactElement {
)}
<ExpandableListItem label="Bee version" value={beeVersion} />
<ExpandableListItem label="Mode" value={nodeInfo?.beeMode} />
{chainId !== null && <ExpandableListItem label="Blockchain network" value={chainIdToName(chainId)} />}
{walletBalance !== null && (
<ExpandableListItem label="Blockchain network" value={chainIdToName(walletBalance.chainID)} />
)}
</div>
)
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { BeeModes } from '@ethersphere/bee-js'
import { Box, Grid, Typography } from '@material-ui/core'
import { BeeModes } from '@ethersphere/bee-js'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect } from 'react'
import { useNavigate } from 'react-router'
+1 -1
View File
@@ -9,7 +9,7 @@ interface Props {
}
export function PostageStamp({ stamp, shorten }: Props): ReactElement {
const batchId = shorten ? stamp.batchID.slice(0, 8) : stamp.batchID
const batchId = shorten ? stamp.batchID.toHex().slice(0, 8) : stamp.batchID
const label = `${batchId}${stamp.label ? ` - ${stamp.label}` : ''}`
return (
@@ -1,5 +1,5 @@
import { PostageBatchOptions, Utils } from '@ethersphere/bee-js'
import { Box, Grid, IconButton, Typography, createStyles, makeStyles } from '@material-ui/core'
import { PostageBatchOptions, Utils } from '@ethersphere/bee-js'
import BigNumber from 'bignumber.js'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react'
@@ -13,7 +13,7 @@ import { Context as BeeContext } from '../../providers/Bee'
import { Context as SettingsContext } from '../../providers/Settings'
import { Context as StampsContext } from '../../providers/Stamps'
import { ROUTES } from '../../routes'
import { calculateStampPrice, convertAmountToSeconds, secondsToTimeString, waitUntilStampExists } from '../../utils'
import { secondsToTimeString } from '../../utils'
import { getHumanReadableFileSize } from '../../utils/file'
interface Props {
@@ -61,18 +61,22 @@ export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElemen
const { enqueueSnackbar } = useSnackbar()
function getTtl(amount: number): string {
function getTtl(amount: bigint): string {
const isCurrentPriceAvailable = chainState && chainState.currentPrice
if (amount <= 0 || !isCurrentPriceAvailable) {
return '-'
}
const pricePerBlock = Number.parseInt(chainState.currentPrice, 10)
const pricePerBlock = chainState.currentPrice
return `${secondsToTimeString(
convertAmountToSeconds(amount, pricePerBlock),
)} (with price of ${pricePerBlock.toFixed(0)} PLUR per block)`
try {
return `${secondsToTimeString(
Utils.getStampDuration(amount, pricePerBlock, 5).toSeconds(),
)} (with price of ${pricePerBlock} PLUR per block)`
} catch {
return `0 seconds (with price of ${pricePerBlock} PLUR per block)`
}
}
function getPrice(depth: number, amount: bigint): string {
@@ -82,9 +86,9 @@ export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElemen
return '-'
}
const price = calculateStampPrice(depth, amount)
const price = Utils.getStampCost(depth, amount)
return `${price.toSignificantDigits()} xBZZ`
return `${price.toSignificantDigits(4)} xBZZ`
}
async function submit() {
@@ -107,8 +111,7 @@ export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElemen
immutableFlag: immutable,
}
const batchId = await beeApi.createPostageBatch(amount.toString(), depth, options)
await waitUntilStampExists(batchId, beeApi)
await beeApi.createPostageBatch(amount.toString(), depth, options)
await refresh()
onFinished()
} catch (e) {
@@ -173,7 +176,7 @@ export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElemen
return '-'
}
const theoreticalMaximumVolume = getHumanReadableFileSize(Utils.getStampMaximumCapacityBytes(depth))
const theoreticalMaximumVolume = getHumanReadableFileSize(Utils.getStampTheoreticalBytes(depth))
const effectiveVolume = getHumanReadableFileSize(Utils.getStampEffectiveBytes(depth))
return (
@@ -227,7 +230,7 @@ export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElemen
<Box mt={0.25} sx={{ bgcolor: '#f6f6f6' }} p={2}>
<Grid container justifyContent="space-between">
<Typography>Corresponding TTL (Time to live)</Typography>
<Typography>{!amountError && amountInput ? getTtl(Number.parseInt(amountInput, 10)) : '-'}</Typography>
<Typography>{!amountError && amountInput ? getTtl(BigInt(amountInput)) : '-'}</Typography>
</Grid>
</Box>
{amountError && <Typography>{amountError}</Typography>}
+3 -3
View File
@@ -14,7 +14,7 @@ export function PostageStampSelector({ onSelect, defaultValue }: Props): ReactEl
if (!stamps) {
return
}
const stamp = stamps.find(x => x.batchID === stampId)
const stamp = stamps.find(x => x.batchID.toHex() === stampId)
if (stamp) {
onSelect(stamp)
@@ -24,8 +24,8 @@ export function PostageStampSelector({ onSelect, defaultValue }: Props): ReactEl
return (
<SwarmSelect
options={(stamps || []).map(x => ({
label: x.label ? x.batchID.slice(0, 8) + ' - ' + x.label : x.batchID.slice(0, 8),
value: x.batchID,
label: x.label ? x.batchID.toHex().slice(0, 8) + ' - ' + x.label : x.batchID.toHex().slice(0, 8),
value: x.batchID.toHex(),
}))}
onChange={event => onChange(event.target.value as string)}
defaultValue={defaultValue}
@@ -1,6 +1,6 @@
import { PostageBatchOptions, Utils } from '@ethersphere/bee-js'
import { Box, Button, Grid, Slider, Typography } from '@material-ui/core'
import { Theme, createStyles, makeStyles } from '@material-ui/core/styles'
import { Duration, PostageBatchOptions, Size, Utils } from '@ethersphere/bee-js'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react'
import { Link } from 'react-router-dom'
@@ -10,7 +10,7 @@ import { SwarmTextInput } from '../../components/SwarmTextInput'
import { Context as SettingsContext } from '../../providers/Settings'
import { Context as StampsContext } from '../../providers/Stamps'
import { ROUTES } from '../../routes'
import { calculateStampPrice, convertAmountToSeconds, secondsToTimeString, waitUntilStampExists } from '../../utils'
import { secondsToTimeString } from '../../utils'
interface Props {
onFinished: () => void
@@ -46,8 +46,8 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
const { refresh } = useContext(StampsContext)
const { beeApi } = useContext(SettingsContext)
const [depthInput, setDepthInput] = useState<number>(Utils.getDepthForCapacity(4))
const [amountInput, setAmountInput] = useState<string>(Utils.getAmountForTtl(30))
const [depthInput, setDepthInput] = useState<number>(Utils.getDepthForSize(Size.fromGigabytes(4)))
const [amountInput, setAmountInput] = useState<bigint>(Utils.getAmountForDuration(Duration.fromDays(30), 26500, 5))
const [labelInput, setLabelInput] = useState('')
const [submitting, setSubmitting] = useState(false)
const [buttonValue, setButtonValue] = useState(4)
@@ -56,24 +56,24 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
if (typeof newValue !== 'number') {
return
}
const amountValue = Utils.getAmountForTtl(newValue)
const amountValue = Utils.getAmountForDuration(Duration.fromDays(newValue), 26500, 5)
setAmountInput(amountValue)
}
const { enqueueSnackbar } = useSnackbar()
function getTtl(amount: string): string {
function getTtl(amount: bigint): string {
const pricePerBlock = 24000
return `${secondsToTimeString(
convertAmountToSeconds(parseInt(amount, 10), pricePerBlock),
)} (with price of ${pricePerBlock.toFixed(0)} PLUR per block)`
Utils.getStampDuration(amount, pricePerBlock, 5).toSeconds(),
)} (with price of ${pricePerBlock} PLUR per block)`
}
function getPrice(depth: number, amount: bigint): string {
const price = calculateStampPrice(depth, amount)
const price = Utils.getStampCost(depth, amount)
return `${price.toSignificantDigits()} xBZZ`
return `${price.toSignificantDigits(4)} xBZZ`
}
async function submit() {
@@ -96,8 +96,7 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
immutableFlag: true,
}
const batchId = await beeApi.createPostageBatch(amount.toString(), depth, options)
await waitUntilStampExists(batchId, beeApi)
await beeApi.createPostageBatch(amount.toString(), depth, options)
await refresh()
onFinished()
} catch (e) {
@@ -109,7 +108,7 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
function handleBatchSize(gigabytes: number) {
setButtonValue(gigabytes)
const capacity = Utils.getDepthForCapacity(gigabytes)
const capacity = Utils.getDepthForSize(Size.fromGigabytes(gigabytes))
setDepthInput(capacity)
}
+3 -3
View File
@@ -4,7 +4,7 @@ import Dialog from '@material-ui/core/Dialog'
import DialogContent from '@material-ui/core/DialogContent'
import DialogTitle from '@material-ui/core/DialogTitle'
import { Check, Clear } from '@material-ui/icons'
import React, { ReactElement, useState } from 'react'
import { ReactElement, useState } from 'react'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import { SwarmSelect } from '../../components/SwarmSelect'
import { EnrichedPostageBatch } from '../../providers/Stamps'
@@ -39,7 +39,7 @@ export function SelectPostageStampModal({ stamps, onSelect, onClose }: Props): R
const classes = useStyles()
function onChange(stampId: string) {
const stamp = stamps.find(x => x.batchID === stampId)
const stamp = stamps.find(x => x.batchID.toHex() === stampId)
if (stamp) {
setSelectedStamp(stamp)
@@ -66,7 +66,7 @@ export function SelectPostageStampModal({ stamps, onSelect, onClose }: Props): R
</DialogTitle>
<DialogContent>
<SwarmSelect
options={stamps.map(x => ({ label: x.batchID, value: x.batchID }))}
options={stamps.map(x => ({ label: x.batchID.toHex(), value: x.batchID.toHex() }))}
onChange={event => onChange(event.target.value as string)}
/>
</DialogContent>
+3 -7
View File
@@ -28,10 +28,10 @@ function StampsTable({ postageStamps }: Props): ReactElement | null {
<ExpandableList label="Postage Stamps" defaultOpen>
{postageStamps.map(stamp => (
<ExpandableElement
key={stamp.batchID}
key={stamp.batchID.toHex()}
expandable={
<>
<ExpandableListItemKey label="Batch ID" value={stamp.batchID} />
<ExpandableListItemKey label="Batch ID" value={stamp.batchID.toHex()} />
<ExpandableListItem label="Depth" value={String(stamp.depth)} />
<ExpandableListItem
label="Capacity"
@@ -40,13 +40,9 @@ function StampsTable({ postageStamps }: Props): ReactElement | null {
)}`}
/>
<ExpandableListItem label="Amount" value={parseInt(stamp.amount, 10).toLocaleString()} />
<ExpandableListItem
label="Expires in"
value={stamp.batchTTL === -1 ? 'does not expire' : `${secondsToTimeString(stamp.batchTTL)}`}
/>
<ExpandableListItem label="Expires in" value={secondsToTimeString(stamp.duration.toSeconds())} />
<ExpandableListItem label="Label" value={stamp.label} />
<ExpandableListItem label="Usable" value={stamp.usable ? 'yes' : 'no'} />
<ExpandableListItem label="Exists" value={stamp.exists ? 'yes' : 'no'} />
<ExpandableListItem label="Immutable" value={stamp.immutableFlag ? 'yes' : 'no'} />
<ExpandableListItem label="Purchase Block Number" value={stamp.blockNumber} />
<ExpandableListItemActions>
+10 -9
View File
@@ -1,4 +1,5 @@
import { Box, Grid, Typography } from '@material-ui/core'
import { DAI } from '@ethersphere/bee-js'
import { ReactElement, useContext } from 'react'
import { useNavigate } from 'react-router'
import Check from 'remixicon-react/CheckLineIcon'
@@ -9,10 +10,9 @@ import { Loading } from '../../components/Loading'
import { SwarmButton } from '../../components/SwarmButton'
import { SwarmDivider } from '../../components/SwarmDivider'
import { Context } from '../../providers/Bee'
import { Context as BalanceProvider } from '../../providers/WalletBalance'
import { TopUpProgressIndicator } from './TopUpProgressIndicator'
const MINIMUM_XDAI = '0.5'
const MINIMUM_XDAI = DAI.fromDecimalString('0.5')
interface Props {
header: string
@@ -22,15 +22,14 @@ interface Props {
}
export default function Index({ header, title, p, next }: Props): ReactElement {
const { nodeAddresses } = useContext(Context)
const { balance } = useContext(BalanceProvider)
const { nodeAddresses, walletBalance } = useContext(Context)
const navigate = useNavigate()
if (!balance || !nodeAddresses) {
if (!walletBalance || !nodeAddresses) {
return <Loading />
}
const disabled = balance.dai.toDecimal.lt(MINIMUM_XDAI)
const disabled = walletBalance.nativeTokenBalance.lt(MINIMUM_XDAI)
return (
<>
@@ -44,17 +43,19 @@ export default function Index({ header, title, p, next }: Props): ReactElement {
<Box mb={4}>{p}</Box>
<SwarmDivider mb={4} />
<Box mb={0.25}>
<ExpandableListItemKey label="Funding wallet address" value={nodeAddresses.ethereum} expanded />
<ExpandableListItemKey label="Funding wallet address" value={nodeAddresses.ethereum.toChecksum()} expanded />
</Box>
<Box mb={4}>
<ExpandableListItem label="xDAI balance" value={balance.dai.toSignificantDigits(4)} />
<ExpandableListItem label="xDAI balance" value={walletBalance.nativeTokenBalance.toSignificantDigits(4)} />
</Box>
<Grid container direction="row" justifyContent="space-between">
<SwarmButton iconType={Check} onClick={() => navigate(next)} disabled={disabled}>
Proceed
</SwarmButton>
{disabled ? (
<Typography>Please deposit at least {MINIMUM_XDAI} xDAI to the address above in order to proceed.</Typography>
<Typography>
Please deposit at least {MINIMUM_XDAI.toSignificantDigits(4)} xDAI to the address above in order to proceed.
</Typography>
) : null}
</Grid>
</>
+13 -8
View File
@@ -1,5 +1,5 @@
import { BeeModes } from '@ethersphere/bee-js'
import { Box, Typography } from '@material-ui/core'
import { BeeModes } from '@ethersphere/bee-js'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router'
@@ -14,16 +14,14 @@ import { SwarmButton } from '../../components/SwarmButton'
import { SwarmDivider } from '../../components/SwarmDivider'
import { Context as BeeContext } from '../../providers/Bee'
import { Context as SettingsContext } from '../../providers/Settings'
import { Context as BalanceProvider } from '../../providers/WalletBalance'
import { ROUTES } from '../../routes'
import { sleepMs } from '../../utils'
import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop'
import { ResolvedWallet } from '../../utils/wallet'
export function GiftCardFund(): ReactElement {
const { nodeAddresses, nodeInfo } = useContext(BeeContext)
const { nodeAddresses, nodeInfo, walletBalance } = useContext(BeeContext)
const { isDesktop, desktopUrl, rpcProvider, rpcProviderUrl } = useContext(SettingsContext)
const { balance } = useContext(BalanceProvider)
const [loading, setLoading] = useState(false)
const [wallet, setWallet] = useState<ResolvedWallet | null>(null)
@@ -41,7 +39,7 @@ export function GiftCardFund(): ReactElement {
ResolvedWallet.make(privateKeyString, rpcProvider).then(setWallet)
}, [privateKeyString, rpcProvider])
if (!wallet || !balance) {
if (!wallet || !walletBalance) {
return <Loading />
}
@@ -108,13 +106,20 @@ export function GiftCardFund(): ReactElement {
<ArrowDown size={24} color="#aaaaaa" />
</Box>
<Box mb={0.25}>
<ExpandableListItemKey label="Node wallet address" value={nodeAddresses?.ethereum || 'N/A'} expanded />
<ExpandableListItemKey
label="Node wallet address"
value={nodeAddresses?.ethereum.toChecksum() || 'N/A'}
expanded
/>
</Box>
<Box mb={0.25}>
<ExpandableListItem label="xDAI balance" value={`${balance.dai.toSignificantDigits(4)} xDAI`} />
<ExpandableListItem
label="xDAI balance"
value={`${walletBalance.nativeTokenBalance.toSignificantDigits(4)} xDAI`}
/>
</Box>
<Box mb={2}>
<ExpandableListItem label="xBZZ balance" value={`${balance.bzz.toSignificantDigits(4)} xBZZ`} />
<ExpandableListItem label="xBZZ balance" value={`${walletBalance.bzzBalance.toSignificantDigits(4)} xBZZ`} />
</Box>
<SwarmButton iconType={Check} onClick={onFund} disabled={loading} loading={loading}>
{canUpgradeToLightNode ? 'Send all funds to your node and Upgrade' : 'Send all funds to your node'}
+6 -7
View File
@@ -1,17 +1,16 @@
import { Box, Typography } from '@material-ui/core'
import { BZZ, DAI } from '@ethersphere/bee-js'
import { Wallet } from 'ethers'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react'
import ArrowRight from 'remixicon-react/ArrowRightLineIcon'
import { useNavigate } from 'react-router'
import { Context as SettingsContext } from '../../providers/Settings'
import ArrowRight from 'remixicon-react/ArrowRightLineIcon'
import { HistoryHeader } from '../../components/HistoryHeader'
import { ProgressIndicator } from '../../components/ProgressIndicator'
import { SwarmButton } from '../../components/SwarmButton'
import { SwarmDivider } from '../../components/SwarmDivider'
import { SwarmTextInput } from '../../components/SwarmTextInput'
import { BzzToken } from '../../models/BzzToken'
import { DaiToken } from '../../models/DaiToken'
import { Context as SettingsContext } from '../../providers/Settings'
import { ROUTES } from '../../routes'
import { Rpc } from '../../utils/rpc'
@@ -29,10 +28,10 @@ export function GiftCardTopUpIndex(): ReactElement {
setLoading(true)
try {
const wallet = new Wallet(giftCode, rpcProvider)
const dai = new DaiToken(await Rpc._eth_getBalance(wallet.address, rpcProvider))
const bzz = new BzzToken(await Rpc._eth_getBalanceERC20(wallet.address, rpcProvider))
const dai = await Rpc._eth_getBalance(wallet.address, rpcProvider)
const bzz = await Rpc._eth_getBalanceERC20(wallet.address, rpcProvider)
if (dai.toDecimal.lt(0.001) || bzz.toDecimal.lt(0.001)) {
if (dai.lt(DAI.fromDecimalString('0.001')) || bzz.lt(BZZ.fromDecimalString('0.001'))) {
throw Error('Gift wallet does not have enough funds')
}
enqueueSnackbar('Successfully verified gift wallet', { variant: 'success' })
+38 -43
View File
@@ -1,5 +1,5 @@
import { BeeModes } from '@ethersphere/bee-js'
import { Box, Typography } from '@material-ui/core'
import { BeeModes, BZZ, DAI } from '@ethersphere/bee-js'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react'
import { useNavigate } from 'react-router'
@@ -13,14 +13,11 @@ import { Loading } from '../../components/Loading'
import { SwarmButton } from '../../components/SwarmButton'
import { SwarmDivider } from '../../components/SwarmDivider'
import { SwarmTextInput } from '../../components/SwarmTextInput'
import { BZZ_DECIMAL_PLACES, BzzToken } from '../../models/BzzToken'
import { DaiToken } from '../../models/DaiToken'
import { Context as BeeContext } from '../../providers/Bee'
import { Context as SettingsContext } from '../../providers/Settings'
import { Context as BalanceProvider } from '../../providers/WalletBalance'
import { ROUTES } from '../../routes'
import { sleepMs } from '../../utils'
import { SwapError, isSwapError, wrapWithSwapError } from '../../utils/SwapError'
import { isSwapError, SwapError, wrapWithSwapError } from '../../utils/SwapError'
import {
getBzzPriceAsDai,
getDesktopConfiguration,
@@ -31,8 +28,8 @@ import {
import { Rpc } from '../../utils/rpc'
import { TopUpProgressIndicator } from './TopUpProgressIndicator'
const MINIMUM_XDAI = '0.1'
const MINIMUM_XBZZ = '0.1'
const MINIMUM_XDAI = DAI.fromDecimalString('0.1')
const MINIMUM_XBZZ = BZZ.fromDecimalString('0.1')
const GENERIC_SWAP_FAILED_ERROR_MESSAGE = 'Failed to swap. The full error is printed to the console.'
@@ -44,15 +41,14 @@ export function Swap({ header }: Props): ReactElement {
const [loading, setLoading] = useState(false)
const [hasSwapped, setSwapped] = useState(false)
const [userInputSwap, setUserInputSwap] = useState<string | null>(null)
const [price, setPrice] = useState(DaiToken.fromDecimal('0.6'))
const [price, setPrice] = useState(DAI.fromDecimalString('0.3'))
const [error, setError] = useState<string | null>(null)
const [daiToSwap, setDaiToSwap] = useState<DaiToken | null>(null)
const [bzzAfterSwap, setBzzAfterSwap] = useState<BzzToken | null>(null)
const [daiAfterSwap, setDaiAfterSwap] = useState<DaiToken | null>(null)
const [daiToSwap, setDaiToSwap] = useState<DAI | null>(null)
const [bzzAfterSwap, setBzzAfterSwap] = useState<BZZ | null>(null)
const [daiAfterSwap, setDaiAfterSwap] = useState<DAI | null>(null)
const { rpcProviderUrl, isDesktop, desktopUrl } = useContext(SettingsContext)
const { nodeAddresses, nodeInfo } = useContext(BeeContext)
const { balance } = useContext(BalanceProvider)
const { nodeAddresses, nodeInfo, walletBalance } = useContext(BeeContext)
const navigate = useNavigate()
const { enqueueSnackbar } = useSnackbar()
@@ -65,30 +61,30 @@ export function Swap({ header }: Props): ReactElement {
// Set the initial xDAI to swap
useEffect(() => {
if (!balance || userInputSwap) {
if (!walletBalance || userInputSwap) {
return
}
const minimumOptimalValue = DaiToken.fromDecimal('1').plusBaseUnits(MINIMUM_XDAI).toDecimal
const minimumOptimalValue = DAI.fromDecimalString('1').plus(MINIMUM_XDAI)
if (balance.dai.toDecimal.isGreaterThanOrEqualTo(minimumOptimalValue)) {
if (walletBalance.nativeTokenBalance.gte(minimumOptimalValue)) {
// Balance has at least 1 + MINIMUM_XDAI xDai
setDaiToSwap(balance.dai.minusBaseUnits('1'))
setDaiToSwap(walletBalance.nativeTokenBalance.minus(DAI.fromDecimalString('1')))
} else {
// Balance is low, halve the amount
setDaiToSwap(new DaiToken(balance.dai.toBigNumber.dividedToIntegerBy(2)))
setDaiToSwap(walletBalance.nativeTokenBalance.divide(BigInt(2)))
}
}, [balance, userInputSwap])
}, [walletBalance, userInputSwap])
// Set the xDAI to swap based on user input
useEffect(() => {
setError(null)
try {
if (userInputSwap) {
const dai = DaiToken.fromDecimal(userInputSwap)
const dai = DAI.fromDecimalString(userInputSwap)
setDaiToSwap(dai)
if (dai.toDecimal.lte(0)) {
if (dai.lte(DAI.fromDecimalString('0'))) {
setError('xDAI to swap must be a positive number')
}
}
@@ -99,25 +95,23 @@ export function Swap({ header }: Props): ReactElement {
// Calculate the amount of tokens after swap
useEffect(() => {
if (!balance || !daiToSwap || error) {
if (!walletBalance || !daiToSwap || error) {
return
}
const daiAfterSwap = new DaiToken(balance.dai.toBigNumber.minus(daiToSwap.toBigNumber))
const daiAfterSwap = walletBalance.nativeTokenBalance.minus(daiToSwap)
setDaiAfterSwap(daiAfterSwap)
const tokensConverted = BzzToken.fromDecimal(
daiToSwap.toBigNumber.dividedBy(price.toBigNumber).decimalPlaces(BZZ_DECIMAL_PLACES),
)
const bzzAfterSwap = new BzzToken(tokensConverted.toBigNumber.plus(balance.bzz.toBigNumber))
const tokensConverted = daiToSwap.exchangeToBZZ(price)
const bzzAfterSwap = tokensConverted.plus(walletBalance.bzzBalance)
setBzzAfterSwap(bzzAfterSwap)
if (daiAfterSwap.toDecimal.lt(MINIMUM_XDAI)) {
setError(`Must keep at least ${MINIMUM_XDAI} xDAI after swap!`)
} else if (bzzAfterSwap.toDecimal.lt(MINIMUM_XBZZ)) {
setError(`Must have at least ${MINIMUM_XBZZ} xBZZ after swap!`)
if (daiAfterSwap.lt(MINIMUM_XDAI)) {
setError(`Must keep at least ${MINIMUM_XDAI.toSignificantDigits(4)} xDAI after swap!`)
} else if (bzzAfterSwap.lt(MINIMUM_XBZZ)) {
setError(`Must have at least ${MINIMUM_XBZZ.toSignificantDigits(4)} xBZZ after swap!`)
}
}, [error, balance, daiToSwap, price])
}, [error, walletBalance, daiToSwap, price])
if (!balance || !nodeAddresses || !daiToSwap || !bzzAfterSwap || !daiAfterSwap) {
if (!walletBalance || !nodeAddresses || !daiToSwap || !bzzAfterSwap || !daiAfterSwap) {
return <Loading />
}
@@ -135,9 +129,9 @@ export function Swap({ header }: Props): ReactElement {
}
}
async function sendSwapRequest(daiToSwap: DaiToken) {
async function sendSwapRequest(daiToSwap: DAI) {
try {
await performSwap(desktopUrl, daiToSwap.toString)
await performSwap(desktopUrl, daiToSwap)
} catch (error) {
// eslint-disable-next-line no-console
console.error(error)
@@ -145,7 +139,7 @@ export function Swap({ header }: Props): ReactElement {
}
}
async function performSwapWithChecks(daiToSwap: DaiToken) {
async function performSwapWithChecks(daiToSwap: DAI) {
if (!localStorage.getItem('apiKey')) {
throw new SwapError('API key is not set, reopen dashboard through Swarm Desktop')
}
@@ -186,7 +180,9 @@ export function Swap({ header }: Props): ReactElement {
: 'Successfully swapped. Balances will refresh soon. You may now navigate away.'
enqueueSnackbar(message, { variant: 'success' })
if (canUpgradeToLightNode) await restart()
if (canUpgradeToLightNode) {
await restart()
}
} catch (error) {
if (isSwapError(error)) {
// we have a custom and user friendly error message
@@ -201,7 +197,6 @@ export function Swap({ header }: Props): ReactElement {
console.error(error) // eslint-disable-line
}
} finally {
balance?.refresh()
setLoading(false)
}
}
@@ -217,15 +212,15 @@ export function Swap({ header }: Props): ReactElement {
</Box>
<Box mb={4}>
<Typography>
You need to swap xDAI to xBZZ in order to use Swarm. Make sure to keep at least {MINIMUM_XDAI} xDAI in order
to pay for transaction costs on the network.
You need to swap xDAI to xBZZ in order to use Swarm. Make sure to keep at least{' '}
{MINIMUM_XDAI.toSignificantDigits(4)} xDAI in order to pay for transaction costs on the network.
</Typography>
</Box>
<SwarmDivider mb={4} />
<Box mb={4}>
<Typography>
Your current balance is {balance.dai.toSignificantDigits(4)} xDAI and {balance.bzz.toSignificantDigits(4)}{' '}
xBZZ.
Your current balance is {walletBalance.nativeTokenBalance.toSignificantDigits(4)} xDAI and{' '}
{walletBalance.bzzBalance.toSignificantDigits(4)} xBZZ.
</Typography>
</Box>
<Box mb={4}>
@@ -242,7 +237,7 @@ export function Swap({ header }: Props): ReactElement {
<ArrowDown size={24} color="#aaaaaa" />
</Box>
<Box mb={0.25}>
<ExpandableListItemKey label="Funding wallet address" value={nodeAddresses.ethereum} expanded />
<ExpandableListItemKey label="Funding wallet address" value={nodeAddresses.ethereum.toChecksum()} expanded />
</Box>
<Box mb={0.25}>
<ExpandableListItem
+7 -9
View File
@@ -1,5 +1,5 @@
import { BeeModes } from '@ethersphere/bee-js'
import { Box, createStyles, Grid, makeStyles, Typography } from '@material-ui/core'
import { BeeModes, BZZ, DAI } from '@ethersphere/bee-js'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react'
import { useNavigate } from 'react-router'
@@ -15,7 +15,6 @@ import { SwarmButton } from '../../components/SwarmButton'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { Context as BeeContext, CheckState } from '../../providers/Bee'
import { Context as SettingsContext } from '../../providers/Settings'
import { Context as BalanceProvider } from '../../providers/WalletBalance'
import { ROUTES } from '../../routes'
import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop'
@@ -33,15 +32,14 @@ const useStyles = makeStyles(() =>
}),
)
const MINIMUM_XDAI = '0.05'
const MINIMUM_XBZZ = '0.1'
const MINIMUM_XDAI = DAI.fromDecimalString('0.05')
const MINIMUM_XBZZ = BZZ.fromDecimalString('0.1')
export default function TopUp(): ReactElement {
const navigate = useNavigate()
const styles = useStyles()
const { isDesktop, desktopUrl } = useContext(SettingsContext)
const { nodeInfo, status } = useContext(BeeContext)
const { balance } = useContext(BalanceProvider)
const { nodeInfo, status, walletBalance } = useContext(BeeContext)
const { rpcProviderUrl } = useContext(SettingsContext)
const [loading, setLoading] = useState(false)
const { enqueueSnackbar } = useSnackbar()
@@ -49,8 +47,8 @@ export default function TopUp(): ReactElement {
const canUpgradeToLightNode =
isDesktop &&
nodeInfo?.beeMode === BeeModes.ULTRA_LIGHT &&
balance?.dai.toDecimal.gte(MINIMUM_XDAI) &&
balance?.bzz.toDecimal.gte(MINIMUM_XBZZ)
walletBalance?.nativeTokenBalance.gte(MINIMUM_XDAI) &&
walletBalance?.bzzBalance.gte(MINIMUM_XBZZ)
async function restart() {
setLoading(true)
@@ -67,7 +65,7 @@ export default function TopUp(): ReactElement {
if (status.all === CheckState.ERROR) return <TroubleshootConnectionCard />
if (!balance) {
if (!walletBalance) {
return <Loading />
}
+29 -56
View File
@@ -1,18 +1,20 @@
import {
AllSettlements,
BeeModes,
BZZ,
ChainState,
ChequebookAddressResponse,
ChequebookBalanceResponse,
LastChequesResponse,
NodeAddresses,
NodeInfo,
Peer,
PeerBalance,
Topology,
WalletBalance,
} from '@ethersphere/bee-js'
import { ReactChild, ReactElement, createContext, useContext, useEffect, useState } from 'react'
import { createContext, ReactChild, ReactElement, useContext, useEffect, useState } from 'react'
import { useLatestBeeRelease } from '../hooks/apiHooks'
import { BzzToken } from '../models/BzzToken'
import { Token } from '../models/Token'
import type { Balance, ChequebookBalance, Settlements } from '../types'
import { Context as SettingsContext } from './Settings'
const LAUNCH_GRACE_PERIOD = 15_000
@@ -50,13 +52,13 @@ interface ContextInterface {
topology: Topology | null
chequebookAddress: ChequebookAddressResponse | null
peers: Peer[] | null
chequebookBalance: ChequebookBalance | null
stake: BzzToken | null
peerBalances: Balance[] | null
chequebookBalance: ChequebookBalanceResponse | null
stake: BZZ | null
peerBalances: PeerBalance[] | null
peerCheques: LastChequesResponse | null
settlements: Settlements | null
settlements: AllSettlements | null
chainState: ChainState | null
chainId: number | null
walletBalance: WalletBalance | null
latestBeeRelease: LatestBeeRelease | null
isLoading: boolean
lastUpdate: number | null
@@ -86,7 +88,7 @@ const initialValues: ContextInterface = {
peerCheques: null,
settlements: null,
chainState: null,
chainId: null,
walletBalance: null,
latestBeeRelease: null,
isLoading: true,
lastUpdate: null,
@@ -107,7 +109,7 @@ function getStatus(
apiHealth: boolean,
topology: Topology | null,
chequebookAddress: ChequebookAddressResponse | null,
chequebookBalance: ChequebookBalance | null,
chequebookBalance: ChequebookBalanceResponse | null,
error: Error | null,
startedAt: number,
): Status {
@@ -173,13 +175,13 @@ export function Provider({ children }: Props): ReactElement {
const [topology, setNodeTopology] = useState<Topology | null>(null)
const [chequebookAddress, setChequebookAddress] = useState<ChequebookAddressResponse | null>(null)
const [peers, setPeers] = useState<Peer[] | null>(null)
const [chequebookBalance, setChequebookBalance] = useState<ChequebookBalance | null>(null)
const [stake, setStake] = useState<BzzToken | null>(null)
const [peerBalances, setPeerBalances] = useState<Balance[] | null>(null)
const [chequebookBalance, setChequebookBalance] = useState<ChequebookBalanceResponse | null>(null)
const [stake, setStake] = useState<BZZ | null>(null)
const [peerBalances, setPeerBalances] = useState<PeerBalance[] | null>(null)
const [peerCheques, setPeerCheques] = useState<LastChequesResponse | null>(null)
const [settlements, setSettlements] = useState<Settlements | null>(null)
const [settlements, setSettlements] = useState<AllSettlements | null>(null)
const [chainState, setChainState] = useState<ChainState | null>(null)
const [chainId, setChainId] = useState<number | null>(null)
const [walletBalance, setWalletBalance] = useState<WalletBalance | null>(null)
const [startedAt] = useState(Date.now())
const { latestBeeRelease } = useLatestBeeRelease()
@@ -232,38 +234,6 @@ export function Provider({ children }: Props): ReactElement {
isRefreshing = true
setError(null)
// Wrap the chequebook balance call to return BZZ values as Token object
const chequeBalanceWrapper = async () => {
const { totalBalance, availableBalance } = await beeApi.getChequebookBalance({ timeout: TIMEOUT })
return {
totalBalance: new Token(totalBalance),
availableBalance: new Token(availableBalance),
}
}
// Wrap the balances call to return BZZ values as Token object
const peerBalanceWrapper = async () => {
const { balances } = await beeApi.getAllBalances({ timeout: TIMEOUT })
return balances.map(({ peer, balance }) => ({ peer, balance: new Token(balance) }))
}
// Wrap the settlements call to return BZZ values as Token object
const settlementsWrapper = async () => {
const { totalReceived, settlements, totalSent } = await beeApi.getAllSettlements({ timeout: TIMEOUT })
return {
totalReceived: new Token(totalReceived),
totalSent: new Token(totalSent),
settlements: settlements.map(({ peer, received, sent }) => ({
peer,
received: new Token(received),
sent: new Token(sent),
})),
}
}
const promises = [
// API health
beeApi
@@ -320,26 +290,29 @@ export function Provider({ children }: Props): ReactElement {
// Wallet
beeApi
.getWalletBalance({ timeout: TIMEOUT })
.then(({ chainID }) => setChainId(chainID))
.catch(() => setChainId(null)),
.then(setWalletBalance)
.catch(() => setWalletBalance(null)),
// Chequebook balance
chequeBalanceWrapper()
beeApi
.getChequebookBalance({ timeout: TIMEOUT })
.then(setChequebookBalance)
.catch(() => setChequebookBalance(null)),
beeApi
.getStake({ timeout: TIMEOUT })
.then(stake => setStake(new BzzToken(stake)))
.then(stake => setStake(stake))
.catch(() => setStake(null)),
// Peer balances
peerBalanceWrapper()
.then(setPeerBalances)
beeApi
.getAllBalances({ timeout: TIMEOUT })
.then(x => setPeerBalances(x.balances))
.catch(() => setPeerBalances(null)),
// Settlements
settlementsWrapper()
beeApi
.getAllSettlements()
.then(setSettlements)
.catch(() => setSettlements(null)),
]
@@ -398,7 +371,7 @@ export function Provider({ children }: Props): ReactElement {
peerCheques,
settlements,
chainState,
chainId,
walletBalance,
latestBeeRelease,
isLoading,
lastUpdate,
+1 -1
View File
@@ -68,7 +68,7 @@ export function Provider({ children }: Props): ReactElement {
setIsLoading(true)
const stamps = await beeApi.getAllPostageBatch()
setStamps(stamps.filter(x => x.exists).map(enrichStamp))
setStamps(stamps.map(enrichStamp))
setLastUpdate(Date.now())
setError(null)
} catch (e) {
-88
View File
@@ -1,88 +0,0 @@
import { createContext, ReactChild, ReactElement, useContext, useEffect, useState } from 'react'
import { Context as SettingsContext } from './Settings'
import { Context as BeeContext } from './Bee'
import { WalletAddress } from '../utils/wallet'
interface ContextInterface {
balance: WalletAddress | null
error: Error | null
isLoading: boolean
lastUpdate: number | null
start: (frequency?: number) => void
stop: () => void
refresh: () => Promise<void>
}
const initialValues: ContextInterface = {
balance: null,
error: null,
isLoading: false,
lastUpdate: null,
start: () => {}, // eslint-disable-line
stop: () => {}, // eslint-disable-line
refresh: () => Promise.reject(),
}
export const Context = createContext<ContextInterface>(initialValues)
export const Consumer = Context.Consumer
interface Props {
children: ReactChild
}
export function Provider({ children }: Props): ReactElement {
const { rpcProvider } = useContext(SettingsContext)
const { nodeAddresses } = useContext(BeeContext)
const [balance, setBalance] = useState<WalletAddress | null>(initialValues.balance)
const [error, setError] = useState<Error | null>(initialValues.error)
const [isLoading, setIsLoading] = useState<boolean>(initialValues.isLoading)
const [lastUpdate, setLastUpdate] = useState<number | null>(initialValues.lastUpdate)
const [frequency, setFrequency] = useState<number | null>(null)
useEffect(() => {
if (nodeAddresses?.ethereum && rpcProvider) {
WalletAddress.make(nodeAddresses.ethereum, rpcProvider).then(setBalance)
} else {
setBalance(null)
}
}, [nodeAddresses, rpcProvider])
const refresh = async () => {
// Don't want to refresh when already refreshing
if (isLoading) return
if (!balance) return
try {
setIsLoading(true)
setBalance(await balance.refresh())
setLastUpdate(Date.now())
} catch (e) {
setError(e as Error)
} finally {
setIsLoading(false)
}
}
const start = (freq = 30000) => setFrequency(freq)
const stop = () => setFrequency(null)
// Start the update loop
useEffect(() => {
refresh()
// Start autorefresh only if the frequency is set
if (frequency) {
const interval = setInterval(refresh, frequency)
return () => clearInterval(interval)
}
}, [frequency]) // eslint-disable-line react-hooks/exhaustive-deps
return (
<Context.Provider value={{ balance, error, isLoading, lastUpdate, start, stop, refresh }}>
{children}
</Context.Provider>
)
}
+4
View File
@@ -3,3 +3,7 @@
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom'
import { TextDecoder, TextEncoder } from 'util'
Object.assign(global, { TextDecoder, TextEncoder })
+2 -23
View File
@@ -1,5 +1,4 @@
import type { NodeAddresses, Topology } from '@ethersphere/bee-js'
import type { Token } from './models/Token'
import { CheckState } from './providers/Bee'
export interface StatusHookCommon {
@@ -12,31 +11,11 @@ export interface StatusNodeVersionHook extends StatusHookCommon {
latestUrl: string
isLatestBeeVersion: boolean
}
export interface StatusEthereumConnectionHook extends StatusHookCommon {
nodeAddresses: NodeAddresses | null
}
export interface StatusTopologyHook extends StatusHookCommon {
topology: Topology | null
}
export interface ChequebookBalance {
totalBalance: Token
availableBalance: Token
}
export interface Balance {
peer: string
balance: Token
}
export interface Settlement {
peer: string
received: Token
sent: Token
}
export interface Settlements {
totalReceived: Token
totalSent: Token
settlements: Settlement[]
}
-5
View File
@@ -1,5 +0,0 @@
import axios from 'axios'
export async function requestBzz(address: string): Promise<void> {
await axios.post(`https://xbzz-faucet.apyos.dev/xbzz/${address}`)
}
+5 -6
View File
@@ -1,7 +1,6 @@
import { DAI } from '@ethersphere/bee-js'
import axios from 'axios'
import { BEE_DESKTOP_LATEST_RELEASE_PAGE_API } from '../constants'
import { DaiToken } from '../models/DaiToken'
import { Token } from '../models/Token'
import { getJson, postJson } from './net'
export interface BeeConfig {
@@ -18,10 +17,10 @@ export interface BeeConfig {
'blockchain-rpc-endpoint'?: string
}
export async function getBzzPriceAsDai(desktopUrl: string): Promise<Token> {
export async function getBzzPriceAsDai(desktopUrl: string): Promise<DAI> {
const response = await axios.get(`${desktopUrl}/price`)
return DaiToken.fromDecimal(response.data)
return DAI.fromDecimalString(response.data)
}
export function upgradeToLightNode(desktopUrl: string, rpcProvider: string): Promise<BeeConfig> {
@@ -53,8 +52,8 @@ export async function createGiftWallet(desktopUrl: string, address: string): Pro
await postJson(`${desktopUrl}/gift-wallet/${address}`)
}
export async function performSwap(desktopUrl: string, daiAmount: string): Promise<void> {
await postJson(`${desktopUrl}/swap`, { dai: daiAmount })
export async function performSwap(desktopUrl: string, daiAmount: DAI): Promise<void> {
await postJson(`${desktopUrl}/swap`, { dai: daiAmount.toWeiString() })
}
export async function getLatestBeeDesktopVersion(): Promise<string> {
+7 -7
View File
@@ -1,4 +1,4 @@
import { BatchId, Bee, Reference } from '@ethersphere/bee-js'
import { BatchId, Bee, NULL_TOPIC, Reference } from '@ethersphere/bee-js'
import { Wallet } from 'ethers'
import { uuidV4, waitUntilStampUsable } from '.'
import { Identity, IdentityType } from '../providers/Feeds'
@@ -80,18 +80,18 @@ async function getWallet(type: IdentityType, data: string, password?: string): P
export async function updateFeed(
beeApi: Bee,
identity: Identity,
hash: string,
stamp: string,
hash: Reference | string,
stamp: BatchId | string,
password?: string,
): Promise<void> {
const wallet = await getWalletFromIdentity(identity, password)
if (!identity.feedHash) {
identity.feedHash = (await beeApi.createFeedManifest(stamp, 'sequence', '00'.repeat(32), wallet.address)).reference
identity.feedHash = (await beeApi.createFeedManifest(stamp, NULL_TOPIC, wallet.address)).toHex()
}
const writer = beeApi.makeFeedWriter('sequence', '00'.repeat(32), wallet.privateKey)
const writer = beeApi.makeFeedWriter(NULL_TOPIC, wallet.privateKey)
await waitUntilStampUsable(stamp as BatchId, beeApi)
await writer.upload(stamp, hash as Reference)
await waitUntilStampUsable(stamp, beeApi)
await writer.upload(stamp, hash)
}
+20 -47
View File
@@ -1,8 +1,6 @@
import { BatchId, Bee, PostageBatch } from '@ethersphere/bee-js'
import { decodeCid } from '@ethersphere/swarm-cid'
import { BatchId, Bee, PostageBatch, Reference } from '@ethersphere/bee-js'
import { BigNumber } from 'bignumber.js'
import { BZZ_LINK_DOMAIN } from '../constants'
import { Token } from '../models/Token'
/**
* Test if value is an integer
@@ -131,13 +129,7 @@ export function extractSwarmCid(s: string): string | undefined {
const cid = matches[1]
try {
const decodeResult = decodeCid(cid)
if (!decodeResult.type) {
return
}
return decodeResult.reference
return new Reference(cid).toHex()
} catch (e) {
return
}
@@ -171,48 +163,36 @@ export function formatEnum(string: string): string {
return (string.charAt(0).toUpperCase() + string.slice(1).toLowerCase()).replaceAll('_', ' ')
}
export function secondsToTimeString(seconds: number): string {
export function secondsToTimeString(seconds: number | bigint): string {
seconds = BigInt(seconds)
let unit = seconds
if (unit < 120) {
return `${seconds} seconds`
}
unit /= 60
unit /= BigInt(60)
if (unit < 120) {
return `${Math.round(unit)} minutes`
return `${unit} minutes`
}
unit /= 60
unit /= BigInt(60)
if (unit < 48) {
return `${Math.round(unit)} hours`
return `${unit} hours`
}
unit /= 24
unit /= BigInt(24)
if (unit < 14) {
return `${Math.round(unit)} days`
return `${unit} days`
}
unit /= 7
unit /= BigInt(7)
if (unit < 52) {
return `${Math.round(unit)} weeks`
return `${unit} weeks`
}
unit /= 52
unit /= BigInt(52)
return `${unit.toFixed(1)} years`
}
export function convertAmountToSeconds(amount: number, pricePerBlock: number): number {
// TODO: blocktime should come directly from the blockchain as it may differ between different networks
const blockTime = 5 // On mainnet there is 5 seconds between blocks
// See https://github.com/ethersphere/bee/blob/66f079930d739182c4c79eb6008784afeeba1096/pkg/debugapi/postage.go#L410-L413
return (amount * blockTime) / pricePerBlock
}
export function calculateStampPrice(depth: number, amount: bigint): Token {
// See https://github.com/ethersphere/bee/blob/66f079930d739182c4c79eb6008784afeeba1096/pkg/debugapi/postage.go#L410-L413
return new Token(amount * BigInt(2 ** depth)) // FIXME: the 2 ** depth should be performed on bigint already
return `${unit} years`
}
export function shortenText(text: string, length = 20, separator = '[…]'): string {
@@ -231,20 +211,11 @@ interface Options {
timeout?: number
}
export function waitUntilStampUsable(batchId: BatchId, bee: Bee, options?: Options): Promise<PostageBatch> {
return waitForStamp(batchId, bee, 'usable', options)
export function waitUntilStampUsable(batchId: BatchId | string, bee: Bee, options?: Options): Promise<PostageBatch> {
return waitForStamp(batchId, bee, options)
}
export function waitUntilStampExists(batchId: BatchId, bee: Bee, options?: Options): Promise<PostageBatch> {
return waitForStamp(batchId, bee, 'exists', options)
}
async function waitForStamp(
batchId: BatchId,
bee: Bee,
field: 'exists' | 'usable',
options?: Options,
): Promise<PostageBatch> {
async function waitForStamp(batchId: BatchId | string, bee: Bee, options?: Options): Promise<PostageBatch> {
const timeout = options?.timeout || DEFAULT_STAMP_USABLE_TIMEOUT
const pollingFrequency = options?.pollingFrequency || DEFAULT_POLLING_FREQUENCY
@@ -252,7 +223,9 @@ async function waitForStamp(
try {
const stamp = await bee.getPostageBatch(batchId)
if (stamp[field]) return stamp
if (stamp.usable) {
return stamp
}
} catch {
// ignore
}
-155
View File
@@ -1,155 +0,0 @@
import { Bee, Utils } from '@ethersphere/bee-js'
import { MantarayNode, MetadataMapping, Reference, loadAllNodes } from 'mantaray-js'
interface ValueNode extends MantarayNode {
getEntry: Reference
}
/**
* The ASCII code of the character `/`.
*
* This prefix of the root node holds metadata such as the index document.
*/
const INDEX_DOCUMENT_FORK_PREFIX = '47'
export class ManifestJs {
private bee: Bee
constructor(bee: Bee) {
this.bee = bee
}
/**
* Tests whether a given Swarm hash is a valid mantaray manifest
*/
public async isManifest(hash: string): Promise<boolean> {
try {
const data = await this.bee.downloadData(hash)
const node = new MantarayNode()
node.deserialize(data)
return true
} catch {
return false
}
}
/**
* Retrieves `website-index-document` from a Swarm hash, or `null` if it is not present
*/
public async getIndexDocumentPath(hash: string): Promise<string | null> {
const metadata = await this.getRootSlashMetadata(hash)
if (!metadata) {
return null
}
return metadata['website-index-document'] || null
}
/**
* Retrieves all paths with the associated hashes from a Swarm manifest
*/
public async getHashes(hash: string, options?: { exclude: string[] }): Promise<Record<string, string>> {
const data = await this.bee.downloadData(hash)
const node = new MantarayNode()
node.deserialize(data)
await loadAllNodes(this.load.bind(this), node)
const result: Record<string, string> = {}
this.extractHashes(result, node)
if (options?.exclude) {
for (const path of options.exclude) {
delete result[path]
}
}
return result
}
/**
* Resolves an arbitrary Swarm feed manifest to its latest update reference.
* @returns `/bzz` root manifest hash, or `Promise<null>` if hash is not a feed manifest
* @throws in case of network errors or bad input
*/
public async resolveFeedManifest(hash: string): Promise<string | null> {
const metadata = await this.getRootSlashMetadata(hash)
if (!metadata) {
return null
}
const owner = metadata['swarm-feed-owner']
const topic = metadata['swarm-feed-topic']
if (!owner || !topic) {
return null
}
const reader = this.bee.makeFeedReader('sequence', topic, owner)
const response = await reader.download()
return response.reference
}
private async getRootSlashMetadata(hash: string): Promise<MetadataMapping | null> {
const data = await this.bee.downloadData(hash)
const node = new MantarayNode()
node.deserialize(data)
if (!node.forks) {
return null
}
const fork = node.forks[INDEX_DOCUMENT_FORK_PREFIX]
if (!fork) {
return null
}
const metadataNode = fork.node
if (!metadataNode.IsWithMetadataType()) {
return null
}
const metadata = metadataNode.getMetadata
if (!metadata) {
return null
}
return metadata
}
private extractHashes(result: Record<string, string>, node: MantarayNode, prefix = ''): void {
if (!node.forks) {
return
}
for (const fork of Object.values(node.forks)) {
const path = prefix + this.bytesToUtf8(fork.prefix)
const childNode = fork.node
if (this.isValueNode(childNode, path)) {
result[path] = Utils.bytesToHex(childNode.getEntry)
}
if (childNode.isEdgeType()) {
this.extractHashes(result, childNode, path)
}
}
}
private load(reference: Uint8Array) {
return this.bee.downloadData(Utils.bytesToHex(reference))
}
private bytesToUtf8(bytes: Uint8Array): string {
return new TextDecoder('utf-8').decode(bytes)
}
private isValueNode(node: MantarayNode, path: string): node is ValueNode {
return !this.isRootSlash(node, path) && node.isValueType() && typeof node.getEntry !== 'undefined'
}
private isRootSlash(node: MantarayNode, path: string): boolean {
return path === '/' && node.IsWithMetadataType()
}
}
+39 -32
View File
@@ -1,6 +1,7 @@
import { debounce } from '@material-ui/core'
import { Contract, providers, Wallet, BigNumber as BN } from 'ethers'
import { bzzABI, BZZ_TOKEN_ADDRESS } from './bzz-abi'
import { BZZ, DAI, EthAddress, PrivateKey } from '@ethersphere/bee-js'
import { BigNumber as BN, Contract, providers, Wallet } from 'ethers'
import { BZZ_TOKEN_ADDRESS, bzzABI } from './bzz-abi'
const NETWORK_ID = 100
@@ -12,13 +13,12 @@ async function getNetworkChainId(url: string): Promise<number> {
return network.chainId
}
async function eth_getBalance(address: string, provider: providers.JsonRpcProvider): Promise<string> {
if (!address.startsWith('0x')) {
address = `0x${address}`
}
const balance = await provider.getBalance(address)
async function eth_getBalance(address: EthAddress | string, provider: providers.JsonRpcProvider): Promise<DAI> {
address = new EthAddress(address)
return balance.toString()
const balance = await provider.getBalance(address.toHex())
return DAI.fromWei(balance.toString())
}
async function eth_getBlockByNumber(provider: providers.JsonRpcProvider): Promise<string> {
@@ -28,17 +28,16 @@ async function eth_getBlockByNumber(provider: providers.JsonRpcProvider): Promis
}
async function eth_getBalanceERC20(
address: string,
address: EthAddress | string,
provider: providers.JsonRpcProvider,
tokenAddress = BZZ_TOKEN_ADDRESS,
): Promise<string> {
if (!address.startsWith('0x')) {
address = `0x${address}`
}
const contract = new Contract(tokenAddress, bzzABI, provider)
const balance = await contract.balanceOf(address)
): Promise<BZZ> {
address = new EthAddress(address)
return balance.toString()
const contract = new Contract(tokenAddress, bzzABI, provider)
const balance = await contract.balanceOf(address.toHex())
return BZZ.fromPLUR(balance.toString())
}
interface TransferResponse {
@@ -47,29 +46,34 @@ interface TransferResponse {
}
export async function estimateNativeTransferTransactionCost(
privateKey: string,
privateKey: PrivateKey | string,
jsonRpcProvider: string,
): Promise<{ gasPrice: BN; totalCost: BN }> {
): Promise<{ gasPrice: DAI; totalCost: DAI }> {
privateKey = new PrivateKey(privateKey)
const signer = await makeReadySigner(privateKey, jsonRpcProvider)
const gasLimit = '21000'
const gasPrice = await signer.getGasPrice()
return { gasPrice, totalCost: gasPrice.mul(gasLimit) }
return { gasPrice: DAI.fromWei(gasPrice.toString()), totalCost: DAI.fromWei(gasPrice.mul(gasLimit).toString()) }
}
export async function sendNativeTransaction(
privateKey: string,
to: string,
value: string,
privateKey: PrivateKey | string,
to: EthAddress | string,
value: DAI,
jsonRpcProvider: string,
externalGasPrice?: BN,
externalGasPrice?: DAI,
): Promise<TransferResponse> {
privateKey = new PrivateKey(privateKey)
to = new EthAddress(to)
const signer = await makeReadySigner(privateKey, jsonRpcProvider)
const gasPrice = externalGasPrice ?? (await signer.getGasPrice())
const gasPrice = externalGasPrice ?? DAI.fromWei((await signer.getGasPrice()).toString())
const transaction = await signer.sendTransaction({
to,
value: BN.from(value),
gasPrice,
to: to.toHex(),
value: BN.from(value.toWeiString()),
gasPrice: BN.from(gasPrice.toWeiString()),
gasLimit: BN.from(21000),
type: 0,
})
@@ -79,11 +83,14 @@ export async function sendNativeTransaction(
}
export async function sendBzzTransaction(
privateKey: string,
to: string,
value: string,
privateKey: PrivateKey | string,
to: EthAddress | string,
value: BZZ,
jsonRpcProvider: string,
): Promise<TransferResponse> {
privateKey = new PrivateKey(privateKey)
to = new EthAddress(to)
const signer = await makeReadySigner(privateKey, jsonRpcProvider)
const gasPrice = await signer.getGasPrice()
const bzz = new Contract(BZZ_TOKEN_ADDRESS, bzzABI, signer)
@@ -93,10 +100,10 @@ export async function sendBzzTransaction(
return { transaction, receipt }
}
async function makeReadySigner(privateKey: string, jsonRpcProvider: string) {
async function makeReadySigner(privateKey: PrivateKey, jsonRpcProvider: string) {
const provider = new providers.JsonRpcProvider(jsonRpcProvider, NETWORK_ID)
await provider.ready
const signer = new Wallet(privateKey, provider)
const signer = new Wallet(privateKey.toUint8Array(), provider)
return signer
}
+12 -36
View File
@@ -1,39 +1,15 @@
import { BZZ, DAI, EthAddress } from '@ethersphere/bee-js'
import { providers, Wallet } from 'ethers'
import { BzzToken } from '../models/BzzToken'
import { DaiToken } from '../models/DaiToken'
import { estimateNativeTransferTransactionCost, Rpc } from './rpc'
export class WalletAddress {
private constructor(
public address: string,
public bzz: BzzToken,
public dai: DaiToken,
public provider: providers.JsonRpcProvider,
) {}
static async make(address: string, provider: providers.JsonRpcProvider): Promise<WalletAddress> {
const bzz = new BzzToken(await Rpc._eth_getBalanceERC20(address, provider))
const dai = new DaiToken(await Rpc._eth_getBalance(address, provider))
return new WalletAddress(address, bzz, dai, provider)
}
public async refresh(): Promise<WalletAddress> {
this.bzz = new BzzToken(await Rpc._eth_getBalanceERC20(this.address, this.provider))
this.dai = new DaiToken(await Rpc._eth_getBalance(this.address, this.provider))
return this
}
}
export class ResolvedWallet {
public address: string
public privateKey: string
private constructor(
public wallet: Wallet,
public bzz: BzzToken,
public dai: DaiToken,
public bzz: BZZ,
public dai: DAI,
public provider: providers.JsonRpcProvider,
) {
this.address = wallet.address
@@ -44,32 +20,32 @@ export class ResolvedWallet {
const wallet =
typeof privateKeyOrWallet === 'string' ? new Wallet(privateKeyOrWallet, provider) : privateKeyOrWallet
const address = wallet.address
const bzz = new BzzToken(await Rpc._eth_getBalanceERC20(address, provider))
const dai = new DaiToken(await Rpc._eth_getBalance(address, provider))
const bzz = await Rpc._eth_getBalanceERC20(address, provider)
const dai = await Rpc._eth_getBalance(address, provider)
return new ResolvedWallet(wallet, bzz, dai, provider)
}
public async refresh(): Promise<ResolvedWallet> {
this.bzz = new BzzToken(await Rpc._eth_getBalanceERC20(this.address, this.provider))
this.dai = new DaiToken(await Rpc._eth_getBalance(this.address, this.provider))
this.bzz = await Rpc._eth_getBalanceERC20(this.address, this.provider)
this.dai = await Rpc._eth_getBalance(this.address, this.provider)
return this
}
public async transfer(destination: string, jsonRpcProvider: string): Promise<void> {
if (this.bzz.toDecimal.gt(0.05)) {
await Rpc.sendBzzTransaction(this.privateKey, destination, this.bzz.toString, jsonRpcProvider)
public async transfer(destination: EthAddress | string, jsonRpcProvider: string): Promise<void> {
if (this.bzz.gt(BZZ.fromDecimalString('0.05'))) {
await Rpc.sendBzzTransaction(this.privateKey, destination, this.bzz, jsonRpcProvider)
await this.refresh()
}
const { gasPrice, totalCost } = await estimateNativeTransferTransactionCost(this.privateKey, jsonRpcProvider)
if (this.dai.toBigNumber.gt(totalCost.toString())) {
if (this.dai.gt(totalCost)) {
await Rpc.sendNativeTransaction(
this.privateKey,
destination,
this.dai.toBigNumber.minus(totalCost.toString()).toString(),
this.dai.minus(totalCost),
jsonRpcProvider,
gasPrice,
)