Compare commits

..

16 Commits

Author SHA1 Message Date
bee-worker a88e78e748 chore(master): release 0.21.0 (#579) 2022-12-02 12:39:02 +01:00
Cafe137 665ae063fa fix: handle auth and server error during swap (#593)
* fix: change execution order for light node upgrade

* refactor: grab new configuration from post config request

* fix: only print successful light node upgrade when it really happens

* fix: log full desktop side swap error (#596)

* refactor: try to make the auth error in swap nicer

* refactor: make error instruction consistent

* fix: avoid overwriting daiToSwap when it is set manually
2022-12-01 12:36:15 +01:00
Cafe137 dc04e26db4 fix: change version mismatch to a warning (#594) 2022-11-30 12:36:34 +01:00
Cafe137 b798fa0e68 fix: always set rpc to newly provided value in desktop (#591)
* fix: always set rpc to newly provided value in desktop

* fix: always set new rpc and restart node

* fix: disable version check in desktop mode

* fix: only set rpc in desktop when in light mode

* refactor: simplify code
2022-11-24 14:06:00 +01:00
Cafe137 4e564dd5c0 feat: add prerequisite checks before swap (#588)
* feat: add prerequisite checks before swap

* fix: add missing authentication on desktop config call

* refactor(wip): introduce swap error

* refactor: use wrapWithSwapError

* fix: log originalError instead of error

* fix: show snackbar when error is unexpected
2022-11-23 14:20:55 +01:00
Adam Uhlíř 1c53364fcd chore: add Bee Factory support (#592) 2022-11-22 15:55:04 +01:00
Cafe137 848e61a7a0 feat: add starting state to sidebar indicator (#587) 2022-11-22 10:33:38 +01:00
bee-worker c3a940c8d7 docs: update supported bee (#586) 2022-11-15 15:23:30 +01:00
Cafe137 02469046b0 fix: add loading state to info page (#584)
* fix: add loading state to info page

* refactor: use bee-js for readiness check
2022-11-14 14:22:49 +01:00
Cafe137 1ce4a47495 fix: fix conditional rendering for blockchain network (#583) 2022-11-14 11:16:14 +01:00
Cafe137 9a8520eb6f fix: hide swap in standalone mode (#582) 2022-11-14 11:15:40 +01:00
Cafe137 ec8fdf0315 fix: always consider user input when performing swap (#572)
* fix: always consider user input when performing swap

* refactor: extract decimal places constants

* refactor: extract minimumOptimalValue

* fix: handle bzz precision and tweak message
2022-11-09 14:21:34 +01:00
Cafe137 a4b8e7ca25 fix: change status page depending on desktop mode (#573)
* fix: change status page depending on desktop mode

* refactor: check desktop reachability periodically
2022-11-07 14:02:22 +01:00
Cafe137 693609810d fix: refresh after chequebook withdraw deposit (#576)
* fix: refresh after chequebook withdraw deposit

* refactor: remove extra catch
2022-11-07 14:02:11 +01:00
Adam Uhlíř 73f845a73a docs: desktop development (#539) 2022-10-05 09:27:28 +02:00
Adam Uhlíř b6419297f4 ci: make preview check step optional (#548) 2022-10-05 09:26:57 +02:00
36 changed files with 1267 additions and 244 deletions
+2
View File
@@ -61,6 +61,7 @@ jobs:
uses: ethersphere/update-supported-bee-action@v1 uses: ethersphere/update-supported-bee-action@v1
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
with: with:
updateEngine: true
token: ${{ secrets.GHA_PAT_BASIC }} token: ${{ secrets.GHA_PAT_BASIC }}
- name: Build - name: Build
@@ -71,6 +72,7 @@ jobs:
- name: Create preview - name: Create preview
uses: ethersphere/swarm-actions/pr-preview@v0 uses: ethersphere/swarm-actions/pr-preview@v0
continue-on-error: true
with: with:
bee-url: https://unlimited.gateway.ethswarm.org bee-url: https://unlimited.gateway.ethswarm.org
token: ${{ secrets.GHA_PAT_BASIC }} token: ${{ secrets.GHA_PAT_BASIC }}
+21
View File
@@ -1,5 +1,26 @@
# Changelog # Changelog
## [0.21.0](https://github.com/ethersphere/bee-dashboard/compare/v0.20.2...v0.21.0) (2022-12-01)
### Features
* add prerequisite checks before swap ([#588](https://github.com/ethersphere/bee-dashboard/issues/588)) ([4e564dd](https://github.com/ethersphere/bee-dashboard/commit/4e564dd5c08b938c95f07818bc60957a7df4f5bb))
* add starting state to sidebar indicator ([#587](https://github.com/ethersphere/bee-dashboard/issues/587)) ([848e61a](https://github.com/ethersphere/bee-dashboard/commit/848e61a7a0fc9b31cae4f603473b37d467f9e914))
### Bug Fixes
* add loading state to info page ([#584](https://github.com/ethersphere/bee-dashboard/issues/584)) ([0246904](https://github.com/ethersphere/bee-dashboard/commit/02469046b05512d6617d8b21ca93b41d6a8a6827))
* always consider user input when performing swap ([#572](https://github.com/ethersphere/bee-dashboard/issues/572)) ([ec8fdf0](https://github.com/ethersphere/bee-dashboard/commit/ec8fdf0315ed7ee75c7612780c602cba49a2321d))
* always set rpc to newly provided value in desktop ([#591](https://github.com/ethersphere/bee-dashboard/issues/591)) ([b798fa0](https://github.com/ethersphere/bee-dashboard/commit/b798fa0e68b367fe324ef64507b1405b642da6e0))
* change status page depending on desktop mode ([#573](https://github.com/ethersphere/bee-dashboard/issues/573)) ([a4b8e7c](https://github.com/ethersphere/bee-dashboard/commit/a4b8e7ca2596028e7c8192c92202c0361610e307))
* change version mismatch to a warning ([#594](https://github.com/ethersphere/bee-dashboard/issues/594)) ([dc04e26](https://github.com/ethersphere/bee-dashboard/commit/dc04e26db4fe6beb9e76fad79c732794b0b7f77d))
* fix conditional rendering for blockchain network ([#583](https://github.com/ethersphere/bee-dashboard/issues/583)) ([1ce4a47](https://github.com/ethersphere/bee-dashboard/commit/1ce4a474954a5ba4debee53b40bb66a46fb19ffc))
* handle auth and server error during swap ([#593](https://github.com/ethersphere/bee-dashboard/issues/593)) ([665ae06](https://github.com/ethersphere/bee-dashboard/commit/665ae063fa49bc94762ea10a9098b57e95327d9c))
* hide swap in standalone mode ([#582](https://github.com/ethersphere/bee-dashboard/issues/582)) ([9a8520e](https://github.com/ethersphere/bee-dashboard/commit/9a8520eb6fe9f40a77c4230ab79d3731ebdd4b42))
* refresh after chequebook withdraw deposit ([#576](https://github.com/ethersphere/bee-dashboard/issues/576)) ([6936098](https://github.com/ethersphere/bee-dashboard/commit/693609810d735d1e54691b13ea0e4db33e678a53))
## [0.20.2](https://github.com/ethersphere/bee-dashboard/compare/v0.20.1...v0.20.2) (2022-09-15) ## [0.20.2](https://github.com/ethersphere/bee-dashboard/compare/v0.20.1...v0.20.2) (2022-09-15)
+3 -3
View File
@@ -13,7 +13,7 @@
**Warning: This project is in alpha state. There might (and most probably will) be changes in the future to its API and **Warning: This project is in alpha state. There might (and most probably will) be changes in the future to its API and
working. Also, no guarantees can be made about its stability, efficiency, and security at this stage.** working. Also, no guarantees can be made about its stability, efficiency, and security at this stage.**
This project is intended to be used with **Bee version <!-- SUPPORTED_BEE_START -->1.7.0-bbf13011<!-- SUPPORTED_BEE_END -->**. This project is intended to be used with **Bee version <!-- SUPPORTED_BEE_START -->1.9.0-13a47043<!-- SUPPORTED_BEE_END -->**.
Using it with older or newer Bee versions is not recommended and may not work. Stay up to date by joining the Using it with older or newer Bee versions is not recommended and may not work. Stay up to date by joining the
[official Discord](https://discord.gg/GU22h2utj6) and by keeping an eye on the [official Discord](https://discord.gg/GU22h2utj6) and by keeping an eye on the
[releases tab](https://github.com/ethersphere/bee-dashboard/releases). [releases tab](https://github.com/ethersphere/bee-dashboard/releases).
@@ -107,10 +107,10 @@ We support following variables:
#### Swarm Desktop development #### Swarm Desktop development
If you want to develop Bee Dashboard in the Swarm Desktop mode, then spin up `swarm-desktop` to the point where you see Bee Dashboard (eq. install Bee etc.) and: If you want to develop Bee Dashboard in the Swarm Desktop mode, then spin up `swarm-desktop` to the point where Desktop is initialized (eq. the splash screen disappear) and:
```sh ```sh
echo "REACT_APP_BEE_DESKTOP_URL=http://localhost:3000 echo "REACT_APP_BEE_DESKTOP_URL=http://localhost:3054
REACT_APP_BEE_DESKTOP_ENABLED=true" > .env.development.local REACT_APP_BEE_DESKTOP_ENABLED=true" > .env.development.local
npm start npm start
+688 -13
View File
File diff suppressed because it is too large Load Diff
+5 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ethersphere/bee-dashboard", "name": "@ethersphere/bee-dashboard",
"version": "0.20.2", "version": "0.21.0",
"description": "An app which helps users to setup their Bee node and do actions like cash out cheques", "description": "An app which helps users to setup their Bee node and do actions like cash out cheques",
"keywords": [ "keywords": [
"bee", "bee",
@@ -26,7 +26,7 @@
"url": "https://github.com/ethersphere/bee-dashboard.git" "url": "https://github.com/ethersphere/bee-dashboard.git"
}, },
"dependencies": { "dependencies": {
"@ethersphere/bee-js": "^5.0.0", "@ethersphere/bee-js": "^5.1.0",
"@ethersphere/manifest-js": "1.2.1", "@ethersphere/manifest-js": "1.2.1",
"@ethersphere/swarm-cid": "^0.1.0", "@ethersphere/swarm-cid": "^0.1.0",
"@material-ui/core": "4.12.3", "@material-ui/core": "4.12.3",
@@ -136,7 +136,8 @@
"lint": "eslint --fix \"src/**/*.ts\" \"src/**/*.tsx\" && prettier --write \"src/**/*.ts\" \"src/**/*.tsx\"", "lint": "eslint --fix \"src/**/*.ts\" \"src/**/*.tsx\" && prettier --write \"src/**/*.ts\" \"src/**/*.tsx\"",
"lint:check": "eslint \"src/**/*.ts\" \"src/**/*.tsx\" && prettier --check \"src/**/*.ts\" \"src/**/*.tsx\"", "lint:check": "eslint \"src/**/*.ts\" \"src/**/*.tsx\" && prettier --check \"src/**/*.ts\" \"src/**/*.tsx\"",
"check:types": "tsc --project tsconfig.lib.json", "check:types": "tsc --project tsconfig.lib.json",
"update-map-data": "node ./utils/update-map-data.js" "update-map-data": "node ./utils/update-map-data.js",
"bee": "bee-factory start"
}, },
"files": [ "files": [
"lib", "lib",
@@ -158,6 +159,6 @@
"engines": { "engines": {
"node": ">=14.0.0", "node": ">=14.0.0",
"npm": ">=6.9.0", "npm": ">=6.9.0",
"bee": ">=0.6.0" "bee": "1.9.0-13a47043"
} }
} }
+2 -2
View File
@@ -1,7 +1,7 @@
import CssBaseline from '@material-ui/core/CssBaseline' import CssBaseline from '@material-ui/core/CssBaseline'
import { ThemeProvider } from '@material-ui/core/styles' import { ThemeProvider } from '@material-ui/core/styles'
import { SnackbarProvider } from 'notistack' import { SnackbarProvider } from 'notistack'
import React, { ReactElement } from 'react' import { ReactElement } from 'react'
import { HashRouter as Router } from 'react-router-dom' import { HashRouter as Router } from 'react-router-dom'
import './App.css' import './App.css'
import Dashboard from './layout/Dashboard' import Dashboard from './layout/Dashboard'
@@ -47,7 +47,7 @@ const App = ({
desktopUrl={desktopUrl} desktopUrl={desktopUrl}
> >
<TopUpProvider> <TopUpProvider>
<BeeProvider> <BeeProvider isDesktop={isDesktop}>
<BalanceProvider> <BalanceProvider>
<StampsProvider> <StampsProvider>
<FileProvider> <FileProvider>
+13 -2
View File
@@ -2,6 +2,7 @@ import { createStyles, makeStyles, Theme, Typography } from '@material-ui/core'
import { ReactElement } from 'react' import { ReactElement } from 'react'
import Check from 'remixicon-react/CheckLineIcon' import Check from 'remixicon-react/CheckLineIcon'
import AlertCircle from 'remixicon-react/ErrorWarningFillIcon' import AlertCircle from 'remixicon-react/ErrorWarningFillIcon'
import RefreshLine from 'remixicon-react/RefreshLineIcon'
import { SwarmButton, SwarmButtonProps } from './SwarmButton' import { SwarmButton, SwarmButtonProps } from './SwarmButton'
interface Props { interface Props {
@@ -9,7 +10,7 @@ interface Props {
title: string title: string
subtitle: string subtitle: string
buttonProps: SwarmButtonProps buttonProps: SwarmButtonProps
status: 'ok' | 'error' status: 'ok' | 'error' | 'loading'
} }
const useStyles = (backgroundColor: string) => const useStyles = (backgroundColor: string) =>
@@ -56,12 +57,22 @@ export default function Card({ buttonProps, icon, title, subtitle, status }: Pro
const { className, ...rest } = buttonProps const { className, ...rest } = buttonProps
const classes = useStyles(backgroundColor)() const classes = useStyles(backgroundColor)()
let statusIcon = null
if (status === 'ok') {
statusIcon = <Check size="13" color="#09ca6c" />
} else if (status === 'error') {
statusIcon = <AlertCircle size="13" color="#f44336" />
} else if (status === 'loading') {
statusIcon = <RefreshLine size="13" color="orange" />
}
return ( return (
<div className={classes.root}> <div className={classes.root}>
<div className={classes.wrapper}> <div className={classes.wrapper}>
<div className={classes.iconWrapper}> <div className={classes.iconWrapper}>
{icon} {icon}
{status === 'ok' ? <Check size="13" color="#09ca6c" /> : <AlertCircle size="13" color="#f44336" />} {statusIcon}
</div> </div>
<Typography variant="h2" style={{ marginBottom: '8px' }}> <Typography variant="h2" style={{ marginBottom: '8px' }}>
{title} {title}
+3
View File
@@ -25,6 +25,9 @@ export default function StatusIcon({ checkState, size, className, isLoading }: P
case CheckState.ERROR: case CheckState.ERROR:
backgroundColor = '#ff3a52' backgroundColor = '#ff3a52'
break break
case CheckState.STARTING:
backgroundColor = 'orange'
break
default: default:
// Default is error // Default is error
backgroundColor = '#ff3a52' backgroundColor = '#ff3a52'
+9 -5
View File
@@ -1,12 +1,13 @@
import { BigNumber } from 'bignumber.js'
import { ReactElement, useContext } from 'react' import { ReactElement, useContext } from 'react'
import Download from 'remixicon-react/DownloadLineIcon' import Download from 'remixicon-react/DownloadLineIcon'
import { Context as SettingsContext } from '../providers/Settings'
import WithdrawDepositModal from '../components/WithdrawDepositModal' import WithdrawDepositModal from '../components/WithdrawDepositModal'
import { BigNumber } from 'bignumber.js' import { Context as BeeContext } from '../providers/Bee'
import { Context as SettingsContext } from '../providers/Settings'
export default function DepositModal(): ReactElement { export default function DepositModal(): ReactElement {
const { beeDebugApi } = useContext(SettingsContext) const { beeDebugApi } = useContext(SettingsContext)
const { refresh } = useContext(BeeContext)
return ( return (
<WithdrawDepositModal <WithdrawDepositModal
@@ -16,10 +17,13 @@ export default function DepositModal(): ReactElement {
label="Deposit" label="Deposit"
icon={<Download size="1rem" />} icon={<Download size="1rem" />}
min={new BigNumber(0)} min={new BigNumber(0)}
action={(amount: bigint) => { action={async (amount: bigint) => {
if (!beeDebugApi) throw new Error('Bee Debug URL is not valid') if (!beeDebugApi) throw new Error('Bee Debug URL is not valid')
return beeDebugApi.depositTokens(amount.toString()) const transactionHash = await beeDebugApi.depositTokens(amount.toString())
refresh()
return transactionHash
}} }}
/> />
) )
+7 -2
View File
@@ -2,10 +2,12 @@ import { BigNumber } from 'bignumber.js'
import { ReactElement, useContext } from 'react' import { ReactElement, useContext } from 'react'
import Upload from 'remixicon-react/UploadLineIcon' import Upload from 'remixicon-react/UploadLineIcon'
import WithdrawDepositModal from '../components/WithdrawDepositModal' import WithdrawDepositModal from '../components/WithdrawDepositModal'
import { Context as BeeContext } from '../providers/Bee'
import { Context as SettingsContext } from '../providers/Settings' import { Context as SettingsContext } from '../providers/Settings'
export default function WithdrawModal(): ReactElement { export default function WithdrawModal(): ReactElement {
const { beeDebugApi } = useContext(SettingsContext) const { beeDebugApi } = useContext(SettingsContext)
const { refresh } = useContext(BeeContext)
return ( return (
<WithdrawDepositModal <WithdrawDepositModal
@@ -15,10 +17,13 @@ export default function WithdrawModal(): ReactElement {
label="Withdraw" label="Withdraw"
icon={<Upload size="1rem" />} icon={<Upload size="1rem" />}
min={new BigNumber(0)} min={new BigNumber(0)}
action={(amount: bigint) => { action={async (amount: bigint) => {
if (!beeDebugApi) throw new Error('Bee Debug URL is not valid') if (!beeDebugApi) throw new Error('Bee Debug URL is not valid')
return beeDebugApi.withdrawTokens(amount.toString()) const transactionHash = await beeDebugApi.withdrawTokens(amount.toString())
refresh()
return transactionHash
}} }}
/> />
) )
+27 -20
View File
@@ -1,8 +1,7 @@
import axios from 'axios' import axios from 'axios'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { getLatestBeeDesktopVersion } from '../utils/desktop'
import { getJson } from '../utils/net'
import { GITHUB_REPO_URL } from '../constants' import { GITHUB_REPO_URL } from '../constants'
import { BeeConfig, getDesktopConfiguration, getLatestBeeDesktopVersion } from '../utils/desktop'
export interface LatestBeeReleaseHook { export interface LatestBeeReleaseHook {
latestBeeRelease: LatestBeeRelease | null latestBeeRelease: LatestBeeRelease | null
@@ -11,6 +10,7 @@ export interface LatestBeeReleaseHook {
} }
export interface BeeDesktopHook { export interface BeeDesktopHook {
reachable: boolean
error: Error | null error: Error | null
isLoading: boolean isLoading: boolean
beeDesktopVersion: string beeDesktopVersion: string
@@ -22,11 +22,34 @@ export interface NewDesktopVersionHook {
} }
export const useBeeDesktop = (isBeeDesktop = false, desktopUrl: string): BeeDesktopHook => { export const useBeeDesktop = (isBeeDesktop = false, desktopUrl: string): BeeDesktopHook => {
const [reachable, setReachable] = useState(false)
const [desktopAutoUpdateEnabled, setDesktopAutoUpdateEnabled] = useState<boolean>(true) const [desktopAutoUpdateEnabled, setDesktopAutoUpdateEnabled] = useState<boolean>(true)
const [beeDesktopVersion, setBeeDesktopVersion] = useState<string>('') const [beeDesktopVersion, setBeeDesktopVersion] = useState<string>('')
const [isLoading, setLoading] = useState<boolean>(true) const [isLoading, setLoading] = useState<boolean>(true)
const [error, setError] = useState<Error | null>(null) const [error, setError] = useState<Error | null>(null)
useEffect(() => {
if (!isBeeDesktop) {
return
}
function runReachabilityCheck() {
axios
.get(`${desktopUrl}/info`)
.then(() => {
setReachable(true)
})
.catch(() => {
setReachable(false)
})
}
runReachabilityCheck()
const interval = setInterval(runReachabilityCheck, 10_000)
return () => clearInterval(interval)
}, [desktopUrl, isBeeDesktop])
useEffect(() => { useEffect(() => {
if (!isBeeDesktop) { if (!isBeeDesktop) {
setLoading(false) setLoading(false)
@@ -48,7 +71,7 @@ export const useBeeDesktop = (isBeeDesktop = false, desktopUrl: string): BeeDesk
} }
}, [desktopUrl, isBeeDesktop]) }, [desktopUrl, isBeeDesktop])
return { error, isLoading, beeDesktopVersion, desktopAutoUpdateEnabled } return { error, isLoading, beeDesktopVersion, desktopAutoUpdateEnabled, reachable }
} }
async function checkNewVersion(desktopUrl: string): Promise<string> { async function checkNewVersion(desktopUrl: string): Promise<string> {
@@ -85,22 +108,6 @@ export function useNewBeeDesktopVersion(
return { newBeeDesktopVersion } return { newBeeDesktopVersion }
} }
export interface BeeConfig {
'api-addr': string
'debug-api-addr': string
'debug-api-enable': boolean
password: string
'swap-enable': boolean
'swap-initial-deposit': bigint
mainnet: boolean
'full-node': boolean
'cors-allowed-origins': string
'resolver-options': string
'use-postage-snapshot': boolean
'data-dir': string
'swap-endpoint'?: string
}
export interface GetBeeConfig { export interface GetBeeConfig {
config: BeeConfig | null config: BeeConfig | null
isLoading: boolean isLoading: boolean
@@ -113,7 +120,7 @@ export const useGetBeeConfig = (desktopUrl: string): GetBeeConfig => {
const [error, setError] = useState<Error | null>(null) const [error, setError] = useState<Error | null>(null)
useEffect(() => { useEffect(() => {
getJson<BeeConfig>(`${desktopUrl}/config`) getDesktopConfiguration(desktopUrl)
.then(beeConf => { .then(beeConf => {
setBeeConfig(beeConf) setBeeConfig(beeConf)
setError(null) setError(null)
+8 -2
View File
@@ -1,8 +1,14 @@
import { BigNumber } from 'bignumber.js' import { BigNumber } from 'bignumber.js'
import { Token } from './Token' import { Token } from './Token'
export const BZZ_DECIMAL_PLACES = 16
export class BzzToken extends Token { export class BzzToken extends Token {
constructor(amount: BigNumber | string | bigint) { constructor(value: BigNumber | string | bigint) {
super(amount, 16) super(value, BZZ_DECIMAL_PLACES)
}
static fromDecimal(value: BigNumber | string | bigint): BzzToken {
return Token.fromDecimal(value, BZZ_DECIMAL_PLACES)
} }
} }
+8 -2
View File
@@ -1,8 +1,14 @@
import { BigNumber } from 'bignumber.js' import { BigNumber } from 'bignumber.js'
import { Token } from './Token' import { Token } from './Token'
const DAI_DECIMAL_PLACES = 18
export class DaiToken extends Token { export class DaiToken extends Token {
constructor(amount: BigNumber | string | bigint) { constructor(value: BigNumber | string | bigint) {
super(amount, 18) super(value, DAI_DECIMAL_PLACES)
}
static fromDecimal(value: BigNumber | string | bigint): DaiToken {
return Token.fromDecimal(value, DAI_DECIMAL_PLACES)
} }
} }
+7
View File
@@ -87,4 +87,11 @@ export class Token {
this.decimals, this.decimals,
) )
} }
plusBaseUnits(amount: string): Token {
return new Token(
this.toBigNumber.plus(new BigNumber(amount).multipliedBy(new BigNumber(10).pow(this.decimals))),
this.decimals,
)
}
} }
+5 -3
View File
@@ -46,9 +46,11 @@ export function AccountWallet(): ReactElement {
<Box mb={4}> <Box mb={4}>
<Grid container direction="row" justifyContent="space-between" alignItems="center"> <Grid container direction="row" justifyContent="space-between" alignItems="center">
<Typography variant="h2">Wallet balance</Typography> <Typography variant="h2">Wallet balance</Typography>
<SwarmButton onClick={onDeposit} iconType={Download}> {isDesktop && (
Top up wallet <SwarmButton onClick={onDeposit} iconType={Download}>
</SwarmButton> Top up wallet
</SwarmButton>
)}
</Grid> </Grid>
</Box> </Box>
{balance && nodeAddresses ? ( {balance && nodeAddresses ? (
+48
View File
@@ -0,0 +1,48 @@
import { useContext } from 'react'
import { useNavigate } from 'react-router'
import ExchangeFunds from 'remixicon-react/ExchangeFundsLineIcon'
import Card from '../../components/Card'
import { Context as BeeContext } from '../../providers/Bee'
import { ROUTES } from '../../routes'
export function ChequebookInfoCard() {
const { chequebookBalance } = useContext(BeeContext)
const navigate = useNavigate()
if (
chequebookBalance?.availableBalance !== undefined &&
chequebookBalance?.availableBalance.toBigNumber.isGreaterThan(0)
) {
return (
<Card
buttonProps={{
iconType: ExchangeFunds,
children: 'View chequebook',
onClick: () => navigate(ROUTES.ACCOUNT_CHEQUEBOOK),
}}
icon={<ExchangeFunds />}
title={`${chequebookBalance?.availableBalance.toSignificantDigits(4)} xBZZ`}
subtitle="Current chequebook balance."
status="ok"
/>
)
}
return (
<Card
buttonProps={{
iconType: ExchangeFunds,
children: 'View chequebook',
onClick: () => navigate(ROUTES.ACCOUNT_CHEQUEBOOK),
}}
icon={<ExchangeFunds />}
title={
chequebookBalance?.availableBalance
? `${chequebookBalance.availableBalance.toSignificantDigits(4)} xBZZ`
: 'No available balance.'
}
subtitle="Chequebook not setup."
status="error"
/>
)
}
+15 -3
View File
@@ -1,17 +1,29 @@
import { ReactElement, useContext } from 'react' import { ReactElement, useContext } from 'react'
import Search from 'remixicon-react/SearchLineIcon'
import Globe from 'remixicon-react/GlobalLineIcon' import Globe from 'remixicon-react/GlobalLineIcon'
import Search from 'remixicon-react/SearchLineIcon'
import Settings from 'remixicon-react/Settings2LineIcon' import Settings from 'remixicon-react/Settings2LineIcon'
import { CheckState, Context as BeeContext } from '../../providers/Bee'
import Card from '../../components/Card'
import { useNavigate } from 'react-router' import { useNavigate } from 'react-router'
import Card from '../../components/Card'
import { CheckState, Context as BeeContext } from '../../providers/Bee'
import { ROUTES } from '../../routes' import { ROUTES } from '../../routes'
export default function NodeInfoCard(): ReactElement { export default function NodeInfoCard(): ReactElement {
const { status } = useContext(BeeContext) const { status } = useContext(BeeContext)
const navigate = useNavigate() const navigate = useNavigate()
if (status.all === CheckState.STARTING) {
return (
<Card
buttonProps={{ iconType: Settings, children: 'Open node setup', onClick: () => navigate(ROUTES.STATUS) }}
icon={<Globe />}
title="Starting up..."
subtitle="Your Bee node is currently launching."
status="loading"
/>
)
}
if (status.all === CheckState.ERROR) { if (status.all === CheckState.ERROR) {
return ( return (
<Card <Card
+53
View File
@@ -0,0 +1,53 @@
import { useContext } from 'react'
import { useNavigate } from 'react-router'
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 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 (nodeInfo?.beeMode && ['light', 'full', 'dev'].includes(nodeInfo.beeMode)) {
return (
<Card
buttonProps={{
iconType: Wallet,
children: 'Manage your wallet',
onClick: () => navigate(ROUTES.ACCOUNT_WALLET),
}}
icon={<Wallet />}
title={balanceText}
subtitle="Current wallet balance."
status="ok"
/>
)
}
return (
<Card
buttonProps={{
iconType: Wallet,
children: 'Setup wallet',
onClick: () => navigate(ROUTES.TOP_UP),
}}
icon={<Upload />}
title="Your wallet is not setup."
subtitle="To share content on Swarm, please setup your wallet."
status="error"
/>
)
}
+10 -79
View File
@@ -1,110 +1,41 @@
import { Button } from '@material-ui/core' import { Button } from '@material-ui/core'
import { ReactElement, useContext } from 'react' import { ReactElement, useContext } from 'react'
import { useNavigate } from 'react-router'
import ExchangeFunds from 'remixicon-react/ExchangeFundsLineIcon'
import Upload from 'remixicon-react/UploadLineIcon'
import Wallet from 'remixicon-react/Wallet3LineIcon'
import Card from '../../components/Card'
import ExpandableListItem from '../../components/ExpandableListItem' import ExpandableListItem from '../../components/ExpandableListItem'
import Map from '../../components/Map' import Map from '../../components/Map'
import { BEE_DESKTOP_LATEST_RELEASE_PAGE } from '../../constants'
import { useBeeDesktop, useNewBeeDesktopVersion } from '../../hooks/apiHooks' import { useBeeDesktop, useNewBeeDesktopVersion } from '../../hooks/apiHooks'
import { Context as BeeContext } from '../../providers/Bee' import { Context as BeeContext } from '../../providers/Bee'
import { Context as SettingsContext } from '../../providers/Settings' import { Context as SettingsContext } from '../../providers/Settings'
import { Context as BalanceProvider } from '../../providers/WalletBalance'
import { ROUTES } from '../../routes'
import { chainIdToName } from '../../utils/chain' import { chainIdToName } from '../../utils/chain'
import { ChequebookInfoCard } from './ChequebookInfoCard'
import NodeInfoCard from './NodeInfoCard' import NodeInfoCard from './NodeInfoCard'
import { BEE_DESKTOP_LATEST_RELEASE_PAGE } from '../../constants' import { WalletInfoCard } from './WalletInfoCard'
export default function Status(): ReactElement { export default function Status(): ReactElement {
const { const {
debugApiReadiness,
status, status,
latestUserVersion, latestUserVersion,
isLatestBeeVersion, isLatestBeeVersion,
latestBeeVersionUrl, latestBeeVersionUrl,
topology, topology,
nodeInfo, nodeInfo,
chequebookBalance,
chainId, chainId,
} = useContext(BeeContext) } = useContext(BeeContext)
const { isDesktop, desktopUrl } = useContext(SettingsContext) const { isDesktop, desktopUrl } = useContext(SettingsContext)
const { balance, error } = useContext(BalanceProvider)
const { beeDesktopVersion } = useBeeDesktop(isDesktop, desktopUrl) const { beeDesktopVersion } = useBeeDesktop(isDesktop, desktopUrl)
const { newBeeDesktopVersion } = useNewBeeDesktopVersion(isDesktop, desktopUrl, false) const { newBeeDesktopVersion } = useNewBeeDesktopVersion(isDesktop, desktopUrl, false)
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`
}
return ( return (
<div> <div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'stretch', alignContent: 'stretch' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'stretch', alignContent: 'stretch' }}>
<NodeInfoCard /> <NodeInfoCard />
<div style={{ width: '8px' }}></div> {debugApiReadiness && (
{nodeInfo?.beeMode && ['light', 'full', 'dev'].includes(nodeInfo.beeMode) ? (
<Card
buttonProps={{
iconType: Wallet,
children: 'Manage your wallet',
onClick: () => navigate(ROUTES.ACCOUNT_WALLET),
}}
icon={<Wallet />}
title={balanceText}
subtitle="Current wallet balance."
status="ok"
/>
) : (
<Card
buttonProps={{
iconType: Wallet,
children: 'Setup wallet',
onClick: () => navigate(ROUTES.TOP_UP),
}}
icon={<Upload />}
title="Your wallet is not setup."
subtitle="To share content on Swarm, please setup your wallet."
status="error"
/>
)}
{nodeInfo?.beeMode && ['light', 'full', 'dev'].includes(nodeInfo.beeMode) && (
<> <>
<div style={{ width: '8px' }} /> <div style={{ width: '8px' }}></div>
{chequebookBalance?.availableBalance !== undefined && <WalletInfoCard />
chequebookBalance?.availableBalance.toBigNumber.isGreaterThan(0) ? ( <div style={{ width: '8px' }}></div>
<Card <ChequebookInfoCard />
buttonProps={{
iconType: ExchangeFunds,
children: 'View chequebook',
onClick: () => navigate(ROUTES.ACCOUNT_CHEQUEBOOK),
}}
icon={<ExchangeFunds />}
title={`${chequebookBalance?.availableBalance.toSignificantDigits(4)} xBZZ`}
subtitle="Current chequebook balance."
status="ok"
/>
) : (
<Card
buttonProps={{
iconType: ExchangeFunds,
children: 'View chequebook',
onClick: () => navigate(ROUTES.ACCOUNT_CHEQUEBOOK),
}}
icon={<ExchangeFunds />}
title={
chequebookBalance?.availableBalance
? `${chequebookBalance.availableBalance.toSignificantDigits(4)} xBZZ`
: 'No available balance.'
}
subtitle="Chequebook not setup."
status="error"
/>
)}
</> </>
)} )}
</div> </div>
@@ -158,7 +89,7 @@ export default function Status(): ReactElement {
} }
/> />
<ExpandableListItem label="Mode" value={nodeInfo?.beeMode} /> <ExpandableListItem label="Mode" value={nodeInfo?.beeMode} />
{chainId && <ExpandableListItem label="Blockchain network" value={chainIdToName(chainId)} />} {chainId !== null && <ExpandableListItem label="Blockchain network" value={chainIdToName(chainId)} />}
</div> </div>
) )
} }
+4 -1
View File
@@ -1,5 +1,6 @@
import { BeeModes } from '@ethersphere/bee-js' import { BeeModes } from '@ethersphere/bee-js'
import { Box, Grid, Typography } from '@material-ui/core' import { Box, Grid, Typography } from '@material-ui/core'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react' import { ReactElement, useContext, useEffect, useState } from 'react'
import { useNavigate } from 'react-router' import { useNavigate } from 'react-router'
import { Waiting } from '../../components/Waiting' import { Waiting } from '../../components/Waiting'
@@ -12,6 +13,7 @@ export default function LightModeRestart(): ReactElement {
const [startedAt] = useState(Number.parseInt(localStorage.getItem(STARTED_UPGRADE_AT) ?? Date.now().toFixed())) const [startedAt] = useState(Number.parseInt(localStorage.getItem(STARTED_UPGRADE_AT) ?? Date.now().toFixed()))
const { apiHealth, nodeInfo } = useContext(Context) const { apiHealth, nodeInfo } = useContext(Context)
const navigate = useNavigate() const navigate = useNavigate()
const { enqueueSnackbar } = useSnackbar()
useEffect(() => { useEffect(() => {
localStorage.setItem(STARTED_UPGRADE_AT, startedAt.toFixed()) localStorage.setItem(STARTED_UPGRADE_AT, startedAt.toFixed())
@@ -23,10 +25,11 @@ export default function LightModeRestart(): ReactElement {
} }
if (apiHealth && nodeInfo?.beeMode === BeeModes.LIGHT) { if (apiHealth && nodeInfo?.beeMode === BeeModes.LIGHT) {
enqueueSnackbar('Upgraded to light node', { variant: 'success' })
localStorage.removeItem(STARTED_UPGRADE_AT) localStorage.removeItem(STARTED_UPGRADE_AT)
navigate(ROUTES.INFO) navigate(ROUTES.INFO)
} }
}, [startedAt, navigate, nodeInfo, apiHealth]) }, [startedAt, navigate, nodeInfo, apiHealth, enqueueSnackbar])
return ( return (
<Grid container direction="column" justifyContent="center" alignItems="center"> <Grid container direction="column" justifyContent="center" alignItems="center">
+8 -9
View File
@@ -1,12 +1,11 @@
import CircularProgress from '@material-ui/core/CircularProgress' import CircularProgress from '@material-ui/core/CircularProgress'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext } from 'react' import { ReactElement, useContext } from 'react'
import ExpandableList from '../../components/ExpandableList' import ExpandableList from '../../components/ExpandableList'
import ExpandableListItemInput from '../../components/ExpandableListItemInput' import ExpandableListItemInput from '../../components/ExpandableListItemInput'
import { Context as BeeContext } from '../../providers/Bee' import { Context as BeeContext } from '../../providers/Bee'
import { Context as SettingsContext } from '../../providers/Settings' import { Context as SettingsContext } from '../../providers/Settings'
import { useSnackbar } from 'notistack' import { getDesktopConfiguration, restartBeeNode, setJsonRpcInDesktop } from '../../utils/desktop'
import { restartBeeNode, setJsonRpcInDesktop } from '../../utils/desktop'
import { BeeModes } from '@ethersphere/bee-js'
export default function SettingsPage(): ReactElement { export default function SettingsPage(): ReactElement {
const { const {
@@ -24,17 +23,17 @@ export default function SettingsPage(): ReactElement {
desktopUrl, desktopUrl,
setAndPersistJsonRpcProvider, setAndPersistJsonRpcProvider,
} = useContext(SettingsContext) } = useContext(SettingsContext)
const { refresh, nodeInfo } = useContext(BeeContext) const { refresh } = useContext(BeeContext)
const { enqueueSnackbar, closeSnackbar } = useSnackbar() const { enqueueSnackbar, closeSnackbar } = useSnackbar()
async function handleSetRpcUrl(value: string) { async function handleSetRpcUrl(value: string) {
try { try {
setAndPersistJsonRpcProvider(value) setAndPersistJsonRpcProvider(value)
// We can't set the RPC URL to the `swap-endpoint` Bee config value unless the Bee node is already in const shouldUpdateDesktop = isDesktop && (await getDesktopConfiguration(desktopUrl))['swap-endpoint']
// light mode as setting this config value, basically upgrades the node to light mode.
if (isDesktop && nodeInfo?.beeMode === BeeModes.LIGHT) { if (shouldUpdateDesktop) {
await setJsonRpcInDesktop(desktopUrl, rpcProviderUrl) await setJsonRpcInDesktop(desktopUrl, value)
const snackKey = enqueueSnackbar('RPC endpoint successfully changed, restarting Bee node...', { const snackKey = enqueueSnackbar('RPC endpoint successfully changed, restarting Bee node...', {
variant: 'success', variant: 'success',
}) })
@@ -48,7 +47,7 @@ export default function SettingsPage(): ReactElement {
await refresh() await refresh()
} catch (e) { } catch (e) {
console.error(e) //eslint-disable-line console.error(e) //eslint-disable-line
enqueueSnackbar(`Failed to change RPC endpoint. Error: ${e}`, { variant: 'error' }) enqueueSnackbar(`Failed to change RPC endpoint. ${e}`, { variant: 'error' })
} }
} }
@@ -11,8 +11,9 @@ import { CheckState, Context } from '../../../providers/Bee'
const ChequebookDeployFund = (): ReactElement | null => { const ChequebookDeployFund = (): ReactElement | null => {
const { status, isLoading, chequebookAddress } = useContext(Context) const { status, isLoading, chequebookAddress } = useContext(Context)
const { checkState, isEnabled } = status.chequebook const { checkState, isEnabled } = status.chequebook
const { checkState: debugApiCheckState } = status.debugApiConnection
if (!isEnabled) return null if (!isEnabled || debugApiCheckState === CheckState.ERROR) return null
let text: ReactNode let text: ReactNode
@@ -11,7 +11,7 @@ import { Context as SettingsContext } from '../../../providers/Settings'
export default function NodeConnectionCheck(): ReactElement | null { export default function NodeConnectionCheck(): ReactElement | null {
const { status, isLoading } = useContext(Context) const { status, isLoading } = useContext(Context)
const { setDebugApiUrl, apiDebugUrl } = useContext(SettingsContext) const { setDebugApiUrl, apiDebugUrl, isDesktop } = useContext(SettingsContext)
const { checkState, isEnabled } = status.debugApiConnection const { checkState, isEnabled } = status.debugApiConnection
if (!isEnabled) return null if (!isEnabled) return null
@@ -26,12 +26,12 @@ export default function NodeConnectionCheck(): ReactElement | null {
> >
<ExpandableListItemNote> <ExpandableListItemNote>
{checkState === CheckState.OK {checkState === CheckState.OK
? 'The connection to the Bee nodes debug API has been successful' ? 'The connection to the Bee node debug API has been successful'
: 'We cannot connect to your nodes debug API. Please check the following to troubleshoot your issue.'} : 'Could not connect to your Bee node debug API.'}
</ExpandableListItemNote> </ExpandableListItemNote>
<ExpandableListItemInput label="Bee Debug API" value={apiDebugUrl} onConfirm={setDebugApiUrl} /> <ExpandableListItemInput label="Bee Debug API" value={apiDebugUrl} onConfirm={setDebugApiUrl} />
{checkState === CheckState.ERROR && ( {checkState === CheckState.ERROR && !isDesktop && (
<ExpandableList level={1} label="Troubleshoot"> <ExpandableList level={1} label="Troubleshoot">
<ExpandableListItem <ExpandableListItem
label={ label={
@@ -0,0 +1,32 @@
import { ReactElement, useContext } from 'react'
import ExpandableList from '../../../components/ExpandableList'
import ExpandableListItem from '../../../components/ExpandableListItem'
import ExpandableListItemNote from '../../../components/ExpandableListItemNote'
import StatusIcon from '../../../components/StatusIcon'
import { useBeeDesktop } from '../../../hooks/apiHooks'
import { CheckState } from '../../../providers/Bee'
import { Context as SettingsContext } from '../../../providers/Settings'
export default function DesktopConnectionCheck(): ReactElement | null {
const { isDesktop, desktopUrl } = useContext(SettingsContext)
const { reachable } = useBeeDesktop(isDesktop, desktopUrl)
return (
<ExpandableList
label={
<>
<StatusIcon checkState={reachable ? CheckState.OK : CheckState.ERROR} isLoading={false} /> Connection to Swarm
Desktop
</>
}
>
<ExpandableListItemNote>
{reachable
? 'The connection to the Swarm Desktop API has been successful'
: 'Could not connect to the Swarm Desktop API'}
</ExpandableListItemNote>
<ExpandableListItem label="Swarm Desktop API" value={desktopUrl} />
</ExpandableList>
)
}
@@ -10,7 +10,7 @@ import StatusIcon from '../../../components/StatusIcon'
import { CheckState, Context } from '../../../providers/Bee' import { CheckState, Context } from '../../../providers/Bee'
export default function NodeConnectionCheck(): ReactElement | null { export default function NodeConnectionCheck(): ReactElement | null {
const { setApiUrl, apiUrl } = useContext(SettingsContext) const { setApiUrl, apiUrl, isDesktop } = useContext(SettingsContext)
const { status, isLoading } = useContext(Context) const { status, isLoading } = useContext(Context)
const { isEnabled, checkState } = status.apiConnection const { isEnabled, checkState } = status.apiConnection
@@ -26,11 +26,11 @@ export default function NodeConnectionCheck(): ReactElement | null {
> >
<ExpandableListItemNote> <ExpandableListItemNote>
{checkState === CheckState.OK {checkState === CheckState.OK
? 'The connection to the Bee nodes API has been successful' ? 'The connection to the Bee node API has been successful'
: 'Could not connect to your Bee nodes API. Please check the troubleshoot below on how you may resolve it.'} : 'Could not connect to your Bee node API.'}
</ExpandableListItemNote> </ExpandableListItemNote>
<ExpandableListItemInput label="Bee API" value={apiUrl} onConfirm={setApiUrl} /> <ExpandableListItemInput label="Bee API" value={apiUrl} onConfirm={setApiUrl} />
{checkState === CheckState.ERROR && ( {checkState === CheckState.ERROR && !isDesktop && (
<ExpandableList level={1} label="Troubleshoot"> <ExpandableList level={1} label="Troubleshoot">
<ExpandableListItem <ExpandableListItem
label={ label={
@@ -8,8 +8,9 @@ import { CheckState, Context } from '../../../providers/Bee'
export default function PeerConnection(): ReactElement | null { export default function PeerConnection(): ReactElement | null {
const { status, isLoading, topology } = useContext(Context) const { status, isLoading, topology } = useContext(Context)
const { isEnabled, checkState } = status.topology const { isEnabled, checkState } = status.topology
const { checkState: debugApiCheckState } = status.debugApiConnection
if (!isEnabled) return null if (!isEnabled || debugApiCheckState === CheckState.ERROR) return null
let text: ReactNode let text: ReactNode
switch (checkState) { switch (checkState) {
+8 -3
View File
@@ -1,6 +1,8 @@
import type { ReactElement } from 'react' import { Context } from '../../providers/Settings'
import { ReactElement, useContext } from 'react'
import DebugConnectionCheck from './SetupSteps/DebugConnectionCheck' import DebugConnectionCheck from './SetupSteps/DebugConnectionCheck'
import DesktopConnection from './SetupSteps/DesktopConnectionCheck'
import NodeConnectionCheck from './SetupSteps/NodeConnectionCheck' import NodeConnectionCheck from './SetupSteps/NodeConnectionCheck'
import VersionCheck from './SetupSteps/VersionCheck' import VersionCheck from './SetupSteps/VersionCheck'
import EthereumConnectionCheck from './SetupSteps/EthereumConnectionCheck' import EthereumConnectionCheck from './SetupSteps/EthereumConnectionCheck'
@@ -8,13 +10,16 @@ import ChequebookDeployFund from './SetupSteps/ChequebookDeployFund'
import PeerConnection from './SetupSteps/PeerConnection' import PeerConnection from './SetupSteps/PeerConnection'
export default function NodeSetupWorkflow(): ReactElement { export default function NodeSetupWorkflow(): ReactElement {
const { isDesktop } = useContext(Context)
return ( return (
<div> <div>
{isDesktop && <DesktopConnection />}
<NodeConnectionCheck />
<DebugConnectionCheck /> <DebugConnectionCheck />
<VersionCheck /> {!isDesktop && <VersionCheck />}
<EthereumConnectionCheck /> <EthereumConnectionCheck />
<ChequebookDeployFund /> <ChequebookDeployFund />
<NodeConnectionCheck />
<PeerConnection /> <PeerConnection />
</div> </div>
) )
+3 -4
View File
@@ -1,9 +1,10 @@
import { BeeModes } from '@ethersphere/bee-js'
import { Box, Typography } from '@material-ui/core' import { Box, Typography } from '@material-ui/core'
import { useSnackbar } from 'notistack' import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react' import { ReactElement, useContext, useEffect, useState } from 'react'
import Check from 'remixicon-react/CheckLineIcon'
import ArrowDown from 'remixicon-react/ArrowDownLineIcon'
import { useNavigate, useParams } from 'react-router' import { useNavigate, useParams } from 'react-router'
import ArrowDown from 'remixicon-react/ArrowDownLineIcon'
import Check from 'remixicon-react/CheckLineIcon'
import ExpandableListItem from '../../components/ExpandableListItem' import ExpandableListItem from '../../components/ExpandableListItem'
import ExpandableListItemKey from '../../components/ExpandableListItemKey' import ExpandableListItemKey from '../../components/ExpandableListItemKey'
import { HistoryHeader } from '../../components/HistoryHeader' import { HistoryHeader } from '../../components/HistoryHeader'
@@ -18,7 +19,6 @@ import { ROUTES } from '../../routes'
import { sleepMs } from '../../utils' import { sleepMs } from '../../utils'
import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop' import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop'
import { ResolvedWallet } from '../../utils/wallet' import { ResolvedWallet } from '../../utils/wallet'
import { BeeModes } from '@ethersphere/bee-js'
export function GiftCardFund(): ReactElement { export function GiftCardFund(): ReactElement {
const { nodeAddresses, nodeInfo } = useContext(BeeContext) const { nodeAddresses, nodeInfo } = useContext(BeeContext)
@@ -52,7 +52,6 @@ export function GiftCardFund(): ReactElement {
await sleepMs(5_000) await sleepMs(5_000)
await upgradeToLightNode(desktopUrl, rpcProviderUrl) await upgradeToLightNode(desktopUrl, rpcProviderUrl)
await restartBeeNode(desktopUrl) await restartBeeNode(desktopUrl)
enqueueSnackbar('Upgraded to light node', { variant: 'success' })
navigate(ROUTES.RESTART_LIGHT) navigate(ROUTES.RESTART_LIGHT)
} catch (error) { } catch (error) {
console.error(error) // eslint-disable-line console.error(error) // eslint-disable-line
+134 -47
View File
@@ -1,6 +1,5 @@
import { BeeModes } from '@ethersphere/bee-js' import { BeeModes } from '@ethersphere/bee-js'
import { Box, Typography } from '@material-ui/core' import { Box, Typography } from '@material-ui/core'
import BigNumber from 'bignumber.js'
import { useSnackbar } from 'notistack' import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react' import { ReactElement, useContext, useEffect, useState } from 'react'
import { useNavigate } from 'react-router' import { useNavigate } from 'react-router'
@@ -14,36 +13,42 @@ import { Loading } from '../../components/Loading'
import { SwarmButton } from '../../components/SwarmButton' import { SwarmButton } from '../../components/SwarmButton'
import { SwarmDivider } from '../../components/SwarmDivider' import { SwarmDivider } from '../../components/SwarmDivider'
import { SwarmTextInput } from '../../components/SwarmTextInput' import { SwarmTextInput } from '../../components/SwarmTextInput'
import { BzzToken } from '../../models/BzzToken' import { BzzToken, BZZ_DECIMAL_PLACES } from '../../models/BzzToken'
import { DaiToken } from '../../models/DaiToken' import { DaiToken } from '../../models/DaiToken'
import { Context as BalanceProvider } from '../../providers/WalletBalance'
import { Context as BeeContext } from '../../providers/Bee' import { Context as BeeContext } from '../../providers/Bee'
import { Context as SettingsContext } from '../../providers/Settings' import { Context as SettingsContext } from '../../providers/Settings'
import { Context as BalanceProvider } from '../../providers/WalletBalance'
import { ROUTES } from '../../routes' import { ROUTES } from '../../routes'
import { sleepMs } from '../../utils' import { sleepMs } from '../../utils'
import { getBzzPriceAsDai, performSwap, restartBeeNode, upgradeToLightNode } from '../../utils/desktop' import {
getBzzPriceAsDai,
getDesktopConfiguration,
performSwap,
restartBeeNode,
upgradeToLightNode,
} from '../../utils/desktop'
import { Rpc } from '../../utils/rpc'
import { isSwapError, SwapError, wrapWithSwapError } from '../../utils/SwapError'
import { TopUpProgressIndicator } from './TopUpProgressIndicator' import { TopUpProgressIndicator } from './TopUpProgressIndicator'
const MINIMUM_XDAI = '0.1' const MINIMUM_XDAI = '0.1'
const MINIMUM_XBZZ = '0.1' const MINIMUM_XBZZ = '0.1'
const GENERIC_SWAP_FAILED_ERROR_MESSAGE = 'Failed to swap. The full error is printed to the console.'
interface Props { interface Props {
header: string header: string
} }
function isPositiveDecimal(value: string): boolean {
try {
return new BigNumber(value).isPositive()
} catch {
return false
}
}
export function Swap({ header }: Props): ReactElement { export function Swap({ header }: Props): ReactElement {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [hasSwapped, setSwapped] = useState(false) const [hasSwapped, setSwapped] = useState(false)
const [userInputSwap, setUserInputSwap] = useState<string | null>(null) const [userInputSwap, setUserInputSwap] = useState<string | null>(null)
const [price, setPrice] = useState(DaiToken.fromDecimal('0.6', 18)) const [price, setPrice] = useState(DaiToken.fromDecimal('0.6'))
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 { rpcProviderUrl, isDesktop, desktopUrl } = useContext(SettingsContext) const { rpcProviderUrl, isDesktop, desktopUrl } = useContext(SettingsContext)
const { nodeAddresses, nodeInfo } = useContext(BeeContext) const { nodeAddresses, nodeInfo } = useContext(BeeContext)
@@ -52,38 +57,77 @@ export function Swap({ header }: Props): ReactElement {
const navigate = useNavigate() const navigate = useNavigate()
const { enqueueSnackbar } = useSnackbar() const { enqueueSnackbar } = useSnackbar()
// Fetch current price of BZZ
useEffect(() => { useEffect(() => {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
getBzzPriceAsDai(desktopUrl).then(setPrice).catch(console.error) getBzzPriceAsDai(desktopUrl).then(setPrice).catch(console.error)
}, [desktopUrl]) }, [desktopUrl])
if (!balance || !nodeAddresses) { // Set the initial xDAI to swap
useEffect(() => {
if (!balance || userInputSwap) {
return
}
const minimumOptimalValue = DaiToken.fromDecimal('1').plusBaseUnits(MINIMUM_XDAI).toDecimal
if (balance.dai.toDecimal.isGreaterThanOrEqualTo(minimumOptimalValue)) {
// Balance has at least 1 + MINIMUM_XDAI xDai
setDaiToSwap(balance.dai.minusBaseUnits('1'))
} else {
// Balance is low, halve the amount
setDaiToSwap(new DaiToken(balance.dai.toBigNumber.dividedToIntegerBy(2)))
}
}, [balance, userInputSwap])
// Set the xDAI to swap based on user input
useEffect(() => {
setError(null)
try {
if (userInputSwap) {
const dai = DaiToken.fromDecimal(userInputSwap)
setDaiToSwap(dai)
if (dai.toDecimal.lte(0)) {
setError('xDAI to swap must be a positive number')
}
}
} catch {
setError('Cannot parse xDAI amount')
}
}, [userInputSwap])
// Calculate the amount of tokens after swap
useEffect(() => {
if (!balance || !daiToSwap || error) {
return
}
const daiAfterSwap = new DaiToken(balance.dai.toBigNumber.minus(daiToSwap.toBigNumber))
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))
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!`)
}
}, [error, balance, daiToSwap, price])
if (!balance || !nodeAddresses || !daiToSwap || !bzzAfterSwap || !daiAfterSwap) {
return <Loading /> return <Loading />
} }
const optimalSwap = balance.dai.minusBaseUnits('1')
const lowAmountSwap = new DaiToken(balance.dai.toBigNumber.dividedToIntegerBy(2))
let daiToSwap: DaiToken
if (userInputSwap && isPositiveDecimal(userInputSwap)) {
daiToSwap = DaiToken.fromDecimal(userInputSwap, 18)
} else {
daiToSwap = lowAmountSwap.toBigNumber.gt(optimalSwap.toBigNumber) ? lowAmountSwap : optimalSwap
}
const daiAfterSwap = new DaiToken(balance.dai.toBigNumber.minus(daiToSwap.toBigNumber))
const bzzAfterSwap = new BzzToken(daiToSwap.toBigNumber.dividedBy(100).dividedToIntegerBy(price.toDecimal))
const canUpgradeToLightNode = isDesktop && nodeInfo?.beeMode === BeeModes.ULTRA_LIGHT const canUpgradeToLightNode = isDesktop && nodeInfo?.beeMode === BeeModes.ULTRA_LIGHT
async function restart() { async function restart() {
try { try {
await sleepMs(5_000) await sleepMs(5_000)
await upgradeToLightNode(desktopUrl, rpcProviderUrl)
await restartBeeNode(desktopUrl) await restartBeeNode(desktopUrl)
enqueueSnackbar('Upgraded to light node', { variant: 'success' })
navigate(ROUTES.RESTART_LIGHT) navigate(ROUTES.RESTART_LIGHT)
} catch (error) { } catch (error) {
console.error(error) // eslint-disable-line console.error(error) // eslint-disable-line
@@ -91,21 +135,71 @@ export function Swap({ header }: Props): ReactElement {
} }
} }
async function sendSwapRequest(daiToSwap: DaiToken) {
try {
await performSwap(desktopUrl, daiToSwap.toString)
} catch (error) {
// eslint-disable-next-line no-console
console.error(error)
throw error
}
}
async function performSwapWithChecks(daiToSwap: DaiToken) {
if (!localStorage.getItem('apiKey')) {
throw new SwapError('API key is not set, reopen dashboard through Swarm Desktop')
}
let desktopConfiguration = await wrapWithSwapError(
getDesktopConfiguration(desktopUrl),
'Unable to reach Desktop API, Swarm Desktop may not be running',
)
if (canUpgradeToLightNode) {
desktopConfiguration = await wrapWithSwapError(
upgradeToLightNode(desktopUrl, rpcProviderUrl),
'Failed to update the configuration file with the new swap values using the Desktop API',
)
}
if (!desktopConfiguration['swap-endpoint']) {
throw new SwapError('Swap endpoint is not configured in Swarm Desktop')
}
await wrapWithSwapError(
Rpc.getNetworkChainId(desktopConfiguration['swap-endpoint']),
`Swap endpoint not reachable at ${desktopConfiguration['swap-endpoint']}`,
)
await wrapWithSwapError(sendSwapRequest(daiToSwap), GENERIC_SWAP_FAILED_ERROR_MESSAGE)
}
async function onSwap() { async function onSwap() {
if (hasSwapped) { if (hasSwapped || !daiToSwap) {
return return
} }
setLoading(true) setLoading(true)
setSwapped(true) setSwapped(true)
try { try {
await performSwap(desktopUrl, daiToSwap.toString) await performSwapWithChecks(daiToSwap)
enqueueSnackbar('Successfully swapped', { variant: 'success' }) const message = canUpgradeToLightNode
? 'Successfully swapped. Beginning light node upgrade...'
: '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) { } catch (error) {
console.error(error) // eslint-disable-line if (isSwapError(error)) {
enqueueSnackbar(`Failed to swap: ${error}`, { variant: 'error' }) // we have a custom and user friendly error message
enqueueSnackbar(error.snackbarMessage, { variant: 'error' })
if (error.originalError) {
console.error(error.originalError) // eslint-disable-line
}
} else {
// we have an unexpected error
enqueueSnackbar(`${GENERIC_SWAP_FAILED_ERROR_MESSAGE} ${error}`, { variant: 'error' })
console.error(error) // eslint-disable-line
}
} finally { } finally {
balance?.refresh() balance?.refresh()
setLoading(false) setLoading(false)
@@ -136,18 +230,13 @@ export function Swap({ header }: Props): ReactElement {
</Box> </Box>
<Box mb={4}> <Box mb={4}>
<SwarmTextInput <SwarmTextInput
label="Amount to swap" label="xDAI to swap"
defaultValue={`${daiToSwap.toSignificantDigits(4)} xDAI`} defaultValue={daiToSwap.toSignificantDigits(4)}
placeholder={`${daiToSwap.toSignificantDigits(4)} xDAI`} placeholder={daiToSwap.toSignificantDigits(4)}
name="x" name="x"
onChange={event => setUserInputSwap(event.target.value)} onChange={event => setUserInputSwap(event.target.value)}
/> />
{daiAfterSwap.toDecimal.lt(MINIMUM_XDAI) ? ( {error && <Typography>{error}</Typography>}
<Typography>Must keep at least {MINIMUM_XDAI} xDAI after swap!</Typography>
) : null}
{bzzAfterSwap.toDecimal.lt(MINIMUM_XBZZ) ? (
<Typography>Must have at least {MINIMUM_XBZZ} xBZZ after swap!</Typography>
) : null}
</Box> </Box>
<Box mb={4}> <Box mb={4}>
<ArrowDown size={24} color="#aaaaaa" /> <ArrowDown size={24} color="#aaaaaa" />
@@ -171,9 +260,7 @@ export function Swap({ header }: Props): ReactElement {
<SwarmButton <SwarmButton
iconType={Check} iconType={Check}
onClick={onSwap} onClick={onSwap}
disabled={ disabled={hasSwapped || loading || error !== null}
hasSwapped || loading || daiAfterSwap.toDecimal.lt(MINIMUM_XDAI) || bzzAfterSwap.toDecimal.lt(MINIMUM_XBZZ)
}
loading={loading} loading={loading}
> >
{canUpgradeToLightNode ? 'Swap Now and Upgrade' : 'Swap Now'} {canUpgradeToLightNode ? 'Swap Now and Upgrade' : 'Swap Now'}
+8 -9
View File
@@ -1,23 +1,23 @@
import { BeeModes } from '@ethersphere/bee-js'
import { Box, createStyles, Grid, makeStyles, Typography } from '@material-ui/core' import { Box, createStyles, Grid, makeStyles, Typography } from '@material-ui/core'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react' import { ReactElement, useContext, useState } from 'react'
import { useNavigate } from 'react-router'
import BankCard from 'remixicon-react/BankCard2LineIcon'
import Check from 'remixicon-react/CheckLineIcon' import Check from 'remixicon-react/CheckLineIcon'
import Download from 'remixicon-react/DownloadLineIcon' import Download from 'remixicon-react/DownloadLineIcon'
import BankCard from 'remixicon-react/BankCard2LineIcon'
import MoneyDollarCircle from 'remixicon-react/MoneyDollarCircleLineIcon'
import Gift from 'remixicon-react/GiftLineIcon' import Gift from 'remixicon-react/GiftLineIcon'
import { useNavigate } from 'react-router' import MoneyDollarCircle from 'remixicon-react/MoneyDollarCircleLineIcon'
import ExpandableListItemActions from '../../components/ExpandableListItemActions' import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import { HistoryHeader } from '../../components/HistoryHeader' import { HistoryHeader } from '../../components/HistoryHeader'
import { Loading } from '../../components/Loading'
import { SwarmButton } from '../../components/SwarmButton' import { SwarmButton } from '../../components/SwarmButton'
import { ROUTES } from '../../routes' import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { CheckState, Context as BeeContext } from '../../providers/Bee' import { CheckState, Context as BeeContext } from '../../providers/Bee'
import { Context as SettingsContext } from '../../providers/Settings' import { Context as SettingsContext } from '../../providers/Settings'
import { Context as BalanceProvider } from '../../providers/WalletBalance' import { Context as BalanceProvider } from '../../providers/WalletBalance'
import { BeeModes } from '@ethersphere/bee-js' import { ROUTES } from '../../routes'
import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop' import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop'
import { Loading } from '../../components/Loading'
import { useSnackbar } from 'notistack'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
const useStyles = makeStyles(() => const useStyles = makeStyles(() =>
createStyles({ createStyles({
@@ -57,7 +57,6 @@ export default function TopUp(): ReactElement {
try { try {
await upgradeToLightNode(desktopUrl, rpcProviderUrl) await upgradeToLightNode(desktopUrl, rpcProviderUrl)
await restartBeeNode(desktopUrl) await restartBeeNode(desktopUrl)
enqueueSnackbar('Upgraded to light node', { variant: 'success' })
navigate(ROUTES.RESTART_LIGHT) navigate(ROUTES.RESTART_LIGHT)
} catch (error) { } catch (error) {
console.error(error) // eslint-disable-line console.error(error) // eslint-disable-line
+38 -12
View File
@@ -25,6 +25,7 @@ export enum CheckState {
OK = 'OK', OK = 'OK',
WARNING = 'Warning', WARNING = 'Warning',
ERROR = 'Error', ERROR = 'Error',
STARTING = 'Starting',
} }
interface StatusItem { interface StatusItem {
@@ -52,6 +53,7 @@ interface ContextInterface {
error: Error | null error: Error | null
apiHealth: boolean apiHealth: boolean
debugApiHealth: Health | null debugApiHealth: Health | null
debugApiReadiness: boolean
nodeAddresses: NodeAddresses | null nodeAddresses: NodeAddresses | null
nodeInfo: NodeInfo | null nodeInfo: NodeInfo | null
topology: Topology | null topology: Topology | null
@@ -89,6 +91,7 @@ const initialValues: ContextInterface = {
error: null, error: null,
apiHealth: false, apiHealth: false,
debugApiHealth: null, debugApiHealth: null,
debugApiReadiness: false,
nodeAddresses: null, nodeAddresses: null,
nodeInfo: null, nodeInfo: null,
topology: null, topology: null,
@@ -117,25 +120,26 @@ interface Props {
function getStatus( function getStatus(
debugApiHealth: Health | null, debugApiHealth: Health | null,
nodeAddresses: NodeAddresses | null, debugApiReadiness: boolean,
nodeInfo: NodeInfo | null, nodeInfo: NodeInfo | null,
apiHealth: boolean, apiHealth: boolean,
topology: Topology | null, topology: Topology | null,
chequebookAddress: ChequebookAddressResponse | null, chequebookAddress: ChequebookAddressResponse | null,
chequebookBalance: ChequebookBalance | null, chequebookBalance: ChequebookBalance | null,
error: Error | null, error: Error | null,
isDesktop: boolean,
): Status { ): Status {
const status: Status = { ...initialValues.status } const status: Status = { ...initialValues.status }
// Version check // Version check
status.version.isEnabled = true status.version.isEnabled = !isDesktop
status.version.checkState = status.version.checkState =
debugApiHealth && debugApiHealth &&
semver.satisfies(debugApiHealth.version, PackageJson.engines.bee, { semver.satisfies(debugApiHealth.version, PackageJson.engines.bee, {
includePrerelease: true, includePrerelease: true,
}) })
? CheckState.OK ? CheckState.OK
: CheckState.ERROR : CheckState.WARNING
// Blockchain connection check // Blockchain connection check
status.blockchainConnection.isEnabled = true status.blockchainConnection.isEnabled = true
@@ -169,27 +173,41 @@ function getStatus(
else status.chequebook.checkState = CheckState.OK else status.chequebook.checkState = CheckState.OK
} }
// Determine overall status status.all = determineOverallStatus(debugApiHealth, debugApiReadiness, status)
if (Object.values(status).some(({ isEnabled, checkState }) => isEnabled && checkState === CheckState.ERROR)) {
status.all = CheckState.ERROR return status
}
function determineOverallStatus(debugApiHealth: Health | null, debugApiReadiness: boolean, status: Status): CheckState {
if (debugApiHealth?.status === 'ok' && !debugApiReadiness) {
return CheckState.STARTING
} else if (Object.values(status).some(({ isEnabled, checkState }) => isEnabled && checkState === CheckState.ERROR)) {
return CheckState.ERROR
} else if ( } else if (
Object.values(status).some(({ isEnabled, checkState }) => isEnabled && checkState === CheckState.WARNING) Object.values(status).some(({ isEnabled, checkState }) => isEnabled && checkState === CheckState.WARNING)
) { ) {
status.all = CheckState.WARNING return CheckState.WARNING
} else { } else {
status.all = CheckState.OK return CheckState.OK
} }
return status
} }
// This does not need to be exposed and works much better as variable than state variable which may trigger some unnecessary re-renders // This does not need to be exposed and works much better as variable than state variable which may trigger some unnecessary re-renders
let isRefreshing = false let isRefreshing = false
export function Provider({ children }: Props): ReactElement { interface InitialSettings {
isDesktop?: boolean
}
interface Props extends InitialSettings {
children: ReactChild
}
export function Provider({ children, isDesktop }: Props): ReactElement {
const { beeApi, beeDebugApi } = useContext(SettingsContext) const { beeApi, beeDebugApi } = useContext(SettingsContext)
const [apiHealth, setApiHealth] = useState<boolean>(false) const [apiHealth, setApiHealth] = useState<boolean>(false)
const [debugApiHealth, setDebugApiHealth] = useState<Health | null>(null) const [debugApiHealth, setDebugApiHealth] = useState<Health | null>(null)
const [debugApiReadiness, setDebugApiReadiness] = useState(false)
const [nodeAddresses, setNodeAddresses] = useState<NodeAddresses | null>(null) const [nodeAddresses, setNodeAddresses] = useState<NodeAddresses | null>(null)
const [nodeInfo, setNodeInfo] = useState<NodeInfo | null>(null) const [nodeInfo, setNodeInfo] = useState<NodeInfo | null>(null)
const [topology, setNodeTopology] = useState<Topology | null>(null) const [topology, setNodeTopology] = useState<Topology | null>(null)
@@ -299,6 +317,12 @@ export function Provider({ children }: Props): ReactElement {
.then(setDebugApiHealth) .then(setDebugApiHealth)
.catch(() => setDebugApiHealth(null)), .catch(() => setDebugApiHealth(null)),
// Debug API readiness
beeDebugApi
.getReadiness({ timeout: TIMEOUT })
.then(setDebugApiReadiness)
.catch(() => setDebugApiReadiness(false)),
// Node Addresses // Node Addresses
beeDebugApi beeDebugApi
.getNodeAddresses({ timeout: TIMEOUT }) .getNodeAddresses({ timeout: TIMEOUT })
@@ -381,13 +405,14 @@ export function Provider({ children }: Props): ReactElement {
const status = getStatus( const status = getStatus(
debugApiHealth, debugApiHealth,
nodeAddresses, debugApiReadiness,
nodeInfo, nodeInfo,
apiHealth, apiHealth,
topology, topology,
chequebookAddress, chequebookAddress,
chequebookBalance, chequebookBalance,
error, error,
Boolean(isDesktop),
) )
useEffect(() => { useEffect(() => {
@@ -426,6 +451,7 @@ export function Provider({ children }: Props): ReactElement {
error, error,
apiHealth, apiHealth,
debugApiHealth, debugApiHealth,
debugApiReadiness,
nodeAddresses, nodeAddresses,
nodeInfo, nodeInfo,
topology, topology,
+10
View File
@@ -0,0 +1,10 @@
export class AuthError extends Error {
constructor() {
super('Bad API key')
this.name = 'AuthError'
}
}
export function isAuthError(error: unknown): error is AuthError {
return error instanceof Error && error.name === 'AuthError'
}
+26
View File
@@ -0,0 +1,26 @@
import { isAuthError } from './AuthError'
export class SwapError extends Error {
snackbarMessage: string
originalError?: Error
constructor(snackbarMessage: string, error?: Error) {
super(error?.message || snackbarMessage)
this.name = 'SwapError'
this.originalError = error
this.snackbarMessage = snackbarMessage
}
}
export function isSwapError(error: unknown): error is SwapError {
return error instanceof Error && error.name === 'SwapError'
}
export function wrapWithSwapError<T>(promise: Promise<T>, snackbarMessage: string): Promise<T> {
return promise.catch((error: Error) => {
if (isAuthError(error)) {
throw new SwapError('Bad API key, reopen dashboard through Swarm Desktop', error)
}
throw new SwapError(snackbarMessage, error)
})
}
+27 -7
View File
@@ -1,17 +1,33 @@
import axios from 'axios' import axios from 'axios'
import { BEE_DESKTOP_LATEST_RELEASE_PAGE_API } from '../constants'
import { DaiToken } from '../models/DaiToken' import { DaiToken } from '../models/DaiToken'
import { Token } from '../models/Token' import { Token } from '../models/Token'
import { postJson } from './net' import { getJson, postJson } from './net'
import { BEE_DESKTOP_LATEST_RELEASE_PAGE_API } from '../constants'
export interface BeeConfig {
'api-addr': string
'debug-api-addr': string
'debug-api-enable': boolean
password: string
'swap-enable': boolean
'swap-initial-deposit': bigint
mainnet: boolean
'full-node': boolean
'cors-allowed-origins': string
'resolver-options': string
'use-postage-snapshot': boolean
'data-dir': string
'swap-endpoint'?: string
}
export async function getBzzPriceAsDai(desktopUrl: string): Promise<Token> { export async function getBzzPriceAsDai(desktopUrl: string): Promise<Token> {
const response = await axios.get(`${desktopUrl}/price`) const response = await axios.get(`${desktopUrl}/price`)
return DaiToken.fromDecimal(response.data, 18) return DaiToken.fromDecimal(response.data)
} }
export async function upgradeToLightNode(desktopUrl: string, rpcProvider: string): Promise<void> { export function upgradeToLightNode(desktopUrl: string, rpcProvider: string): Promise<BeeConfig> {
await updateDesktopConfiguration(desktopUrl, { return updateDesktopConfiguration(desktopUrl, {
'swap-enable': true, 'swap-enable': true,
'swap-endpoint': rpcProvider, 'swap-endpoint': rpcProvider,
}) })
@@ -23,8 +39,12 @@ export async function setJsonRpcInDesktop(desktopUrl: string, value: string): Pr
}) })
} }
async function updateDesktopConfiguration(desktopUrl: string, values: Record<string, unknown>): Promise<void> { export function getDesktopConfiguration(desktopUrl: string): Promise<BeeConfig> {
await postJson(`${desktopUrl}/config`, values) return getJson(`${desktopUrl}/config`)
}
function updateDesktopConfiguration(desktopUrl: string, values: Record<string, unknown>): Promise<BeeConfig> {
return postJson(`${desktopUrl}/config`, values)
} }
export async function restartBeeNode(desktopUrl: string): Promise<void> { export async function restartBeeNode(desktopUrl: string): Promise<void> {
+11 -1
View File
@@ -1,12 +1,13 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import axios from 'axios' import axios from 'axios'
import { AuthError } from './AuthError'
export function getJson<T extends Record<string, any>>(url: string): Promise<T> { export function getJson<T extends Record<string, any>>(url: string): Promise<T> {
return sendRequest(url, 'GET') as Promise<T> return sendRequest(url, 'GET') as Promise<T>
} }
export function postJson<T extends Record<string, any>>(url: string, data?: T): Promise<T> { export function postJson<T extends Record<string, any>>(url: string, data?: Record<string, any>): Promise<T> {
return sendRequest(url, 'POST', data) as Promise<T> return sendRequest(url, 'POST', data) as Promise<T>
} }
@@ -27,6 +28,15 @@ export async function sendRequest(
method, method,
headers, headers,
data, data,
}).catch(error => {
if (error?.response?.status === 401) {
throw new AuthError()
}
if (error?.response?.data) {
throw Error(`Request ${method} ${url} failed: ${JSON.stringify(error.response.data)}`)
}
throw error
}) })
return response.data return response.data
+12 -1
View File
@@ -2,6 +2,16 @@ import { debounce } from '@material-ui/core'
import { Contract, providers, Wallet, BigNumber as BN } from 'ethers' import { Contract, providers, Wallet, BigNumber as BN } from 'ethers'
import { bzzABI, BZZ_TOKEN_ADDRESS } from './bzz-abi' import { bzzABI, BZZ_TOKEN_ADDRESS } from './bzz-abi'
const NETWORK_ID = 100
async function getNetworkChainId(url: string): Promise<number> {
const provider = new providers.JsonRpcProvider(url, NETWORK_ID)
await provider.ready
const network = await provider.getNetwork()
return network.chainId
}
async function eth_getBalance(address: string, provider: providers.JsonRpcProvider): Promise<string> { async function eth_getBalance(address: string, provider: providers.JsonRpcProvider): Promise<string> {
if (!address.startsWith('0x')) { if (!address.startsWith('0x')) {
address = `0x${address}` address = `0x${address}`
@@ -78,7 +88,7 @@ export async function sendBzzTransaction(
} }
async function makeReadySigner(privateKey: string, jsonRpcProvider: string) { async function makeReadySigner(privateKey: string, jsonRpcProvider: string) {
const provider = new providers.JsonRpcProvider(jsonRpcProvider, 100) const provider = new providers.JsonRpcProvider(jsonRpcProvider, NETWORK_ID)
await provider.ready await provider.ready
const signer = new Wallet(privateKey, provider) const signer = new Wallet(privateKey, provider)
@@ -86,6 +96,7 @@ async function makeReadySigner(privateKey: string, jsonRpcProvider: string) {
} }
export const Rpc = { export const Rpc = {
getNetworkChainId,
sendNativeTransaction, sendNativeTransaction,
sendBzzTransaction, sendBzzTransaction,
_eth_getBalance: eth_getBalance, _eth_getBalance: eth_getBalance,