diff --git a/src/App.tsx b/src/App.tsx
index 0336b45..a7a7171 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -13,6 +13,7 @@ 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 +46,28 @@ const App = ({
>
-
-
-
-
-
-
-
- <>
-
-
-
-
- >
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ <>
+
+
+
+
+ >
+
+
+
+
+
+
+
+
diff --git a/src/pages/gift-code/index.tsx b/src/pages/gift-code/index.tsx
index 3f56c8f..c4944b1 100644
--- a/src/pages/gift-code/index.tsx
+++ b/src/pages/gift-code/index.tsx
@@ -1,5 +1,5 @@
-import { Box, Tooltip, Typography } from '@material-ui/core'
import { BZZ, DAI } from '@ethersphere/bee-js'
+import { Box, Tooltip, Typography } from '@material-ui/core'
import { Wallet } from 'ethers'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react'
@@ -12,9 +12,9 @@ import ExpandableListItemKey from '../../components/ExpandableListItemKey'
import { HistoryHeader } from '../../components/HistoryHeader'
import { Loading } from '../../components/Loading'
import { SwarmButton } from '../../components/SwarmButton'
-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'
@@ -24,7 +24,7 @@ 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 { walletBalance } = useContext(BeeContext)
+ const { balance } = useContext(BalanceProvider)
const [loading, setLoading] = useState(false)
const [balances, setBalances] = useState([])
@@ -67,13 +67,12 @@ export default function Index(): ReactElement {
navigate(-1)
}
- if (!walletBalance) {
+ if (!balance) {
return
}
const notEnoughFundsCheck =
- walletBalance.nativeTokenBalance.lte(GIFT_WALLET_FUND_DAI_AMOUNT) ||
- walletBalance.bzzBalance.lt(GIFT_WALLET_FUND_BZZ_AMOUNT)
+ balance.dai.lte(GIFT_WALLET_FUND_DAI_AMOUNT) || balance.bzz.lt(GIFT_WALLET_FUND_BZZ_AMOUNT)
return (
<>
@@ -86,13 +85,10 @@ export default function Index(): ReactElement {
-
+
-
+
{balances.map((x, i) => (
diff --git a/src/pages/top-up/Balance.tsx b/src/pages/top-up/Balance.tsx
index 778052b..e2ec2e1 100644
--- a/src/pages/top-up/Balance.tsx
+++ b/src/pages/top-up/Balance.tsx
@@ -1,5 +1,5 @@
-import { Box, Grid, Typography } from '@material-ui/core'
import { DAI } from '@ethersphere/bee-js'
+import { Box, Grid, Typography } from '@material-ui/core'
import { ReactElement, useContext } from 'react'
import { useNavigate } from 'react-router'
import Check from 'remixicon-react/CheckLineIcon'
@@ -10,6 +10,7 @@ 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 = DAI.fromDecimalString('0.5')
@@ -22,14 +23,15 @@ interface Props {
}
export default function Index({ header, title, p, next }: Props): ReactElement {
- const { nodeAddresses, walletBalance } = useContext(Context)
+ const { nodeAddresses } = useContext(Context)
+ const { balance } = useContext(BalanceProvider)
const navigate = useNavigate()
- if (!walletBalance || !nodeAddresses) {
+ if (!balance || !nodeAddresses) {
return
}
- const disabled = walletBalance.nativeTokenBalance.lt(MINIMUM_XDAI)
+ const disabled = balance.dai.lt(MINIMUM_XDAI)
return (
<>
@@ -43,10 +45,10 @@ export default function Index({ header, title, p, next }: Props): ReactElement {
{p}
-
+
-
+
navigate(next)} disabled={disabled}>
diff --git a/src/pages/top-up/GiftCardFund.tsx b/src/pages/top-up/GiftCardFund.tsx
index 3e2d455..dc86e57 100644
--- a/src/pages/top-up/GiftCardFund.tsx
+++ b/src/pages/top-up/GiftCardFund.tsx
@@ -1,5 +1,5 @@
-import { Box, Typography } from '@material-ui/core'
import { BeeModes } from '@ethersphere/bee-js'
+import { Box, Typography } from '@material-ui/core'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router'
@@ -14,14 +14,16 @@ 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, walletBalance } = useContext(BeeContext)
+ const { nodeAddresses, nodeInfo } = useContext(BeeContext)
const { isDesktop, desktopUrl, rpcProvider, rpcProviderUrl } = useContext(SettingsContext)
+ const { balance } = useContext(BalanceProvider)
const [loading, setLoading] = useState(false)
const [wallet, setWallet] = useState(null)
@@ -39,7 +41,7 @@ export function GiftCardFund(): ReactElement {
ResolvedWallet.make(privateKeyString, rpcProvider).then(setWallet)
}, [privateKeyString, rpcProvider])
- if (!wallet || !walletBalance) {
+ if (!wallet || !balance) {
return
}
@@ -94,7 +96,7 @@ export function GiftCardFund(): ReactElement {
-
+
@@ -113,13 +115,10 @@ export function GiftCardFund(): ReactElement {
/>
-
+
-
+
{canUpgradeToLightNode ? 'Send all funds to your node and Upgrade' : 'Send all funds to your node'}
diff --git a/src/pages/top-up/Swap.tsx b/src/pages/top-up/Swap.tsx
index 8ef82d6..238eb50 100644
--- a/src/pages/top-up/Swap.tsx
+++ b/src/pages/top-up/Swap.tsx
@@ -1,5 +1,5 @@
-import { Box, Typography } from '@material-ui/core'
import { BeeModes, BZZ, DAI } from '@ethersphere/bee-js'
+import { Box, Typography } from '@material-ui/core'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react'
import { useNavigate } from 'react-router'
@@ -15,6 +15,7 @@ import { SwarmDivider } from '../../components/SwarmDivider'
import { SwarmTextInput } from '../../components/SwarmTextInput'
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 { isSwapError, SwapError, wrapWithSwapError } from '../../utils/SwapError'
@@ -48,7 +49,8 @@ export function Swap({ header }: Props): ReactElement {
const [daiAfterSwap, setDaiAfterSwap] = useState(null)
const { rpcProviderUrl, isDesktop, desktopUrl } = useContext(SettingsContext)
- const { nodeAddresses, nodeInfo, walletBalance } = useContext(BeeContext)
+ const { nodeAddresses, nodeInfo } = useContext(BeeContext)
+ const { balance } = useContext(BalanceProvider)
const navigate = useNavigate()
const { enqueueSnackbar } = useSnackbar()
@@ -61,20 +63,20 @@ export function Swap({ header }: Props): ReactElement {
// Set the initial xDAI to swap
useEffect(() => {
- if (!walletBalance || userInputSwap) {
+ if (!balance || userInputSwap) {
return
}
const minimumOptimalValue = DAI.fromDecimalString('1').plus(MINIMUM_XDAI)
- if (walletBalance.nativeTokenBalance.gte(minimumOptimalValue)) {
+ if (balance.dai.gte(minimumOptimalValue)) {
// Balance has at least 1 + MINIMUM_XDAI xDai
- setDaiToSwap(walletBalance.nativeTokenBalance.minus(DAI.fromDecimalString('1')))
+ setDaiToSwap(balance.dai.minus(DAI.fromDecimalString('1')))
} else {
// Balance is low, halve the amount
- setDaiToSwap(walletBalance.nativeTokenBalance.divide(BigInt(2)))
+ setDaiToSwap(balance.dai.divide(BigInt(2)))
}
- }, [walletBalance, userInputSwap])
+ }, [balance, userInputSwap])
// Set the xDAI to swap based on user input
useEffect(() => {
@@ -95,13 +97,13 @@ export function Swap({ header }: Props): ReactElement {
// Calculate the amount of tokens after swap
useEffect(() => {
- if (!walletBalance || !daiToSwap || error) {
+ if (!balance || !daiToSwap || error) {
return
}
- const daiAfterSwap = walletBalance.nativeTokenBalance.minus(daiToSwap)
+ const daiAfterSwap = balance.dai.minus(daiToSwap)
setDaiAfterSwap(daiAfterSwap)
const tokensConverted = daiToSwap.exchangeToBZZ(price)
- const bzzAfterSwap = tokensConverted.plus(walletBalance.bzzBalance)
+ const bzzAfterSwap = tokensConverted.plus(balance.bzz)
setBzzAfterSwap(bzzAfterSwap)
if (daiAfterSwap.lt(MINIMUM_XDAI)) {
@@ -109,9 +111,9 @@ export function Swap({ header }: Props): ReactElement {
} else if (bzzAfterSwap.lt(MINIMUM_XBZZ)) {
setError(`Must have at least ${MINIMUM_XBZZ.toSignificantDigits(4)} xBZZ after swap!`)
}
- }, [error, walletBalance, daiToSwap, price])
+ }, [error, balance, daiToSwap, price])
- if (!walletBalance || !nodeAddresses || !daiToSwap || !bzzAfterSwap || !daiAfterSwap) {
+ if (!balance || !nodeAddresses || !daiToSwap || !bzzAfterSwap || !daiAfterSwap) {
return
}
@@ -219,8 +221,8 @@ export function Swap({ header }: Props): ReactElement {
- Your current balance is {walletBalance.nativeTokenBalance.toSignificantDigits(4)} xDAI and{' '}
- {walletBalance.bzzBalance.toSignificantDigits(4)} xBZZ.
+ Your current balance is {balance.dai.toSignificantDigits(4)} xDAI and {balance.bzz.toSignificantDigits(4)}{' '}
+ xBZZ.
diff --git a/src/pages/top-up/index.tsx b/src/pages/top-up/index.tsx
index 40e4787..e6164c1 100644
--- a/src/pages/top-up/index.tsx
+++ b/src/pages/top-up/index.tsx
@@ -1,5 +1,5 @@
-import { Box, createStyles, Grid, makeStyles, Typography } from '@material-ui/core'
import { BeeModes, BZZ, DAI } from '@ethersphere/bee-js'
+import { Box, createStyles, Grid, makeStyles, Typography } from '@material-ui/core'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react'
import { useNavigate } from 'react-router'
@@ -15,6 +15,7 @@ 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'
@@ -39,7 +40,8 @@ export default function TopUp(): ReactElement {
const navigate = useNavigate()
const styles = useStyles()
const { isDesktop, desktopUrl } = useContext(SettingsContext)
- const { nodeInfo, status, walletBalance } = useContext(BeeContext)
+ const { nodeInfo, status } = useContext(BeeContext)
+ const { balance } = useContext(BalanceProvider)
const { rpcProviderUrl } = useContext(SettingsContext)
const [loading, setLoading] = useState(false)
const { enqueueSnackbar } = useSnackbar()
@@ -47,8 +49,8 @@ export default function TopUp(): ReactElement {
const canUpgradeToLightNode =
isDesktop &&
nodeInfo?.beeMode === BeeModes.ULTRA_LIGHT &&
- walletBalance?.nativeTokenBalance.gte(MINIMUM_XDAI) &&
- walletBalance?.bzzBalance.gte(MINIMUM_XBZZ)
+ balance?.dai.gte(MINIMUM_XDAI) &&
+ balance?.bzz.gte(MINIMUM_XBZZ)
async function restart() {
setLoading(true)
@@ -65,7 +67,7 @@ export default function TopUp(): ReactElement {
if (status.all === CheckState.ERROR) return
- if (!walletBalance) {
+ if (!balance) {
return
}
diff --git a/src/providers/WalletBalance.tsx b/src/providers/WalletBalance.tsx
new file mode 100644
index 0000000..92224ce
--- /dev/null
+++ b/src/providers/WalletBalance.tsx
@@ -0,0 +1,88 @@
+import { createContext, ReactChild, ReactElement, useContext, useEffect, useState } from 'react'
+import { WalletAddress } from '../utils/wallet'
+import { Context as BeeContext } from './Bee'
+import { Context as SettingsContext } from './Settings'
+
+interface ContextInterface {
+ balance: WalletAddress | null
+ error: Error | null
+ isLoading: boolean
+ lastUpdate: number | null
+ start: (frequency?: number) => void
+ stop: () => void
+ refresh: () => Promise
+}
+
+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(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(initialValues.balance)
+ const [error, setError] = useState(initialValues.error)
+ const [isLoading, setIsLoading] = useState(initialValues.isLoading)
+ const [lastUpdate, setLastUpdate] = useState(initialValues.lastUpdate)
+ const [frequency, setFrequency] = useState(null)
+
+ useEffect(() => {
+ if (nodeAddresses?.ethereum && rpcProvider) {
+ WalletAddress.make(nodeAddresses.ethereum.toHex(), 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 (
+
+ {children}
+
+ )
+}
diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts
index 4d8fc8c..fd7ecc4 100644
--- a/src/utils/wallet.ts
+++ b/src/utils/wallet.ts
@@ -2,6 +2,29 @@ import { BZZ, DAI, EthAddress } from '@ethersphere/bee-js'
import { providers, Wallet } from 'ethers'
import { estimateNativeTransferTransactionCost, Rpc } from './rpc'
+export class WalletAddress {
+ private constructor(
+ public address: string,
+ public bzz: BZZ,
+ public dai: DAI,
+ public provider: providers.JsonRpcProvider,
+ ) {}
+
+ static async make(address: string, provider: providers.JsonRpcProvider): Promise {
+ const bzz = await Rpc._eth_getBalanceERC20(address, provider)
+ const dai = await Rpc._eth_getBalance(address, provider)
+
+ return new WalletAddress(address, bzz, dai, provider)
+ }
+
+ public async refresh(): Promise {
+ this.bzz = await Rpc._eth_getBalanceERC20(this.address, this.provider)
+ this.dai = await Rpc._eth_getBalance(this.address, this.provider)
+
+ return this
+ }
+}
+
export class ResolvedWallet {
public address: string
public privateKey: string