Compare commits

..

4 Commits

Author SHA1 Message Date
OpenCode 29b138346e fix: adapt redistribution feature for v0.35.1
- Use @mui/material import in redistribution page

- Configure pnpm workspace build script approvals
2026-06-15 09:55:16 +00:00
woggioni b5b0d37e94 added new page with node redistribution statistics 2026-06-15 09:47:25 +00:00
bee-worker b3137fbef1 chore(master): release 0.35.1 (#732)
* chore(master): release 0.35.1
* docs: update changelog.md for v0.53.1 bug fixes

---------

Co-authored-by: Ferenc Sárai <sarai.ferenc@gmail.com>
2026-04-10 11:25:29 +02:00
Ferenc Sárai e6f882d7e1 fix: for upcoming v0.53.1 (#731)
* fix: swap error caused by invalid id and batchcount
* fix: enhance creation messages for admin drive and user drives (#238)
* fix: enhance creation messages for admin drive and user drives
* fix: update creation message to indicate longer processing time
* fix: identity and wallet creation (#240)
* fix: asset preview types
* fix: fm search unicode text
* fix: feed identity and stamp usage
* fix: ui display changes (#239)
* fix: ui layout changes

* fix: stamp buy and dilute (#242)

fix: vite polyfill warning for stream
refactor: stamp depth and amount validation

* fix: spdv-917 (#243)

* fix: spdv-917

* refactor: spdv-917

* fix: add syncing message for Bee node and update page state handling spdv-955 (#244)

* fix: spdv-1037 (#245)

* fix: spdv-1038 (#246)

* fix: spdv-1038

* refactor: spdv-1038

* fix: validate stamp before every upgrade click (#247)

* fix: validate stamp before every upgrade click
---------

Co-authored-by: Roland Seres <roland.seres90@gmail.com>

* fix: use tochecksum() and toplurbigint() for ethers v6 compatibility (#248)

---------

Co-authored-by: Balint Ujvari <balint.ujvari@solarpunk.buzz>
Co-authored-by: Bálint Ujvári <58116288+bosi95@users.noreply.github.com>
Co-authored-by: rolandlor <33499567+rolandlor@users.noreply.github.com>
Co-authored-by: Roland Seres <roland.seres90@gmail.com>
2026-04-10 10:59:20 +02:00
16 changed files with 259 additions and 16 deletions
+18
View File
@@ -1,5 +1,23 @@
# Changelog # Changelog
## [0.35.1](https://github.com/ethersphere/bee-dashboard/compare/v0.35.0...v0.35.1) (2026-04-10)
### Bug Fixes
- [`cb6854e`](https://github.com/ethersphere/bee-dashboard/commit/cb6854eb68ffe3064a39a171bc1e23f628ee93bb) fix: swap error caused by invalid id and batchcount
- [`bb93d5c`](https://github.com/ethersphere/bee-dashboard/commit/bb93d5c26fa5414c6423b87a3992e0f2e410e515) fix: enhance creation messages for admin drive and user drives [(#238)](https://github.com/ethersphere/bee-dashboard/issues/238)
- [`c08bf8a`](https://github.com/ethersphere/bee-dashboard/commit/d65da143d2200db653fe7a80a7891dacf4c2937e) fix: identity and wallet creation [(#240)](https://github.com/ethersphere/bee-dashboard/pull/240)
- [`d65da14`](https://github.com/ethersphere/bee-dashboard/commit/d65da143d2200db653fe7a80a7891dacf4c2937e) fix: ui display changes [(#239)](https://github.com/ethersphere/bee-dashboard/issues/239)
- [`c890f7c`](https://github.com/ethersphere/bee-dashboard/commit/c890f7c1e8e4d21f8d252b3e1a9c783982459adf) fix: stamp buy and dilute [(#242)](https://github.com/ethersphere/bee-dashboard/issues/242)
- [`b33b663`](https://github.com/ethersphere/bee-dashboard/commit/b33b6630c2b5830b0fdbfbcf14cadc3fa1225190) fix: standard mode postage stamp purchase [(#243)](https://github.com/ethersphere/bee-dashboard/issues/243)
- [`f943f7a`](https://github.com/ethersphere/bee-dashboard/commit/f943f7ad666de15ef780cb5adf736b533902eef7) fix: add syncing message for bee node and update page state [(#244)](https://github.com/ethersphere/bee-dashboard/pull/244)
- [`056188a`](https://github.com/ethersphere/bee-dashboard/commit/056188abedf3a8ac828b8eb10a71a3b823cd5e6e) fix: duplicated ttl (time to live) information [(#245)](https://github.com/ethersphere/bee-dashboard/issues/245)
- [`8b36556`](https://github.com/ethersphere/bee-dashboard/commit/8b36556502d316ac5bd7dba49ce34b594857d449) fix: misleading "bee node is syncing" message for ultra-light nodes in file manager [(#246)](https://github.com/ethersphere/bee-dashboard/pull/246)
- [`9732170`](https://github.com/ethersphere/bee-dashboard/commit/97321706c33fb02abe7e067e6d865a046051d68b) fix: validate stamp before every upgrade click [(#247)](https://github.com/ethersphere/bee-dashboard/issues/247)
- [`f52ed4a`](https://github.com/ethersphere/bee-dashboard/commit/f52ed4abb2bb5274b33430c1e8efadae6b3fa795) fix: use tochecksum() and toplurbigint for ethers v6 compatibility [(#248)](https://github.com/ethersphere/bee-dashboard/pull/248)
## [0.35.0](https://github.com/ethersphere/bee-dashboard/compare/v0.34.0...v0.35.0) (2026-04-02) ## [0.35.0](https://github.com/ethersphere/bee-dashboard/compare/v0.34.0...v0.35.0) (2026-04-02)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ethersphere/bee-dashboard", "name": "@ethersphere/bee-dashboard",
"version": "0.35.0", "version": "0.35.1",
"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",
"homepage": ".", "homepage": ".",
"bin": { "bin": {
+5
View File
@@ -0,0 +1,5 @@
allowBuilds:
'@parcel/watcher': false
esbuild: false
puppeteer: false
unrs-resolver: false
+73
View File
@@ -0,0 +1,73 @@
import { BZZ, DAI, RedistributionState } from '@ethersphere/bee-js'
import { useContext, useEffect, useState } from 'react'
import { Context } from '../providers/Settings'
import ExpandableListItem from './ExpandableListItem'
export function Redistribution() {
const { beeApi } = useContext(Context)
const [redistributionState, setRedistributionState] = useState<RedistributionState | null>(null)
useEffect(() => {
const interval = setInterval(() => {
if (!beeApi) {
return
}
beeApi.getRedistributionState().then(setRedistributionState).catch(console.error) // eslint-disable-line
}, 3_000)
return () => clearInterval(interval)
})
const formatDurationSeconds = (s?: number) => {
if (s === null || s === undefined) {
return '-'
} else {
return `${s} s`
}
}
const formatBzzAmount = (amount?: BZZ) => {
if (amount === null || amount === undefined) {
return '-'
} else {
return `${amount.toSignificantDigits(4)} xBZZ`
}
}
const formatDaiAmount = (amount?: DAI) => {
if (amount === null || amount === undefined) {
return '-'
} else {
return `${amount.toSignificantDigits(4)} xDAI`
}
}
return (
<>
<ExpandableListItem
label="Has sufficient funds"
value={redistributionState?.hasSufficientFunds?.toString() ?? '-'}
/>
<ExpandableListItem label="Fully synced" value={redistributionState?.isFullySynced?.toString() ?? '-'} />
<ExpandableListItem label="Frozen" value={redistributionState?.isFrozen?.toString() ?? '-'} />
<ExpandableListItem label="Phase" value={redistributionState?.phase ?? '-'} />
<ExpandableListItem label="Round" value={redistributionState?.round?.toString() ?? '-'} />
<ExpandableListItem
label="Last selected round"
value={redistributionState?.lastSelectedRound.toString() ?? '-'}
/>
<ExpandableListItem label="Last played round" value={redistributionState?.lastPlayedRound.toString() ?? '-'} />
<ExpandableListItem label="Last round won" value={redistributionState?.lastWonRound.toString() ?? '-'} />
<ExpandableListItem label="Last frozen round" value={redistributionState?.lastFrozenRound.toString() ?? '-'} />
<ExpandableListItem
label="Last sample duration"
value={formatDurationSeconds(redistributionState?.lastSampleDurationSeconds)}
/>
<ExpandableListItem label="Reward" value={formatBzzAmount(redistributionState?.reward)} />
<ExpandableListItem label="Fees" value={formatDaiAmount(redistributionState?.fees)} />
</>
)
}
+6
View File
@@ -4,6 +4,7 @@ import { ReactElement, useContext, useState } from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import FilesIcon from 'remixicon-react/ArrowUpDownLineIcon' import FilesIcon from 'remixicon-react/ArrowUpDownLineIcon'
import DocsIcon from 'remixicon-react/BookOpenLineIcon' import DocsIcon from 'remixicon-react/BookOpenLineIcon'
import ExchangeDollarLineIcon from 'remixicon-react/ExchangeDollarLineIcon'
import ExternalLinkIcon from 'remixicon-react/ExternalLinkLineIcon' import ExternalLinkIcon from 'remixicon-react/ExternalLinkLineIcon'
import FileManagerIcon from 'remixicon-react/FolderOpenLineIcon' import FileManagerIcon from 'remixicon-react/FolderOpenLineIcon'
import GithubIcon from 'remixicon-react/GithubFillIcon' import GithubIcon from 'remixicon-react/GithubFillIcon'
@@ -147,6 +148,11 @@ export default function SideBar(): ReactElement {
icon: AccountIcon, icon: AccountIcon,
pathMatcherSubstring: '/account/', pathMatcherSubstring: '/account/',
}, },
{
label: 'Redistribution',
path: ROUTES.REDISTRIBUTION,
icon: ExchangeDollarLineIcon,
},
{ {
label: 'Settings', label: 'Settings',
path: ROUTES.SETTINGS, path: ROUTES.SETTINGS,
@@ -1,9 +1,10 @@
import { PostageBatch } from '@ethersphere/bee-js' import { Bee, PostageBatch } from '@ethersphere/bee-js'
import { DriveInfo, FileInfo } from '@solarpunkltd/file-manager-lib' import { DriveInfo, FileInfo } from '@solarpunkltd/file-manager-lib'
import { ReactElement, useEffect, useMemo, useState } from 'react' import { ReactElement, useEffect, useMemo, useState } from 'react'
import { createPortal } from 'react-dom' import { createPortal } from 'react-dom'
import AlertIcon from 'remixicon-react/AlertLineIcon' import AlertIcon from 'remixicon-react/AlertLineIcon'
import { validateStampStillExists } from '../../utils/bee'
import { getDaysLeft } from '../../utils/common' import { getDaysLeft } from '../../utils/common'
import { Button } from '../Button/Button' import { Button } from '../Button/Button'
import { UpgradeDriveModal } from '../UpgradeDriveModal/UpgradeDriveModal' import { UpgradeDriveModal } from '../UpgradeDriveModal/UpgradeDriveModal'
@@ -16,19 +17,23 @@ import '../../styles/global.scss'
const EXPIRING_ITEMS_PAGE_SIZE = 3 const EXPIRING_ITEMS_PAGE_SIZE = 3
interface ExpiringNotificationModalProps { interface ExpiringNotificationModalProps {
bee: Bee
stamps: PostageBatch[] stamps: PostageBatch[]
drives: DriveInfo[] drives: DriveInfo[]
files: FileInfo[] files: FileInfo[]
onCancelClick: () => void onCancelClick: () => void
setErrorMessage?: (error: string) => void setErrorMessage?: (error: string) => void
setShowError: (show: boolean) => void
} }
export function ExpiringNotificationModal({ export function ExpiringNotificationModal({
bee,
stamps, stamps,
drives, drives,
files, files,
onCancelClick, onCancelClick,
setErrorMessage, setErrorMessage,
setShowError,
}: ExpiringNotificationModalProps): ReactElement { }: ExpiringNotificationModalProps): ReactElement {
const [showUpgradeDriveModal, setShowUpgradeDriveModal] = useState(false) const [showUpgradeDriveModal, setShowUpgradeDriveModal] = useState(false)
const [actualStamp, setActualStamp] = useState<PostageBatch | undefined>(undefined) const [actualStamp, setActualStamp] = useState<PostageBatch | undefined>(undefined)
@@ -75,7 +80,18 @@ export function ExpiringNotificationModal({
files={files} files={files}
currentPage={currentPage} currentPage={currentPage}
index={index} index={index}
onUpgradeClick={(stamp, drive) => { onUpgradeClick={async (stamp, drive) => {
const isStampValid = await validateStampStillExists(bee, stamp.batchID)
if (!isStampValid) {
setErrorMessage?.(
`Drive ${drive.name} has expired. Please clear the browser cache and reload the page.`,
)
setShowError(true)
return
}
setActualStamp(stamp) setActualStamp(stamp)
setActualDrive(drive) setActualDrive(drive)
setShowUpgradeDriveModal(true) setShowUpgradeDriveModal(true)
@@ -10,6 +10,7 @@ import React, {
useRef, useRef,
useState, useState,
} from 'react' } from 'react'
import { createPortal } from 'react-dom'
import { useSearch } from '../../../../pages/filemanager/SearchContext' import { useSearch } from '../../../../pages/filemanager/SearchContext'
import { useView } from '../../../../pages/filemanager/ViewContext' import { useView } from '../../../../pages/filemanager/ViewContext'
@@ -87,7 +88,9 @@ function ErrorModalBlock({
return null return null
} }
return <ErrorModal label={label} onClick={onOk} /> const modalRoot = document.querySelector('.fm-main') || document.body
return createPortal(<ErrorModal label={label} onClick={onOk} />, modalRoot)
} }
const extractFilesFromClipboardEvent = (e: React.ClipboardEvent): File[] => { const extractFilesFromClipboardEvent = (e: React.ClipboardEvent): File[] => {
@@ -431,8 +434,10 @@ export function FileBrowser({ errorMessage, setErrorMessage }: FileBrowserProps)
if (rafIdRef.current) { if (rafIdRef.current) {
cancelAnimationFrame(rafIdRef.current) cancelAnimationFrame(rafIdRef.current)
} }
setShowError(false)
} }
}, []) }, [setShowError])
useEffect(() => { useEffect(() => {
let title = currentDrive?.name || '' let title = currentDrive?.name || ''
@@ -30,7 +30,7 @@ export function NotificationBar({ setErrorMessage }: NotificationBarProps): Reac
const [stampsToExpire, setStampsToExpire] = useState<PostageBatch[]>([]) const [stampsToExpire, setStampsToExpire] = useState<PostageBatch[]>([])
const [drivesToExpire, setDrivesToExpire] = useState<DriveInfo[]>([]) const [drivesToExpire, setDrivesToExpire] = useState<DriveInfo[]>([])
const { beeApi } = useContext(SettingsContext) const { beeApi } = useContext(SettingsContext)
const { drives, files, adminDrive } = useContext(FMContext) const { drives, files, adminDrive, setShowError } = useContext(FMContext)
const showExpiration = stampsToExpire.length > 0 const showExpiration = stampsToExpire.length > 0
@@ -109,8 +109,9 @@ export function NotificationBar({ setErrorMessage }: NotificationBarProps): Reac
<div className="fm-notification-bar fm-red-font" onClick={() => setShowExpiringModal(true)}> <div className="fm-notification-bar fm-red-font" onClick={() => setShowExpiringModal(true)}>
{stampsToExpire.length} drive{stampsToExpire.length > 1 ? 's' : ''} expiring soon <UpIcon size="16px" /> {stampsToExpire.length} drive{stampsToExpire.length > 1 ? 's' : ''} expiring soon <UpIcon size="16px" />
</div> </div>
{showExpiringModal && ( {showExpiringModal && beeApi && (
<ExpiringNotificationModal <ExpiringNotificationModal
bee={beeApi}
stamps={stampsToExpire} stamps={stampsToExpire}
drives={drivesToExpire} drives={drivesToExpire}
files={files} files={files}
@@ -118,6 +119,7 @@ export function NotificationBar({ setErrorMessage }: NotificationBarProps): Reac
setShowExpiringModal(false) setShowExpiringModal(false)
}} }}
setErrorMessage={setErrorMessage} setErrorMessage={setErrorMessage}
setShowError={setShowError}
/> />
)} )}
</> </>
+22 -1
View File
@@ -1,3 +1,4 @@
import { BeeModes } from '@ethersphere/bee-js'
import { DriveInfo, FileManagerBase } from '@solarpunkltd/file-manager-lib' import { DriveInfo, FileManagerBase } from '@solarpunkltd/file-manager-lib'
import { ReactElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react' import { ReactElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
@@ -44,6 +45,18 @@ function InitializationErrorBlock({ onOk }: { onOk: () => void }) {
) )
} }
function UltraLightNodeErrorBlock() {
return (
<div className="fm-main">
<div className="fm-loading">
<div className="fm-loading-title">
File Manager is not available with an Ultra-light node. Please upgrade to a Light node to continue.
</div>
</div>
</div>
)
}
function ResetModalBlock({ cacheHelpUrl, onConfirm }: { cacheHelpUrl: string; onConfirm: () => void }) { function ResetModalBlock({ cacheHelpUrl, onConfirm }: { cacheHelpUrl: string; onConfirm: () => void }) {
return ( return (
<div className="fm-main"> <div className="fm-main">
@@ -169,6 +182,7 @@ function FileManagerMainContent(props: {
enum PageState { enum PageState {
Connecting = 'connecting', // still warming up — show nothing / loader Connecting = 'connecting', // still warming up — show nothing / loader
UltraLightNode = 'ultra-light-node', // ultra-light node — file manager not available
NoPrivateKey = 'no-pk', // private key not set NoPrivateKey = 'no-pk', // private key not set
Loading = 'loading', // bee ready, pk present, FM init in progress Loading = 'loading', // bee ready, pk present, FM init in progress
Reset = 'reset', // STATE_INVALID emitted and user has not yet acknowledged Reset = 'reset', // STATE_INVALID emitted and user has not yet acknowledged
@@ -189,7 +203,7 @@ export function FileManagerPage(): ReactElement {
const [connectionErrorDismissed, setConnectionErrorDismissed] = useState<boolean>(false) const [connectionErrorDismissed, setConnectionErrorDismissed] = useState<boolean>(false)
const [cacheHelpUrl, setCacheHelpUrl] = useState<string>(cacheClearUrls[BrowserPlatform.Chrome]) const [cacheHelpUrl, setCacheHelpUrl] = useState<string>(cacheClearUrls[BrowserPlatform.Chrome])
const { status, chainState } = useContext(BeeContext) const { status, chainState, nodeInfo } = useContext(BeeContext)
const { fm, initDone, shallReset, adminDrive, initializationError, notifyPkSaved } = useContext(FMContext) const { fm, initDone, shallReset, adminDrive, initializationError, notifyPkSaved } = useContext(FMContext)
useEffect(() => { useEffect(() => {
@@ -228,6 +242,8 @@ export function FileManagerPage(): ReactElement {
if (!isBeeReady && !initDone) return PageState.Connecting if (!isBeeReady && !initDone) return PageState.Connecting
if (nodeInfo?.beeMode === BeeModes.ULTRA_LIGHT) return PageState.UltraLightNode
if (!hasPk) return PageState.NoPrivateKey if (!hasPk) return PageState.NoPrivateKey
if (!initDone) return PageState.Loading if (!initDone) return PageState.Loading
@@ -259,6 +275,7 @@ export function FileManagerPage(): ReactElement {
adminDrive, adminDrive,
isCreationInProgress, isCreationInProgress,
chainState, chainState,
nodeInfo?.beeMode,
]) ])
const handlePrivateKeySaved = useCallback(() => { const handlePrivateKeySaved = useCallback(() => {
@@ -274,6 +291,10 @@ export function FileManagerPage(): ReactElement {
const loading = !fm?.adminStamp || !adminDrive const loading = !fm?.adminStamp || !adminDrive
const isFormbricksActive = Boolean(fm && fm.adminStamp && adminDrive && !loading) const isFormbricksActive = Boolean(fm && fm.adminStamp && adminDrive && !loading)
if (pageState === PageState.UltraLightNode) {
return <UltraLightNodeErrorBlock />
}
if (pageState === PageState.Connecting || pageState === PageState.Loading) { if (pageState === PageState.Connecting || pageState === PageState.Loading) {
return <LoadingBlock /> return <LoadingBlock />
} }
+4 -1
View File
@@ -15,7 +15,7 @@ import NodeInfoCard from './NodeInfoCard'
import { WalletInfoCard } from './WalletInfoCard' import { WalletInfoCard } from './WalletInfoCard'
export default function Status(): ReactElement { export default function Status(): ReactElement {
const { beeVersion, status, topology, nodeInfo, walletBalance } = useContext(BeeContext) const { beeVersion, status, topology, nodeInfo, nodeStatus, walletBalance } = useContext(BeeContext)
const { isDesktop, desktopUrl } = useContext(SettingsContext) const { isDesktop, desktopUrl } = useContext(SettingsContext)
const { beeDesktopVersion } = useBeeDesktop(isDesktop, desktopUrl) const { beeDesktopVersion } = useBeeDesktop(isDesktop, desktopUrl)
const { newBeeDesktopVersion } = useNewBeeDesktopVersion(isDesktop, desktopUrl, false) const { newBeeDesktopVersion } = useNewBeeDesktopVersion(isDesktop, desktopUrl, false)
@@ -40,7 +40,10 @@ export default function Status(): ReactElement {
<div style={{ height: '2px' }} /> <div style={{ height: '2px' }} />
<ExpandableListItem label="Connected peers" value={topology?.connected ?? '-'} /> <ExpandableListItem label="Connected peers" value={topology?.connected ?? '-'} />
<ExpandableListItem label="Population" value={topology?.population ?? '-'} /> <ExpandableListItem label="Population" value={topology?.population ?? '-'} />
<ExpandableListItem label="Pullsync rate" value={nodeStatus?.pullsyncRate} />
<ExpandableListItem label="Depth" value={topology?.depth ?? '-'} /> <ExpandableListItem label="Depth" value={topology?.depth ?? '-'} />
<ExpandableListItem label="Neighborhood size" value={nodeStatus?.neighborhoodSize} />
<ExpandableListItem label="Node is reachable" value={nodeStatus?.isReachable?.toString()} />
<ChainSync /> <ChainSync />
<div style={{ height: '16px' }} /> <div style={{ height: '16px' }} />
+26
View File
@@ -0,0 +1,26 @@
import CircularProgress from '@mui/material/CircularProgress'
import { ReactElement, useContext } from 'react'
import ExpandableList from '../../components/ExpandableList'
import { Redistribution } from '../../components/Redistribution'
import { Context as SettingsContext } from '../../providers/Settings'
export default function RedistributionPage(): ReactElement {
const { isLoading } = useContext(SettingsContext)
if (isLoading) {
return (
<div style={{ textAlign: 'center', width: '100%' }}>
<CircularProgress />
</div>
)
}
return (
<>
<ExpandableList label="Redistribution" defaultOpen>
<Redistribution />
</ExpandableList>
</>
)
}
@@ -191,7 +191,6 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
<Grid container justifyContent="space-between"> <Grid container justifyContent="space-between">
<Typography>Corresponding TTL (Time to live)</Typography> <Typography>Corresponding TTL (Time to live)</Typography>
<Typography>{amountInput ? getTtl(amountInput) : '-'}</Typography> <Typography>{amountInput ? getTtl(amountInput) : '-'}</Typography>
<Typography>{amountInput ? getTtl(amountInput) : '-'}</Typography>
</Grid> </Grid>
</Box> </Box>
<Box display="flex" justifyContent={'right'} mt={0.5}> <Box display="flex" justifyContent={'right'} mt={0.5}>
+18
View File
@@ -5,11 +5,13 @@ import {
ChainState, ChainState,
ChequebookAddressResponse, ChequebookAddressResponse,
ChequebookBalanceResponse, ChequebookBalanceResponse,
DebugStatus,
LastChequesResponse, LastChequesResponse,
NodeAddresses, NodeAddresses,
NodeInfo, NodeInfo,
Peer, Peer,
PeerBalance, PeerBalance,
RedistributionState,
Topology, Topology,
WalletBalance, WalletBalance,
} from '@ethersphere/bee-js' } from '@ethersphere/bee-js'
@@ -61,6 +63,7 @@ interface ContextInterface {
apiHealth: boolean apiHealth: boolean
nodeAddresses: NodeAddresses | null nodeAddresses: NodeAddresses | null
nodeInfo: NodeInfo | null nodeInfo: NodeInfo | null
nodeStatus: DebugStatus | null
topology: Topology | null topology: Topology | null
chequebookAddress: ChequebookAddressResponse | null chequebookAddress: ChequebookAddressResponse | null
peers: Peer[] | null peers: Peer[] | null
@@ -71,6 +74,7 @@ interface ContextInterface {
settlements: AllSettlements | null settlements: AllSettlements | null
chainState: ChainState | null chainState: ChainState | null
walletBalance: WalletBalance | null walletBalance: WalletBalance | null
redistributionState: RedistributionState | null
latestBeeRelease: LatestBeeRelease | null latestBeeRelease: LatestBeeRelease | null
isLoading: boolean isLoading: boolean
lastUpdate: number | null lastUpdate: number | null
@@ -91,6 +95,7 @@ const initialValues: ContextInterface = {
apiHealth: false, apiHealth: false,
nodeAddresses: null, nodeAddresses: null,
nodeInfo: null, nodeInfo: null,
nodeStatus: null,
topology: null, topology: null,
chequebookAddress: null, chequebookAddress: null,
stake: null, stake: null,
@@ -101,6 +106,7 @@ const initialValues: ContextInterface = {
settlements: null, settlements: null,
chainState: null, chainState: null,
walletBalance: null, walletBalance: null,
redistributionState: null,
latestBeeRelease: null, latestBeeRelease: null,
isLoading: true, isLoading: true,
lastUpdate: null, lastUpdate: null,
@@ -199,6 +205,7 @@ export function Provider({ children }: Props): ReactElement {
const [isWarmingUp, setIsWarmingUp] = useState<boolean>(true) const [isWarmingUp, setIsWarmingUp] = useState<boolean>(true)
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 [nodeStatus, setNodeStatus] = useState<DebugStatus | null>(null)
const [topology, setNodeTopology] = useState<Topology | null>(null) const [topology, setNodeTopology] = useState<Topology | null>(null)
const [chequebookAddress, setChequebookAddress] = useState<ChequebookAddressResponse | null>(null) const [chequebookAddress, setChequebookAddress] = useState<ChequebookAddressResponse | null>(null)
const [peers, setPeers] = useState<Peer[] | null>(null) const [peers, setPeers] = useState<Peer[] | null>(null)
@@ -209,6 +216,7 @@ export function Provider({ children }: Props): ReactElement {
const [settlements, setSettlements] = useState<AllSettlements | null>(null) const [settlements, setSettlements] = useState<AllSettlements | null>(null)
const [chainState, setChainState] = useState<ChainState | null>(null) const [chainState, setChainState] = useState<ChainState | null>(null)
const [walletBalance, setWalletBalance] = useState<WalletBalance | null>(null) const [walletBalance, setWalletBalance] = useState<WalletBalance | null>(null)
const [redistributionState, setRedistributionState] = useState<RedistributionState | null>(null)
const [startedAt, setStartedAt] = useState(() => Date.now()) const [startedAt, setStartedAt] = useState(() => Date.now())
const { latestBeeRelease } = useLatestBeeRelease() const { latestBeeRelease } = useLatestBeeRelease()
@@ -257,6 +265,7 @@ export function Provider({ children }: Props): ReactElement {
walletResult, walletResult,
chequebookBalanceResult, chequebookBalanceResult,
stakeResult, stakeResult,
redistributionStateResult,
peerBalancesResult, peerBalancesResult,
settlementsResult, settlementsResult,
] = await Promise.allSettled([ ] = await Promise.allSettled([
@@ -272,6 +281,7 @@ export function Provider({ children }: Props): ReactElement {
beeApi.getWalletBalance({ timeout: TIMEOUT }), beeApi.getWalletBalance({ timeout: TIMEOUT }),
beeApi.getChequebookBalance({ timeout: TIMEOUT }), beeApi.getChequebookBalance({ timeout: TIMEOUT }),
beeApi.getStake({ timeout: TIMEOUT }), beeApi.getStake({ timeout: TIMEOUT }),
beeApi.getRedistributionState({ timeout: TIMEOUT }),
beeApi.getAllBalances({ timeout: TIMEOUT }), beeApi.getAllBalances({ timeout: TIMEOUT }),
beeApi.getAllSettlements(), beeApi.getAllSettlements(),
]) ])
@@ -282,6 +292,7 @@ export function Provider({ children }: Props): ReactElement {
setApiHealth(Boolean(health)) setApiHealth(Boolean(health))
setIsWarmingUp(getFulfilledValue(statusResult)?.isWarmingUp ?? false) setIsWarmingUp(getFulfilledValue(statusResult)?.isWarmingUp ?? false)
setNodeStatus(getFulfilledValue(statusResult))
setNodeAddresses(getFulfilledValue(nodeAddressesResult)) setNodeAddresses(getFulfilledValue(nodeAddressesResult))
setNodeInfo(getFulfilledValue(nodeInfoResult)) setNodeInfo(getFulfilledValue(nodeInfoResult))
setNodeTopology(getFulfilledValue(topologyResult)) setNodeTopology(getFulfilledValue(topologyResult))
@@ -292,6 +303,7 @@ export function Provider({ children }: Props): ReactElement {
setWalletBalance(getFulfilledValue(walletResult)) setWalletBalance(getFulfilledValue(walletResult))
setChequebookBalance(getFulfilledValue(chequebookBalanceResult)) setChequebookBalance(getFulfilledValue(chequebookBalanceResult))
setStake(getFulfilledValue(stakeResult)) setStake(getFulfilledValue(stakeResult))
setRedistributionState(getFulfilledValue(redistributionStateResult))
setPeerBalances(getFulfilledValue(peerBalancesResult)?.balances ?? null) setPeerBalances(getFulfilledValue(peerBalancesResult)?.balances ?? null)
setSettlements(getFulfilledValue(settlementsResult)) setSettlements(getFulfilledValue(settlementsResult))
setError(null) setError(null)
@@ -332,6 +344,7 @@ export function Provider({ children }: Props): ReactElement {
setNodeAddresses(null) setNodeAddresses(null)
setNodeTopology(null) setNodeTopology(null)
setNodeInfo(null) setNodeInfo(null)
setNodeStatus(null)
setPeers(null) setPeers(null)
setChequebookAddress(null) setChequebookAddress(null)
setChequebookBalance(null) setChequebookBalance(null)
@@ -339,6 +352,7 @@ export function Provider({ children }: Props): ReactElement {
setPeerCheques(null) setPeerCheques(null)
setSettlements(null) setSettlements(null)
setChainState(null) setChainState(null)
setRedistributionState(null)
if (beeApi !== null) { if (beeApi !== null) {
refresh() refresh()
@@ -381,6 +395,7 @@ export function Provider({ children }: Props): ReactElement {
apiHealth, apiHealth,
nodeAddresses, nodeAddresses,
nodeInfo, nodeInfo,
nodeStatus,
topology, topology,
chequebookAddress, chequebookAddress,
peers, peers,
@@ -391,6 +406,7 @@ export function Provider({ children }: Props): ReactElement {
settlements, settlements,
chainState, chainState,
walletBalance, walletBalance,
redistributionState,
latestBeeRelease, latestBeeRelease,
isLoading, isLoading,
lastUpdate, lastUpdate,
@@ -405,6 +421,7 @@ export function Provider({ children }: Props): ReactElement {
apiHealth, apiHealth,
nodeAddresses, nodeAddresses,
nodeInfo, nodeInfo,
nodeStatus,
topology, topology,
chequebookAddress, chequebookAddress,
peers, peers,
@@ -415,6 +432,7 @@ export function Provider({ children }: Props): ReactElement {
settlements, settlements,
chainState, chainState,
walletBalance, walletBalance,
redistributionState,
latestBeeRelease, latestBeeRelease,
isLoading, isLoading,
lastUpdate, lastUpdate,
+3
View File
@@ -17,6 +17,7 @@ import { UploadLander } from './pages/files/UploadLander'
import GiftCards from './pages/giftCode' import GiftCards from './pages/giftCode'
import Info from './pages/info' import Info from './pages/info'
import PageNotFound from './pages/notFound/PageNotFound' import PageNotFound from './pages/notFound/PageNotFound'
import RedistributionPage from './pages/redistribution'
import LightModeRestart from './pages/restart/LightModeRestart' import LightModeRestart from './pages/restart/LightModeRestart'
import Settings from './pages/settings' import Settings from './pages/settings'
import { CreatePostageStampPage } from './pages/stamps/CreatePostageStampAdvancedPage' import { CreatePostageStampPage } from './pages/stamps/CreatePostageStampAdvancedPage'
@@ -38,6 +39,7 @@ export enum ROUTES {
UPLOAD_IN_PROGRESS = '/files/upload/workflow', UPLOAD_IN_PROGRESS = '/files/upload/workflow',
DOWNLOAD = '/files/download', DOWNLOAD = '/files/download',
HASH = '/files/hash/:hash', HASH = '/files/hash/:hash',
REDISTRIBUTION = '/redistribution',
SETTINGS = '/settings', SETTINGS = '/settings',
STATUS = '/status', STATUS = '/status',
TOP_UP = '/account/wallet/top-up', TOP_UP = '/account/wallet/top-up',
@@ -82,6 +84,7 @@ const BaseRouter = (): ReactElement => {
<Route path={ROUTES.SETTINGS} element={<Settings />} /> <Route path={ROUTES.SETTINGS} element={<Settings />} />
<Route path={ROUTES.STATUS} element={<Status />} /> <Route path={ROUTES.STATUS} element={<Status />} />
<Route path={ROUTES.INFO} element={<Info />} /> <Route path={ROUTES.INFO} element={<Info />} />
<Route path={ROUTES.REDISTRIBUTION} element={<RedistributionPage />} />
<Route path={ROUTES.TOP_UP} element={<TopUp />} /> <Route path={ROUTES.TOP_UP} element={<TopUp />} />
<Route path={ROUTES.TOP_UP_CRYPTO} element={<CryptoTopUpIndex />} /> <Route path={ROUTES.TOP_UP_CRYPTO} element={<CryptoTopUpIndex />} />
<Route path={ROUTES.TOP_UP_CRYPTO_SWAP} element={<Swap header="Top-up with cryptocurrencies" />} /> <Route path={ROUTES.TOP_UP_CRYPTO_SWAP} element={<Swap header="Top-up with cryptocurrencies" />} />
+2 -2
View File
@@ -84,7 +84,7 @@ export async function sendNativeTransaction(
const feedData = await signer.provider.getFeeData() const feedData = await signer.provider.getFeeData()
const gasPrice = externalGasPrice ?? DAI.fromWei(feedData.gasPrice?.toString() || '0') const gasPrice = externalGasPrice ?? DAI.fromWei(feedData.gasPrice?.toString() || '0')
const transaction = await signer.sendTransaction({ const transaction = await signer.sendTransaction({
to: to.toHex(), to: to.toChecksum(),
value: BigInt(value.toWeiString()), value: BigInt(value.toWeiString()),
gasPrice: BigInt(gasPrice.toWeiString()), gasPrice: BigInt(gasPrice.toWeiString()),
gasLimit: BigInt(21000), gasLimit: BigInt(21000),
@@ -117,7 +117,7 @@ export async function sendBzzTransaction(
const feeData = await signer.provider.getFeeData() const feeData = await signer.provider.getFeeData()
const gasPrice = feeData.gasPrice || BigInt(0) const gasPrice = feeData.gasPrice || BigInt(0)
const bzz = new Contract(BZZ_TOKEN_ADDRESS, bzzABI, signer) const bzz = new Contract(BZZ_TOKEN_ADDRESS, bzzABI, signer)
const transaction = await bzz.transfer(to.toChecksum(), value, { gasPrice }) const transaction = await bzz.transfer(to.toChecksum(), value.toPLURBigInt(), { gasPrice })
const receipt = await transaction.wait(1) const receipt = await transaction.wait(1)
if (!receipt) { if (!receipt) {
+52 -4
View File
@@ -1,6 +1,6 @@
import { BZZ } from '@ethersphere/bee-js' import { BZZ, DAI } from '@ethersphere/bee-js'
import { sendBzzTransaction } from '../../src/utils/rpc' import { sendBzzTransaction, sendNativeTransaction } from '../../src/utils/rpc'
interface MockProvider { interface MockProvider {
getFeeData: jest.Mock getFeeData: jest.Mock
@@ -11,12 +11,14 @@ const mockWait = jest.fn()
const mockTransfer = jest.fn() const mockTransfer = jest.fn()
const mockGetFeeData = jest.fn() const mockGetFeeData = jest.fn()
const mockGetNetwork = jest.fn() const mockGetNetwork = jest.fn()
const mockSendTransaction = jest.fn()
const mockProvider: MockProvider = { const mockProvider: MockProvider = {
getFeeData: mockGetFeeData, getFeeData: mockGetFeeData,
getNetwork: mockGetNetwork, getNetwork: mockGetNetwork,
} }
const value = BZZ.fromDecimalString('1') const bzzValue = BZZ.fromDecimalString('1')
const daiValue = DAI.fromDecimalString('1')
const privateKey = 'FFFF000000000000000000000000000000000000000000000000000000000000' const privateKey = 'FFFF000000000000000000000000000000000000000000000000000000000000'
const jsonRpcProvider = 'http://mock-json-rpc-provider' const jsonRpcProvider = 'http://mock-json-rpc-provider'
@@ -39,6 +41,7 @@ jest.mock('ethers', () => {
class Wallet { class Wallet {
provider: MockProvider provider: MockProvider
sendTransaction = mockSendTransaction
constructor(_privateKey: string, provider: MockProvider) { constructor(_privateKey: string, provider: MockProvider) {
this.provider = provider this.provider = provider
@@ -64,8 +67,53 @@ describe('sendBzzTransaction', () => {
}) })
it.each(addresses)('sendBzzTransaction to address: %s', async (address: string) => { it.each(addresses)('sendBzzTransaction to address: %s', async (address: string) => {
await sendBzzTransaction(privateKey, address, value, jsonRpcProvider) await sendBzzTransaction(privateKey, address, bzzValue, jsonRpcProvider)
const to = mockTransfer.mock.calls[0][0] as string const to = mockTransfer.mock.calls[0][0] as string
expect(to.startsWith('0x')).toBe(true) expect(to.startsWith('0x')).toBe(true)
}) })
it('passes BZZ value as bigint (not BZZ object)', async () => {
await sendBzzTransaction(privateKey, '0x52908400098527886e0f7030069857d2e4169ee7', bzzValue, jsonRpcProvider)
const transferredValue = mockTransfer.mock.calls[0][1]
expect(typeof transferredValue).toBe('bigint')
expect(transferredValue).toBe(bzzValue.toPLURBigInt())
})
})
describe('sendNativeTransaction', () => {
const addresses = ['52908400098527886e0f7030069857d2e4169ee7', '0x52908400098527886e0f7030069857d2e4169ee7']
beforeEach(() => {
jest.clearAllMocks()
mockWait.mockResolvedValue({ status: 1 })
mockSendTransaction.mockResolvedValue({ wait: mockWait })
mockGetFeeData.mockResolvedValue({ gasPrice: BigInt(1) })
mockGetNetwork.mockResolvedValue({ chainId: BigInt(100) })
})
it.each(addresses)('sendNativeTransaction to address: %s passes 0x-prefixed address', async (address: string) => {
await sendNativeTransaction(privateKey, address, daiValue, jsonRpcProvider)
const tx = mockSendTransaction.mock.calls[0][0] as { to: string }
expect(tx.to.startsWith('0x')).toBe(true)
})
it('passes DAI value as bigint', async () => {
await sendNativeTransaction(privateKey, '0x52908400098527886e0f7030069857d2e4169ee7', daiValue, jsonRpcProvider)
const tx = mockSendTransaction.mock.calls[0][0] as { value: bigint }
expect(typeof tx.value).toBe('bigint')
expect(tx.value).toBe(BigInt(daiValue.toWeiString()))
})
it('uses externalGasPrice when provided', async () => {
const externalGasPrice = DAI.fromWei('9999')
await sendNativeTransaction(
privateKey,
'0x52908400098527886e0f7030069857d2e4169ee7',
daiValue,
jsonRpcProvider,
externalGasPrice,
)
const tx = mockSendTransaction.mock.calls[0][0] as { gasPrice: bigint }
expect(tx.gasPrice).toBe(BigInt(externalGasPrice.toWeiString()))
})
}) })