Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 16ffffb0c4 | |||
| 080d9f2c2a | |||
| 4f9abc614e | |||
| 20a051b658 | |||
| 0c2ac0c454 | |||
| 8802d20555 | |||
| 7fa1cb0ccf | |||
| bab08e1df2 | |||
| d91c334cf8 | |||
| bce93ce3cd | |||
| 8367f2b76a | |||
| 055a3002b3 | |||
| c9c4e7d7d1 | |||
| d97bc27c14 | |||
| e215c61ea1 | |||
| 8298d0bc66 | |||
| fac72b1299 |
@@ -1,5 +1,41 @@
|
||||
# Changelog
|
||||
|
||||
## [0.25.0](https://github.com/ethersphere/bee-dashboard/compare/v0.24.1...v0.25.0) (2023-12-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve topup and dilute ux ([0c2ac0c](https://github.com/ethersphere/bee-dashboard/commit/0c2ac0c454ad02200a2762958c5bc5abbdfe8005))
|
||||
* update postage stamp creation screen ([#641](https://github.com/ethersphere/bee-dashboard/issues/641)) ([4f9abc6](https://github.com/ethersphere/bee-dashboard/commit/4f9abc614eedd5ce3a279a4686cc832c4d1e62c7))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing stamp labels and fix inputs ([#634](https://github.com/ethersphere/bee-dashboard/issues/634)) ([7fa1cb0](https://github.com/ethersphere/bee-dashboard/commit/7fa1cb0ccf9f2a32263e84aa76732ebd2fc7fb22))
|
||||
* put stamp input error handling in state ([#640](https://github.com/ethersphere/bee-dashboard/issues/640)) ([20a051b](https://github.com/ethersphere/bee-dashboard/commit/20a051b6589c22397a7305d722a56df0604ff7a4))
|
||||
|
||||
## [0.24.1](https://github.com/ethersphere/bee-dashboard/compare/v0.24.0...v0.24.1) (2023-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update `swap-endpoint` to `blockchain-rpc-endpoint` ([#628](https://github.com/ethersphere/bee-dashboard/issues/628)) ([bce93ce](https://github.com/ethersphere/bee-dashboard/commit/bce93ce3cdc1ef4b1f50fcf274591ba00726be16))
|
||||
|
||||
## [0.24.0](https://github.com/ethersphere/bee-dashboard/compare/v0.23.0...v0.24.0) (2023-08-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add stamp dilute and topup ([#619](https://github.com/ethersphere/bee-dashboard/issues/619)) ([055a300](https://github.com/ethersphere/bee-dashboard/commit/055a3002b303df45c7010ef4d365e14b979e9084))
|
||||
|
||||
## [0.23.0](https://github.com/ethersphere/bee-dashboard/compare/v0.22.0...v0.23.0) (2023-02-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add staking for full nodes ([#590](https://github.com/ethersphere/bee-dashboard/issues/590)) ([fac72b1](https://github.com/ethersphere/bee-dashboard/commit/fac72b1299353c104231aa038c1bab9df78c1355))
|
||||
* upgrade bee-js to 5.2.0 ([#611](https://github.com/ethersphere/bee-dashboard/issues/611)) ([e215c61](https://github.com/ethersphere/bee-dashboard/commit/e215c61ea1619fc388fe8b1904d160b04a1a5c0d))
|
||||
|
||||
## [0.22.0](https://github.com/ethersphere/bee-dashboard/compare/v0.21.1...v0.22.0) (2023-01-19)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
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.9.0-13a47043<!-- SUPPORTED_BEE_END -->**.
|
||||
This project is intended to be used with **Bee version <!-- SUPPORTED_BEE_START -->1.12.0-88c1d236<!-- 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
|
||||
[official Discord](https://discord.gg/GU22h2utj6) and by keeping an eye on the
|
||||
[releases tab](https://github.com/ethersphere/bee-dashboard/releases).
|
||||
|
||||
Generated
+96
-882
File diff suppressed because it is too large
Load Diff
+7
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ethersphere/bee-dashboard",
|
||||
"version": "0.22.0",
|
||||
"version": "0.25.0",
|
||||
"description": "An app which helps users to setup their Bee node and do actions like cash out cheques",
|
||||
"keywords": [
|
||||
"bee",
|
||||
@@ -26,8 +26,7 @@
|
||||
"url": "https://github.com/ethersphere/bee-dashboard.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ethersphere/bee-js": "^5.1.0",
|
||||
"@ethersphere/manifest-js": "1.2.1",
|
||||
"@ethersphere/bee-js": "^6.7.0",
|
||||
"@ethersphere/swarm-cid": "^0.1.0",
|
||||
"@material-ui/core": "4.12.3",
|
||||
"@material-ui/icons": "4.11.2",
|
||||
@@ -44,13 +43,14 @@
|
||||
"formik": "2.2.9",
|
||||
"formik-material-ui": "3.0.1",
|
||||
"jszip": "^3.7.1",
|
||||
"mantaray-js": "^1.0.3",
|
||||
"material-ui-dropzone": "3.5.0",
|
||||
"notistack": "1.0.10",
|
||||
"opener": "1.5.2",
|
||||
"qrcode.react": "1.0.1",
|
||||
"react": ">=17.0.0 || >=18.0.0",
|
||||
"react": ">= 17.0.2",
|
||||
"react-copy-to-clipboard": "5.0.4",
|
||||
"react-dom": ">=17.0.0 || >=18.0.0",
|
||||
"react-dom": ">= 17.0.2",
|
||||
"react-identicons": "1.2.5",
|
||||
"react-router": "6.2.1",
|
||||
"react-router-dom": "6.2.1",
|
||||
@@ -137,7 +137,7 @@
|
||||
"lint:check": "eslint \"src/**/*.ts\" \"src/**/*.tsx\" && prettier --check \"src/**/*.ts\" \"src/**/*.tsx\"",
|
||||
"check:types": "tsc --project tsconfig.lib.json",
|
||||
"update-map-data": "node ./utils/update-map-data.js",
|
||||
"bee": "bee-factory start"
|
||||
"bee": "npx bee-factory start"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
@@ -159,6 +159,6 @@
|
||||
"engines": {
|
||||
"node": ">=14.0.0",
|
||||
"npm": ">=6.9.0",
|
||||
"bee": "1.9.0-13a47043"
|
||||
"bee": "1.16.1-8e269c8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
},
|
||||
contentLevel12: {
|
||||
marginTop: theme.spacing(0.25),
|
||||
'& > li:last-of-type': {
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
infoText: {
|
||||
color: '#c9c9c9',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ReactElement, ReactNode } from 'react'
|
||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
||||
import { Typography, Grid, IconButton, Tooltip } from '@material-ui/core'
|
||||
import Info from 'remixicon-react/InformationLineIcon'
|
||||
import { Grid, IconButton, Tooltip, Typography } from '@material-ui/core'
|
||||
import ListItem from '@material-ui/core/ListItem'
|
||||
import { Theme, createStyles, makeStyles } from '@material-ui/core/styles'
|
||||
import { ReactElement, ReactNode } from 'react'
|
||||
import Info from 'remixicon-react/InformationLineIcon'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Grid, IconButton, InputBase, ListItem, Typography } from '@material-ui/core'
|
||||
import { Box, Grid, IconButton, InputBase, ListItem, Typography } from '@material-ui/core'
|
||||
import Collapse from '@material-ui/core/Collapse'
|
||||
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
|
||||
import { ChangeEvent, ReactElement, useState } from 'react'
|
||||
@@ -134,6 +134,7 @@ export default function ExpandableListItemKey({
|
||||
</ListItem>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
{helperText && <ExpandableListItemNote>{helperText}</ExpandableListItemNote>}
|
||||
<Box mt={2}>
|
||||
<ExpandableListItemActions>
|
||||
<SwarmButton
|
||||
disabled={
|
||||
@@ -159,6 +160,7 @@ export default function ExpandableListItemKey({
|
||||
Cancel
|
||||
</SwarmButton>
|
||||
</ExpandableListItemActions>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { BeeDebug } from '@ethersphere/bee-js'
|
||||
import { Box } from '@material-ui/core'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import Input from '@material-ui/core/Input'
|
||||
import { useSnackbar } from 'notistack'
|
||||
import { ReactElement, ReactNode, useState } from 'react'
|
||||
|
||||
interface Props {
|
||||
type: 'Topup' | 'Dilute'
|
||||
icon: ReactNode
|
||||
beeDebug: BeeDebug
|
||||
stamp: string
|
||||
}
|
||||
|
||||
export default function StampExtensionModal({ type, icon, beeDebug, stamp }: Props): ReactElement {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [amount, setAmount] = useState('')
|
||||
const { enqueueSnackbar } = useSnackbar()
|
||||
const label = `${type} ${stamp.substring(0, 8)}`
|
||||
|
||||
const handleClickOpen = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setOpen(true)
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const handleAction = async () => {
|
||||
if (type === 'Topup') {
|
||||
try {
|
||||
await beeDebug.topUpBatch(stamp, amount)
|
||||
enqueueSnackbar(`Successfully topped up stamp, your changes will appear soon`, { variant: 'success' })
|
||||
} catch (error) {
|
||||
enqueueSnackbar(`Failed to topup stamp: ${error || 'Unknown reason'}`, { variant: 'error' })
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'Dilute') {
|
||||
try {
|
||||
await beeDebug.diluteBatch(stamp, parseInt(amount, 10))
|
||||
enqueueSnackbar(`Successfully diluted stamp, your changes will appear soon`, { variant: 'success' })
|
||||
} catch (error) {
|
||||
enqueueSnackbar(`Failed to dilute stamp: ${error || 'Unknown reason'}`, { variant: 'error' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
||||
setAmount(event.target.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box mb={2}>
|
||||
<Button variant="contained" onClick={handleClickOpen} startIcon={icon}>
|
||||
{type}
|
||||
</Button>
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">{label}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Input
|
||||
autoFocus
|
||||
margin="dense"
|
||||
id="name"
|
||||
type="text"
|
||||
placeholder={type === 'Topup' ? 'Amount to add' : 'New depth to dilute'}
|
||||
fullWidth
|
||||
value={amount}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button disabled={amount === ''} onClick={handleAction} color="primary">
|
||||
{type}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -30,6 +30,7 @@ interface Props {
|
||||
formik?: boolean
|
||||
defaultValue?: string
|
||||
placeholder?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
@@ -60,6 +61,7 @@ export function SwarmSelect({
|
||||
onChange,
|
||||
label,
|
||||
placeholder,
|
||||
disabled = false,
|
||||
}: Props): ReactElement {
|
||||
const classes = useStyles()
|
||||
|
||||
@@ -69,6 +71,7 @@ export function SwarmSelect({
|
||||
{label && <FormHelperText>{label}</FormHelperText>}
|
||||
<Field
|
||||
required
|
||||
disabled={disabled}
|
||||
component={Select}
|
||||
name={name}
|
||||
fullWidth
|
||||
@@ -94,6 +97,7 @@ export function SwarmSelect({
|
||||
{label && <FormHelperText>{label}</FormHelperText>}
|
||||
<MuiSelect
|
||||
required
|
||||
disabled={disabled}
|
||||
name={name}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
import { ReactElement, useContext } from 'react'
|
||||
import Download from 'remixicon-react/DownloadLineIcon'
|
||||
import WithdrawDepositModal from '../components/WithdrawDepositModal'
|
||||
import { Context as BeeContext } from '../providers/Bee'
|
||||
import { Context as SettingsContext } from '../providers/Settings'
|
||||
|
||||
interface Props {
|
||||
onStarted: () => void
|
||||
onFinished: () => void
|
||||
}
|
||||
|
||||
export default function StakeModal({ onStarted, onFinished }: Props): ReactElement {
|
||||
const { beeDebugApi } = useContext(SettingsContext)
|
||||
const { refresh } = useContext(BeeContext)
|
||||
|
||||
return (
|
||||
<WithdrawDepositModal
|
||||
successMessage="Successfully deposited stake."
|
||||
errorMessage="Error with depositing"
|
||||
dialogMessage="Specify the amount of xBZZ you would like to stake. Your first stake must be at least 10 xBZZ. This will lock your tokens."
|
||||
label="Stake"
|
||||
icon={<Download size="1rem" />}
|
||||
min={new BigNumber(0)}
|
||||
action={async (amount: bigint) => {
|
||||
if (!beeDebugApi) throw new Error('Bee Debug URL is not valid')
|
||||
|
||||
onStarted()
|
||||
|
||||
try {
|
||||
await beeDebugApi.depositStake(amount.toString())
|
||||
} finally {
|
||||
refresh()
|
||||
onFinished()
|
||||
}
|
||||
|
||||
return 'unknown'
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { BeeModes } from '@ethersphere/bee-js'
|
||||
import { createStyles, makeStyles, Tab, Tabs, Theme } from '@material-ui/core'
|
||||
import { ReactElement } from 'react'
|
||||
import { ReactElement, useContext } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Context } from '../../providers/Bee'
|
||||
import { ACCOUNT_TABS } from '../../routes'
|
||||
|
||||
const tabMap = {
|
||||
@@ -8,10 +10,11 @@ const tabMap = {
|
||||
CHEQUEBOOK: 1,
|
||||
STAMPS: 2,
|
||||
FEEDS: 3,
|
||||
STAKING: 4,
|
||||
}
|
||||
|
||||
interface Props {
|
||||
active: 'WALLET' | 'CHEQUEBOOK' | 'STAMPS' | 'FEEDS'
|
||||
active: 'WALLET' | 'CHEQUEBOOK' | 'STAMPS' | 'FEEDS' | 'STAKING'
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
@@ -20,16 +23,12 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
flexGrow: 1,
|
||||
marginBottom: theme.spacing(4),
|
||||
textTransform: 'none',
|
||||
marginLeft: theme.spacing(-0.25),
|
||||
marginRight: theme.spacing(-0.25),
|
||||
},
|
||||
leftTab: {
|
||||
marginRight: theme.spacing(0.125),
|
||||
},
|
||||
centerTab: {
|
||||
marginLeft: theme.spacing(0.125),
|
||||
marginRight: theme.spacing(0.125),
|
||||
},
|
||||
rightTab: {
|
||||
marginLeft: theme.spacing(0.125),
|
||||
tab: {
|
||||
marginLeft: theme.spacing(0.25),
|
||||
marginRight: theme.spacing(0.25),
|
||||
},
|
||||
}),
|
||||
)
|
||||
@@ -37,6 +36,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
export function AccountNavigation({ active }: Props): ReactElement {
|
||||
const classes = useStyles()
|
||||
const navigate = useNavigate()
|
||||
const { nodeInfo } = useContext(Context)
|
||||
|
||||
function onChange(event: React.ChangeEvent<Record<string, never>>, newValue: number) {
|
||||
navigate(ACCOUNT_TABS[newValue])
|
||||
@@ -45,10 +45,11 @@ export function AccountNavigation({ active }: Props): ReactElement {
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Tabs value={tabMap[active]} onChange={onChange} variant="fullWidth">
|
||||
<Tab className={classes.leftTab} key="WALLET" label="Wallet" />
|
||||
<Tab className={classes.centerTab} key="CHEQUEBOOK" label="Chequebook" />
|
||||
<Tab className={classes.centerTab} key="STAMPS" label="Stamps" />
|
||||
<Tab className={classes.rightTab} key="FEEDS" label="Feeds" />
|
||||
<Tab className={classes.tab} key="WALLET" label="Wallet" />
|
||||
<Tab className={classes.tab} key="CHEQUEBOOK" label="Chequebook" />
|
||||
<Tab className={classes.tab} key="STAMPS" label="Stamps" />
|
||||
<Tab className={classes.tab} key="FEEDS" label="Feeds" />
|
||||
{nodeInfo?.beeMode === BeeModes.FULL ? <Tab className={classes.tab} key="STAKING" label="Staking" /> : null}
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Context as SettingsContext } from '../../../providers/Settings'
|
||||
import PeerBalances from '../../accounting/PeerBalances'
|
||||
import { AccountNavigation } from '../AccountNavigation'
|
||||
import { Header } from '../Header'
|
||||
import { Box } from '@material-ui/core'
|
||||
|
||||
export function AccountChequebook(): ReactElement {
|
||||
const { status, nodeAddresses, chequebookAddress, chequebookBalance, settlements, peerBalances } =
|
||||
@@ -43,10 +44,12 @@ export function AccountChequebook(): ReactElement {
|
||||
label="Total Cheques Amount Sent"
|
||||
value={`${settlements?.totalSent.toFixedDecimal()} xBZZ`}
|
||||
/>
|
||||
<Box mb={2}>
|
||||
<ExpandableListItem
|
||||
label="Total Cheques Amount Received"
|
||||
value={`${settlements?.totalReceived.toFixedDecimal()} xBZZ`}
|
||||
/>
|
||||
</Box>
|
||||
<ExpandableListItemActions>
|
||||
<WithdrawModal />
|
||||
<DepositModal />
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { ReactElement, useContext, useState } from 'react'
|
||||
import ExpandableList from '../../../components/ExpandableList'
|
||||
import ExpandableListItem from '../../../components/ExpandableListItem'
|
||||
import ExpandableListItemActions from '../../../components/ExpandableListItemActions'
|
||||
import { Loading } from '../../../components/Loading'
|
||||
import TroubleshootConnectionCard from '../../../components/TroubleshootConnectionCard'
|
||||
import StakeModal from '../../../containers/StakeModal'
|
||||
import { CheckState, Context as BeeContext } from '../../../providers/Bee'
|
||||
import { Context as BalanceContext } from '../../../providers/WalletBalance'
|
||||
import { AccountNavigation } from '../AccountNavigation'
|
||||
import { Header } from '../Header'
|
||||
|
||||
export function AccountStaking(): ReactElement {
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const { status, stake } = useContext(BeeContext)
|
||||
const { balance } = useContext(BalanceContext)
|
||||
|
||||
if (status.all === CheckState.ERROR) return <TroubleshootConnectionCard />
|
||||
|
||||
function onStarted() {
|
||||
setLoading(true)
|
||||
}
|
||||
|
||||
function onFinished() {
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<AccountNavigation active="STAKING" />
|
||||
<div>
|
||||
{loading || stake?.toDecimal === undefined ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<ExpandableList label="Staking" defaultOpen>
|
||||
<ExpandableListItem label="Staked BZZ" value={`${stake?.toSignificantDigits()} xBZZ`} />
|
||||
{balance?.bzz ? (
|
||||
<ExpandableListItem
|
||||
label="Available xBZZ balance"
|
||||
value={`${balance?.bzz.toSignificantDigits(4)} xBZZ`}
|
||||
/>
|
||||
) : null}
|
||||
<ExpandableListItemActions>
|
||||
<StakeModal onStarted={onStarted} onFinished={onFinished} />
|
||||
</ExpandableListItemActions>
|
||||
</ExpandableList>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { CircularProgress, Container, createStyles, makeStyles } from '@material-ui/core'
|
||||
import { CircularProgress, createStyles, makeStyles } from '@material-ui/core'
|
||||
import { ReactElement, useContext, useEffect } from 'react'
|
||||
import PlusSquare from 'remixicon-react/AddBoxLineIcon'
|
||||
import { useNavigate } from 'react-router'
|
||||
import PlusSquare from 'remixicon-react/AddBoxLineIcon'
|
||||
import { Loading } from '../../../components/Loading'
|
||||
import { SwarmButton } from '../../../components/SwarmButton'
|
||||
import TroubleshootConnectionCard from '../../../components/TroubleshootConnectionCard'
|
||||
import { CheckState, Context as BeeContext } from '../../../providers/Bee'
|
||||
import { Context as BeeContext, CheckState } from '../../../providers/Bee'
|
||||
import { Context as StampsContext } from '../../../providers/Stamps'
|
||||
import { ROUTES } from '../../../routes'
|
||||
import StampsTable from '../../stamps/StampsTable'
|
||||
@@ -45,7 +46,7 @@ export function AccountStamps(): ReactElement {
|
||||
if (status.all === CheckState.ERROR) return <TroubleshootConnectionCard />
|
||||
|
||||
function navigateToNewStamp() {
|
||||
navigate(ROUTES.ACCOUNT_STAMPS_NEW)
|
||||
navigate(ROUTES.ACCOUNT_STAMPS_NEW_STANDARD)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -53,11 +54,7 @@ export function AccountStamps(): ReactElement {
|
||||
<Header />
|
||||
<AccountNavigation active="STAMPS" />
|
||||
<div className={classes.root}>
|
||||
{error && (
|
||||
<Container style={{ textAlign: 'center', padding: '50px' }}>
|
||||
Error loading postage stamps details: {error.message}
|
||||
</Container>
|
||||
)}
|
||||
{error && <Loading />}
|
||||
{!error && (
|
||||
<>
|
||||
<div className={classes.actions}>
|
||||
|
||||
@@ -14,13 +14,15 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function PeerBalances({ accounting, isLoadingUncashed, totalUncashed }: Props): ReactElement | null {
|
||||
const uncashedPeers = accounting?.filter(({ uncashedAmount }) => uncashedAmount.toBigNumber.isGreaterThan('0')) || []
|
||||
|
||||
return (
|
||||
<ExpandableList
|
||||
label={`Peers (${accounting?.length || 0})`}
|
||||
label={`Peers (${uncashedPeers.length})`}
|
||||
info={`${totalUncashed.toFixedDecimal()} xBZZ (uncashed)`}
|
||||
>
|
||||
<ExpandableListItem label="Uncashed Amount Total" value={`${totalUncashed.toFixedDecimal()} xBZZ`} />
|
||||
{accounting?.map(({ peer, balance, received, sent, uncashedAmount, total }) => (
|
||||
{uncashedPeers.map(({ peer, balance, received, sent, uncashedAmount, total }) => (
|
||||
<ExpandableList
|
||||
key={peer}
|
||||
label={`Peer ${peer.slice(0, 8)}[…]`}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import * as swarmCid from '@ethersphere/swarm-cid'
|
||||
import { Box } from '@material-ui/core'
|
||||
import { ReactElement, useContext, useEffect, useState } from 'react'
|
||||
import X from 'remixicon-react/CloseLineIcon'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import X from 'remixicon-react/CloseLineIcon'
|
||||
import { DocumentationText } from '../../components/DocumentationText'
|
||||
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
|
||||
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
|
||||
import ExpandableListItemLink from '../../components/ExpandableListItemLink'
|
||||
import { HistoryHeader } from '../../components/HistoryHeader'
|
||||
import { SwarmButton } from '../../components/SwarmButton'
|
||||
import { Context as BeeContext } from '../../providers/Bee'
|
||||
@@ -55,14 +53,8 @@ export function FeedSubpage(): ReactElement {
|
||||
<UploadArea showHelp={false} uploadOrigin={{ origin: 'FEED', uuid }} />
|
||||
{available && identity.feedHash ? (
|
||||
<>
|
||||
<Box mb={0.25}>
|
||||
<ExpandableListItemKey label="Feed hash" value={identity.feedHash} />
|
||||
</Box>
|
||||
<Box mb={4}>
|
||||
<ExpandableListItemLink
|
||||
label="BZZ Link"
|
||||
value={`https://${swarmCid.encodeFeedReference(identity.feedHash)}.bzz.link`}
|
||||
/>
|
||||
<ExpandableListItemKey label="Feed hash" value={identity.feedHash} />
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as swarmCid from '@ethersphere/swarm-cid'
|
||||
import { Utils } from '@ethersphere/bee-js'
|
||||
import { Box } from '@material-ui/core'
|
||||
import { ReactElement } from 'react'
|
||||
import { Utils } from '@ethersphere/bee-js'
|
||||
import { DocumentationText } from '../../components/DocumentationText'
|
||||
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
|
||||
import ExpandableListItemLink from '../../components/ExpandableListItemLink'
|
||||
@@ -19,16 +18,6 @@ export function AssetSummary({ isWebsite, reference }: Props): ReactElement {
|
||||
<Box mb={4}>
|
||||
{isHash && <ExpandableListItemKey label="Swarm hash" value={reference} />}
|
||||
{!isHash && <ExpandableListItemLink label="ENS" value={reference} />}
|
||||
<ExpandableListItemLink
|
||||
label="Share on Swarm Gateway"
|
||||
value={`https://gateway.ethswarm.org/access/${reference}`}
|
||||
/>
|
||||
{isWebsite && isHash && (
|
||||
<ExpandableListItemLink
|
||||
label="BZZ Link"
|
||||
value={`https://${swarmCid.encodeManifestReference(reference).toString()}.bzz.link`}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<DocumentationText>
|
||||
The Swarm Gateway is graciously provided by the Swarm Foundation. This service is under development and provided
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { BeeModes, Utils } from '@ethersphere/bee-js'
|
||||
import { ManifestJs } from '@ethersphere/manifest-js'
|
||||
import { useSnackbar } from 'notistack'
|
||||
import { ReactElement, useContext, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import Search from 'remixicon-react/SearchLineIcon'
|
||||
import ExpandableListItemInput from '../../components/ExpandableListItemInput'
|
||||
import { History } from '../../components/History'
|
||||
import { Context as BeeContext } from '../../providers/Bee'
|
||||
import { Context as FileContext, defaultUploadOrigin } from '../../providers/File'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import { Context as BeeContext } from '../../providers/Bee'
|
||||
import { ROUTES } from '../../routes'
|
||||
import { recognizeEnsOrSwarmHash, regexpEns } from '../../utils'
|
||||
import { determineHistoryName, HISTORY_KEYS, putHistory } from '../../utils/local-storage'
|
||||
import { ManifestJs } from '../../utils/manifest'
|
||||
import { FileNavigation } from './FileNavigation'
|
||||
|
||||
export function Download(): ReactElement {
|
||||
@@ -34,9 +34,7 @@ export function Download(): ReactElement {
|
||||
) {
|
||||
setReferenceError(undefined)
|
||||
} else {
|
||||
setReferenceError(
|
||||
'Incorrect format of swarm hash. Expected 64 or 128 hexstring characters, bzz.link url or ENS domain.',
|
||||
)
|
||||
setReferenceError('Incorrect format of swarm hash. Expected 64 or 128 hexstring characters or ENS domain.')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ManifestJs } from '@ethersphere/manifest-js'
|
||||
import { Box, Typography } from '@material-ui/core'
|
||||
import { saveAs } from 'file-saver'
|
||||
import JSZip from 'jszip'
|
||||
@@ -13,6 +12,7 @@ import { Context as BeeContext } from '../../providers/Bee'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import { ROUTES } from '../../routes'
|
||||
import { determineHistoryName, HISTORY_KEYS, putHistory } from '../../utils/local-storage'
|
||||
import { ManifestJs } from '../../utils/manifest'
|
||||
import { AssetPreview } from './AssetPreview'
|
||||
import { AssetSummary } from './AssetSummary'
|
||||
import { DownloadActionBar } from './DownloadActionBar'
|
||||
|
||||
@@ -7,18 +7,18 @@ import { HistoryHeader } from '../../components/HistoryHeader'
|
||||
import { ProgressIndicator } from '../../components/ProgressIndicator'
|
||||
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
||||
import { META_FILE_NAME, PREVIEW_FILE_NAME } from '../../constants'
|
||||
import { CheckState, Context as BeeContext } from '../../providers/Bee'
|
||||
import { Context as IdentityContext, Identity } from '../../providers/Feeds'
|
||||
import { Context as BeeContext, CheckState } from '../../providers/Bee'
|
||||
import { Identity, Context as IdentityContext } from '../../providers/Feeds'
|
||||
import { Context as FileContext } from '../../providers/File'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import { Context as StampsContext, EnrichedPostageBatch } from '../../providers/Stamps'
|
||||
import { EnrichedPostageBatch, Context as StampsContext } from '../../providers/Stamps'
|
||||
import { ROUTES } from '../../routes'
|
||||
import { waitUntilStampUsable } from '../../utils'
|
||||
import { detectIndexHtml, getAssetNameFromFiles, packageFile } from '../../utils/file'
|
||||
import { persistIdentity, updateFeed } from '../../utils/identity'
|
||||
import { HISTORY_KEYS, putHistory } from '../../utils/local-storage'
|
||||
import { FeedPasswordDialog } from '../feeds/FeedPasswordDialog'
|
||||
import { PostageStampCreation } from '../stamps/PostageStampCreation'
|
||||
import { PostageStampAdvancedCreation } from '../stamps/PostageStampAdvancedCreation'
|
||||
import { PostageStampSelector } from '../stamps/PostageStampSelector'
|
||||
import { AssetPreview } from './AssetPreview'
|
||||
import { StampPreview } from './StampPreview'
|
||||
@@ -130,7 +130,7 @@ export function Upload(): ReactElement {
|
||||
}
|
||||
|
||||
beeApi
|
||||
.uploadFiles(stamp.batchID, fls, { indexDocument })
|
||||
.uploadFiles(stamp.batchID, fls, { indexDocument, deferred: true })
|
||||
.then(hash => {
|
||||
putHistory(HISTORY_KEYS.UPLOAD_HISTORY, hash.reference, getAssetNameFromFiles(files))
|
||||
|
||||
@@ -186,7 +186,7 @@ export function Upload(): ReactElement {
|
||||
{hasAnyStamps && stampMode === 'SELECT' ? (
|
||||
<PostageStampSelector onSelect={stamp => setStamp(stamp)} defaultValue={stamp?.batchID} />
|
||||
) : (
|
||||
<PostageStampCreation onFinished={() => setStampMode('SELECT')} />
|
||||
<PostageStampAdvancedCreation onFinished={() => setStampMode('SELECT')} />
|
||||
)}
|
||||
</Box>
|
||||
<Box mb={4}>
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { Box, Tooltip, Typography } from '@material-ui/core'
|
||||
import { Wallet } from 'ethers'
|
||||
import { useSnackbar } from 'notistack'
|
||||
import { ReactElement, useContext, useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router'
|
||||
import Check from 'remixicon-react/CheckLineIcon'
|
||||
import X from 'remixicon-react/CloseLineIcon'
|
||||
import { useNavigate } from 'react-router'
|
||||
import { Wallet } from 'ethers'
|
||||
import ExpandableListItem from '../../components/ExpandableListItem'
|
||||
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
|
||||
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
|
||||
import { HistoryHeader } from '../../components/HistoryHeader'
|
||||
import { Loading } from '../../components/Loading'
|
||||
import { SwarmButton } from '../../components/SwarmButton'
|
||||
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
||||
import { Context as TopUpContext } from '../../providers/TopUp'
|
||||
import { Token } from '../../models/Token'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import { Context as TopUpContext } from '../../providers/TopUp'
|
||||
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
||||
import { createGiftWallet } from '../../utils/desktop'
|
||||
import { ResolvedWallet } from '../../utils/wallet'
|
||||
import { Token } from '../../models/Token'
|
||||
|
||||
const GIFT_WALLET_FUND_DAI_AMOUNT = Token.fromDecimal('0.1', 18)
|
||||
const GIFT_WALLET_FUND_BZZ_AMOUNT = Token.fromDecimal('0.5', 16)
|
||||
@@ -31,6 +31,9 @@ export default function Index(): ReactElement {
|
||||
|
||||
useEffect(() => {
|
||||
async function mapGiftWallets() {
|
||||
if (!rpcProvider) {
|
||||
return
|
||||
}
|
||||
const results = []
|
||||
for (const giftWallet of giftWallets) {
|
||||
results.push(await ResolvedWallet.make(giftWallet, rpcProvider))
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function SettingsPage(): ReactElement {
|
||||
try {
|
||||
setAndPersistJsonRpcProvider(value)
|
||||
|
||||
const shouldUpdateDesktop = isDesktop && (await getDesktopConfiguration(desktopUrl))['swap-endpoint']
|
||||
const shouldUpdateDesktop = isDesktop && (await getDesktopConfiguration(desktopUrl))['blockchain-rpc-endpoint']
|
||||
|
||||
if (shouldUpdateDesktop) {
|
||||
await setJsonRpcInDesktop(desktopUrl, value)
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@ import { ReactElement } from 'react'
|
||||
import { useNavigate } from 'react-router'
|
||||
import { HistoryHeader } from '../../components/HistoryHeader'
|
||||
import { ROUTES } from '../../routes'
|
||||
import { PostageStampCreation } from './PostageStampCreation'
|
||||
import { PostageStampAdvancedCreation } from './PostageStampAdvancedCreation'
|
||||
|
||||
export function CreatePostageStampPage(): ReactElement {
|
||||
const navigate = useNavigate()
|
||||
@@ -13,8 +13,8 @@ export function CreatePostageStampPage(): ReactElement {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<HistoryHeader>Buy new postage stamp</HistoryHeader>
|
||||
<PostageStampCreation onFinished={onFinished} />
|
||||
<HistoryHeader>Buy new postage stamp batch</HistoryHeader>
|
||||
<PostageStampAdvancedCreation onFinished={onFinished} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ReactElement } from 'react'
|
||||
import { useNavigate } from 'react-router'
|
||||
import { HistoryHeader } from '../../components/HistoryHeader'
|
||||
import { ROUTES } from '../../routes'
|
||||
import { PostageStampStandardCreation } from './PostageStampStandardCreation'
|
||||
|
||||
export function CreatePostageStampBasicPage(): ReactElement {
|
||||
const navigate = useNavigate()
|
||||
|
||||
function onFinished() {
|
||||
navigate(ROUTES.ACCOUNT_STAMPS)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<HistoryHeader>Buy new postage stamp batch</HistoryHeader>
|
||||
<PostageStampStandardCreation onFinished={onFinished} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
import { PostageBatchOptions } from '@ethersphere/bee-js'
|
||||
import { Box, Grid, Typography, createStyles, makeStyles } from '@material-ui/core'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { useSnackbar } from 'notistack'
|
||||
import { ReactElement, useContext, useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import Check from 'remixicon-react/CheckLineIcon'
|
||||
import { SwarmButton } from '../../components/SwarmButton'
|
||||
import { SwarmSelect } from '../../components/SwarmSelect'
|
||||
import { SwarmTextInput } from '../../components/SwarmTextInput'
|
||||
import { Context as BeeContext } from '../../providers/Bee'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import { Context as StampsContext } from '../../providers/Stamps'
|
||||
import { ROUTES } from '../../routes'
|
||||
import {
|
||||
calculateStampPrice,
|
||||
convertAmountToSeconds,
|
||||
convertDepthToBytes,
|
||||
secondsToTimeString,
|
||||
waitUntilStampExists,
|
||||
} from '../../utils'
|
||||
import { getHumanReadableFileSize } from '../../utils/file'
|
||||
|
||||
interface Props {
|
||||
onFinished: () => void
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
link: {
|
||||
color: '#dd7700',
|
||||
textDecoration: 'underline',
|
||||
'&:hover': {
|
||||
textDecoration: 'none',
|
||||
|
||||
// https://github.com/mui-org/material-ui/issues/22543
|
||||
'@media (hover: none)': {
|
||||
textDecoration: 'none',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElement {
|
||||
const classes = useStyles()
|
||||
const { chainState } = useContext(BeeContext)
|
||||
const { refresh } = useContext(StampsContext)
|
||||
const { beeDebugApi } = useContext(SettingsContext)
|
||||
|
||||
const [depthInput, setDepthInput] = useState<string>('')
|
||||
const [amountInput, setAmountInput] = useState<string>('')
|
||||
const [labelInput, setLabelInput] = useState('')
|
||||
const [immutable, setImmutable] = useState(false)
|
||||
const [depthError, setDepthError] = useState<string>('')
|
||||
const [amountError, setAmountError] = useState<string>('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar()
|
||||
|
||||
function getFileSize(depth: number): string {
|
||||
if (isNaN(depth) || depth < 17 || depth > 255) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
return `~${getHumanReadableFileSize(convertDepthToBytes(depth))}`
|
||||
}
|
||||
|
||||
function getTtl(amount: number): string {
|
||||
const isCurrentPriceAvailable = chainState && chainState.currentPrice
|
||||
|
||||
if (amount <= 0 || !isCurrentPriceAvailable) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
const pricePerBlock = Number.parseInt(chainState.currentPrice, 10)
|
||||
|
||||
return `${secondsToTimeString(
|
||||
convertAmountToSeconds(amount, pricePerBlock),
|
||||
)} (with price of ${pricePerBlock.toFixed(0)} per block)`
|
||||
}
|
||||
|
||||
function getPrice(depth: number, amount: bigint): string {
|
||||
const hasInvalidInput = amount <= 0 || isNaN(depth) || depth < 17 || depth > 255
|
||||
|
||||
if (hasInvalidInput) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
const price = calculateStampPrice(depth, amount)
|
||||
|
||||
return `${price.toSignificantDigits()} xBZZ`
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
try {
|
||||
// This is really just a typeguard, the validation pretty much guarantees these will have the right values
|
||||
if (!depthInput || !amountInput) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!beeDebugApi) {
|
||||
return
|
||||
}
|
||||
|
||||
setSubmitting(true)
|
||||
const amount = BigInt(amountInput)
|
||||
const depth = Number.parseInt(depthInput)
|
||||
const options: PostageBatchOptions = {
|
||||
waitForUsable: false,
|
||||
label: labelInput || undefined,
|
||||
immutableFlag: immutable,
|
||||
}
|
||||
|
||||
const batchId = await beeDebugApi.createPostageBatch(amount.toString(), depth, options)
|
||||
await waitUntilStampExists(batchId, beeDebugApi)
|
||||
await refresh()
|
||||
onFinished()
|
||||
} catch (e) {
|
||||
console.error(e) // eslint-disable-line
|
||||
enqueueSnackbar(`Error: ${(e as Error).message}`, { variant: 'error' })
|
||||
}
|
||||
setSubmitting(false)
|
||||
}
|
||||
|
||||
function validateAmountInput(amountInput: string) {
|
||||
let validAmountInput = '0'
|
||||
|
||||
if (!amountInput) {
|
||||
setAmountError('Required field')
|
||||
} else {
|
||||
if (amountInput.indexOf('.') > -1) {
|
||||
setAmountError('Amount must be an integer')
|
||||
} else {
|
||||
const amount = new BigNumber(amountInput)
|
||||
|
||||
if (amount.isNaN()) {
|
||||
setAmountError('Amount must contain only digits')
|
||||
} else if (amount.isLessThanOrEqualTo(0)) {
|
||||
setAmountError('Amount must be greater than 0')
|
||||
} else {
|
||||
setAmountError('')
|
||||
validAmountInput = amountInput
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setAmountInput(validAmountInput)
|
||||
}
|
||||
|
||||
function validateDepthInput(depthInput: string) {
|
||||
let validDepthInput = '0'
|
||||
|
||||
if (!depthInput) {
|
||||
setDepthError('Required field')
|
||||
} else {
|
||||
const depth = new BigNumber(depthInput)
|
||||
|
||||
if (!depth.isInteger()) {
|
||||
setDepthError('Depth must be an integer')
|
||||
} else if (depth.isLessThan(17)) {
|
||||
setDepthError('Minimal depth is 17')
|
||||
} else if (depth.isGreaterThan(255)) {
|
||||
setDepthError('Depth has to be at most 255')
|
||||
} else {
|
||||
setDepthError('')
|
||||
validDepthInput = depthInput
|
||||
}
|
||||
}
|
||||
|
||||
setDepthInput(validDepthInput)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box mb={4}>
|
||||
<Typography>
|
||||
To upload data to Swarm network, you will need to purchase a postage stamp. If you're not familiar with
|
||||
this, please read{' '}
|
||||
<a
|
||||
href="https://medium.com/ethereum-swarm/how-to-upload-data-to-the-swarm-network-c0766c3ae381"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
this guide
|
||||
</a>
|
||||
.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box mb={2}>
|
||||
<SwarmTextInput name="depth" label="Depth" onChange={event => validateDepthInput(event.target.value)} />
|
||||
<Box mt={0.25} sx={{ bgcolor: '#f6f6f6' }} p={2}>
|
||||
<Grid container justifyContent="space-between">
|
||||
<Typography>Corresponding file size</Typography>
|
||||
<Typography>{!depthError && depthInput ? getFileSize(parseInt(depthInput, 10)) : '-'}</Typography>
|
||||
</Grid>
|
||||
</Box>
|
||||
{depthError && <Typography>{depthError}</Typography>}
|
||||
</Box>
|
||||
<Box mb={2}>
|
||||
<SwarmTextInput name="amount" label="Amount" onChange={event => validateAmountInput(event.target.value)} />
|
||||
<Box mt={0.25} sx={{ bgcolor: '#f6f6f6' }} p={2}>
|
||||
<Grid container justifyContent="space-between">
|
||||
<Typography>Corresponding TTL (Time to live)</Typography>
|
||||
<Typography>{!amountError && amountInput ? getTtl(Number.parseInt(amountInput, 10)) : '-'}</Typography>
|
||||
</Grid>
|
||||
</Box>
|
||||
{amountError && <Typography>{amountError}</Typography>}
|
||||
</Box>
|
||||
<Box mb={2}>
|
||||
<SwarmTextInput name="label" label="Label" optional onChange={event => setLabelInput(event.target.value)} />
|
||||
</Box>
|
||||
<Box mb={2}>
|
||||
<SwarmSelect
|
||||
label="Immutable"
|
||||
defaultValue="No"
|
||||
onChange={event => setImmutable(event.target.value === 'Yes')}
|
||||
options={[
|
||||
{ value: 'Yes', label: 'Yes' },
|
||||
{ value: 'No', label: 'No' },
|
||||
]}
|
||||
/>
|
||||
<Box mt={0.25} sx={{ bgcolor: '#f6f6f6' }} p={2}>
|
||||
<Grid container justifyContent="space-between">
|
||||
{immutable && (
|
||||
<Typography>
|
||||
Once an immutable stamp is maxed out, it disallows further content uploads, thereby safeguarding your
|
||||
previously uploaded content from unintentional overwriting.
|
||||
</Typography>
|
||||
)}
|
||||
{!immutable && (
|
||||
<Typography>
|
||||
When a mutable stamp reaches full capacity, it still permits new content uploads. However, this comes
|
||||
with the caveat of overwriting previously uploaded content associated with the same stamp.
|
||||
</Typography>
|
||||
)}
|
||||
</Grid>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box mb={4} sx={{ bgcolor: '#fcf2e8' }} p={2}>
|
||||
<Grid container justifyContent="space-between">
|
||||
<Typography>Indicative Price</Typography>
|
||||
<Typography>
|
||||
{!amountError && !depthError && amountInput && depthInput
|
||||
? getPrice(parseInt(depthInput, 10), BigInt(amountInput))
|
||||
: '-'}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
<Grid container justifyContent="space-between" alignItems="center">
|
||||
<Grid item>
|
||||
<SwarmButton
|
||||
disabled={submitting || Boolean(depthError) || Boolean(amountError) || !depthInput || !amountInput}
|
||||
onClick={submit}
|
||||
iconType={Check}
|
||||
loading={submitting}
|
||||
>
|
||||
Buy New Stamp
|
||||
</SwarmButton>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Link to={ROUTES.ACCOUNT_STAMPS_NEW_STANDARD} className={classes.link}>
|
||||
Standard mode
|
||||
</Link>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
import { PostageBatchOptions } from '@ethersphere/bee-js'
|
||||
import { Box, Grid, Typography } from '@material-ui/core'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { Form, Formik, FormikHelpers } from 'formik'
|
||||
import { useSnackbar } from 'notistack'
|
||||
import { ReactElement, useContext } from 'react'
|
||||
import Check from 'remixicon-react/CheckLineIcon'
|
||||
import { SwarmButton } from '../../components/SwarmButton'
|
||||
import { SwarmTextInput } from '../../components/SwarmTextInput'
|
||||
import { Context as BeeContext } from '../../providers/Bee'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import { Context as StampsContext } from '../../providers/Stamps'
|
||||
import {
|
||||
calculateStampPrice,
|
||||
convertAmountToSeconds,
|
||||
convertDepthToBytes,
|
||||
secondsToTimeString,
|
||||
waitUntilStampExists,
|
||||
} from '../../utils'
|
||||
import { getHumanReadableFileSize } from '../../utils/file'
|
||||
|
||||
interface FormValues {
|
||||
depth?: string
|
||||
amount?: string
|
||||
label?: string
|
||||
}
|
||||
type FormErrors = Partial<FormValues>
|
||||
const initialFormValues: FormValues = {
|
||||
depth: '',
|
||||
amount: '',
|
||||
label: '',
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onFinished: () => void
|
||||
}
|
||||
|
||||
export function PostageStampCreation({ onFinished }: Props): ReactElement {
|
||||
const { chainState } = useContext(BeeContext)
|
||||
const { refresh } = useContext(StampsContext)
|
||||
const { beeDebugApi } = useContext(SettingsContext)
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar()
|
||||
|
||||
function getFileSize(depth: number): string {
|
||||
if (isNaN(depth) || depth < 17 || depth > 255) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
return `~${getHumanReadableFileSize(convertDepthToBytes(depth))}`
|
||||
}
|
||||
|
||||
function getTtl(amount: number): string {
|
||||
const isCurrentPriceAvailable = chainState && chainState.currentPrice
|
||||
|
||||
if (amount <= 0 || !isCurrentPriceAvailable) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
const pricePerBlock = Number.parseInt(chainState.currentPrice, 10)
|
||||
|
||||
return `${secondsToTimeString(
|
||||
convertAmountToSeconds(amount, pricePerBlock),
|
||||
)} (with price of ${pricePerBlock.toFixed(0)} per block)`
|
||||
}
|
||||
|
||||
function getPrice(depth: number, amount: bigint): string {
|
||||
const hasInvalidInput = amount <= 0 || isNaN(depth) || depth < 17 || depth > 255
|
||||
|
||||
if (hasInvalidInput) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
const price = calculateStampPrice(depth, amount)
|
||||
|
||||
return `${price.toSignificantDigits()} xBZZ`
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box mb={4}>
|
||||
<Typography>
|
||||
To upload data to Swarm network, you will need to purchase a postage stamp. If you're not familiar with
|
||||
this, please read{' '}
|
||||
<a
|
||||
href="https://medium.com/ethereum-swarm/how-to-upload-data-to-the-swarm-network-c0766c3ae381"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
this guide
|
||||
</a>
|
||||
.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Formik
|
||||
initialValues={initialFormValues}
|
||||
onSubmit={async (values: FormValues, actions: FormikHelpers<FormValues>) => {
|
||||
try {
|
||||
// This is really just a typeguard, the validation pretty much guarantees these will have the right values
|
||||
if (!values.depth || !values.amount) return
|
||||
|
||||
if (!beeDebugApi) return
|
||||
|
||||
const amount = BigInt(values.amount)
|
||||
const depth = Number.parseInt(values.depth)
|
||||
const options: PostageBatchOptions = { waitForUsable: false, label: values.label || undefined }
|
||||
const batchId = await beeDebugApi.createPostageBatch(amount.toString(), depth, options)
|
||||
await waitUntilStampExists(batchId, beeDebugApi)
|
||||
actions.resetForm()
|
||||
await refresh()
|
||||
onFinished()
|
||||
} catch (e) {
|
||||
console.error(e) // eslint-disable-line
|
||||
enqueueSnackbar(`Error: ${(e as Error).message}`, { variant: 'error' })
|
||||
actions.setSubmitting(false)
|
||||
}
|
||||
}}
|
||||
validate={(values: FormValues) => {
|
||||
const errors: FormErrors = {}
|
||||
|
||||
// Depth
|
||||
if (!values.depth) errors.depth = 'Required field'
|
||||
else {
|
||||
const depth = new BigNumber(values.depth)
|
||||
|
||||
if (!depth.isInteger()) errors.depth = 'Depth must be an integer'
|
||||
else if (depth.isLessThan(17)) errors.depth = 'Minimal depth is 17'
|
||||
else if (depth.isGreaterThan(255)) errors.depth = 'Depth has to be at most 255'
|
||||
}
|
||||
|
||||
// Amount
|
||||
if (!values.amount) errors.amount = 'Required field'
|
||||
else {
|
||||
const amount = new BigNumber(values.amount)
|
||||
|
||||
if (!amount.isInteger()) errors.amount = 'Amount must be an integer'
|
||||
else if (amount.isLessThanOrEqualTo(0)) errors.amount = 'Amount must be greater than 0'
|
||||
}
|
||||
|
||||
return errors
|
||||
}}
|
||||
>
|
||||
{({ submitForm, isValid, isSubmitting, values, errors }) => (
|
||||
<Form>
|
||||
<Box mb={2}>
|
||||
<SwarmTextInput name="depth" label="Depth" formik />
|
||||
<Box mt={0.25} sx={{ bgcolor: '#f6f6f6' }} p={2}>
|
||||
<Grid container justifyContent="space-between">
|
||||
<Typography>Corresponding file size</Typography>
|
||||
<Typography>
|
||||
{!errors.depth && values.depth ? getFileSize(parseInt(values.depth, 10)) : '-'}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box mb={2}>
|
||||
<SwarmTextInput name="amount" label="Amount" formik />
|
||||
<Box mt={0.25} sx={{ bgcolor: '#f6f6f6' }} p={2}>
|
||||
<Grid container justifyContent="space-between">
|
||||
<Typography>Corresponding TTL (Time to live)</Typography>
|
||||
<Typography>
|
||||
{!errors.amount && values.amount ? getTtl(Number.parseInt(values.amount, 10)) : '-'}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box mb={2}>
|
||||
<SwarmTextInput name="label" label="Label" optional formik />
|
||||
</Box>
|
||||
<Box mb={4} sx={{ bgcolor: '#fcf2e8' }} p={2}>
|
||||
<Grid container justifyContent="space-between">
|
||||
<Typography>Indicative Price</Typography>
|
||||
<Typography>
|
||||
{!errors.amount && !errors.depth && values.amount && values.depth
|
||||
? getPrice(parseInt(values.depth, 10), BigInt(values.amount))
|
||||
: '-'}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Box>
|
||||
<SwarmButton
|
||||
disabled={isSubmitting || !isValid || !values.amount || !values.depth}
|
||||
onClick={submitForm}
|
||||
iconType={Check}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Buy New Stamp
|
||||
</SwarmButton>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -23,7 +23,10 @@ export function PostageStampSelector({ onSelect, defaultValue }: Props): ReactEl
|
||||
|
||||
return (
|
||||
<SwarmSelect
|
||||
options={(stamps || []).map(x => ({ label: x.batchID.slice(0, 8), value: x.batchID }))}
|
||||
options={(stamps || []).map(x => ({
|
||||
label: x.label ? x.batchID.slice(0, 8) + ' - ' + x.label : x.batchID.slice(0, 8),
|
||||
value: x.batchID,
|
||||
}))}
|
||||
onChange={event => onChange(event.target.value as string)}
|
||||
defaultValue={defaultValue}
|
||||
placeholder="Please select a postage stamp..."
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
import { PostageBatchOptions, Utils } from '@ethersphere/bee-js'
|
||||
import { Box, Button, Grid, Slider, Typography } from '@material-ui/core'
|
||||
import { Theme, createStyles, makeStyles } from '@material-ui/core/styles'
|
||||
import { useSnackbar } from 'notistack'
|
||||
import { ReactElement, useContext, useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import Check from 'remixicon-react/CheckLineIcon'
|
||||
import { SwarmButton } from '../../components/SwarmButton'
|
||||
import { SwarmTextInput } from '../../components/SwarmTextInput'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import { Context as StampsContext } from '../../providers/Stamps'
|
||||
import { ROUTES } from '../../routes'
|
||||
import { calculateStampPrice, convertAmountToSeconds, secondsToTimeString, waitUntilStampExists } from '../../utils'
|
||||
|
||||
interface Props {
|
||||
onFinished: () => void
|
||||
}
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
link: {
|
||||
color: '#dd7700',
|
||||
textDecoration: 'underline',
|
||||
'&:hover': {
|
||||
textDecoration: 'none',
|
||||
|
||||
// https://github.com/mui-org/material-ui/issues/22543
|
||||
'@media (hover: none)': {
|
||||
textDecoration: 'none',
|
||||
},
|
||||
},
|
||||
},
|
||||
buttonSelected: {
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
const marks = [
|
||||
{ value: 1, label: '1 day' },
|
||||
{ value: 365, label: '365 days' },
|
||||
]
|
||||
|
||||
export function PostageStampStandardCreation({ onFinished }: Props): ReactElement {
|
||||
const classes = useStyles()
|
||||
const { refresh } = useContext(StampsContext)
|
||||
const { beeDebugApi } = useContext(SettingsContext)
|
||||
|
||||
const [depthInput, setDepthInput] = useState<number>(Utils.getDepthForCapacity(4))
|
||||
const [amountInput, setAmountInput] = useState<string>(Utils.getAmountForTtl(30))
|
||||
const [labelInput, setLabelInput] = useState('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [buttonValue, setButtonValue] = useState(4)
|
||||
|
||||
function sliderValueChange(_: unknown, newValue: number | number[]) {
|
||||
if (typeof newValue !== 'number') {
|
||||
return
|
||||
}
|
||||
const amountValue = Utils.getAmountForTtl(newValue)
|
||||
setAmountInput(amountValue)
|
||||
}
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar()
|
||||
|
||||
function getTtl(amount: string): string {
|
||||
const pricePerBlock = 24000
|
||||
|
||||
return `${secondsToTimeString(
|
||||
convertAmountToSeconds(parseInt(amount, 10), pricePerBlock),
|
||||
)} (with price of ${pricePerBlock.toFixed(0)} per block)`
|
||||
}
|
||||
|
||||
function getPrice(depth: number, amount: bigint): string {
|
||||
const price = calculateStampPrice(depth, amount)
|
||||
|
||||
return `${price.toSignificantDigits()} xBZZ`
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
try {
|
||||
// This is really just a typeguard, the validation pretty much guarantees these will have the right values
|
||||
if (!depthInput || !amountInput) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!beeDebugApi) {
|
||||
return
|
||||
}
|
||||
|
||||
setSubmitting(true)
|
||||
const amount = BigInt(amountInput)
|
||||
const depth = depthInput
|
||||
const options: PostageBatchOptions = {
|
||||
waitForUsable: false,
|
||||
label: labelInput || undefined,
|
||||
immutableFlag: true,
|
||||
}
|
||||
|
||||
const batchId = await beeDebugApi.createPostageBatch(amount.toString(), depth, options)
|
||||
await waitUntilStampExists(batchId, beeDebugApi)
|
||||
await refresh()
|
||||
onFinished()
|
||||
} catch (e) {
|
||||
console.error(e) // eslint-disable-line
|
||||
enqueueSnackbar(`Error: ${(e as Error).message}`, { variant: 'error' })
|
||||
}
|
||||
setSubmitting(false)
|
||||
}
|
||||
|
||||
function handleBatchSize(gigabytes: number) {
|
||||
setButtonValue(gigabytes)
|
||||
const capacity = Utils.getDepthForCapacity(gigabytes)
|
||||
setDepthInput(capacity)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box mb={4}>
|
||||
<Typography>
|
||||
A postage stamp batch containes postage stamps that will give you the right to upload data to the Swarm
|
||||
network. If you're not familiar with this, please read
|
||||
<a
|
||||
href="https://medium.com/ethereum-swarm/how-to-upload-data-to-the-swarm-network-c0766c3ae381"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
this guide
|
||||
</a>
|
||||
.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box mb={1}>
|
||||
<Typography variant="h2">Batch name</Typography>
|
||||
</Box>
|
||||
<Box mb={2}>
|
||||
<SwarmTextInput name="depth" label="Label" onChange={e => setLabelInput(e.target.value)} />
|
||||
</Box>
|
||||
<Box mb={1}>
|
||||
<Typography variant="h2">Batch size</Typography>
|
||||
</Box>
|
||||
<Box mb={2}>
|
||||
<Grid container justifyContent="space-between" spacing={2}>
|
||||
<Grid item xs={4}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={() => handleBatchSize(4)}
|
||||
className={buttonValue === 4 ? classes.buttonSelected : ''}
|
||||
>
|
||||
4 GB
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={() => handleBatchSize(32)}
|
||||
className={buttonValue === 32 ? classes.buttonSelected : ''}
|
||||
>
|
||||
32 GB
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={() => handleBatchSize(256)}
|
||||
className={buttonValue === 256 ? classes.buttonSelected : ''}
|
||||
>
|
||||
256 GB
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
<Box mb={1}>
|
||||
<Typography variant="h2">Data persistence</Typography>
|
||||
</Box>
|
||||
<Box mb={2}>
|
||||
<Slider
|
||||
aria-label="Volume"
|
||||
min={1}
|
||||
max={365}
|
||||
step={1}
|
||||
marks={marks}
|
||||
valueLabelDisplay="auto"
|
||||
defaultValue={30}
|
||||
onChange={sliderValueChange}
|
||||
/>
|
||||
</Box>
|
||||
<Box mb={2}>
|
||||
<Box mt={0.25} sx={{ bgcolor: '#f6f6f6' }} p={2}>
|
||||
<Grid container justifyContent="space-between">
|
||||
<Typography>Corresponding TTL (Time to live)</Typography>
|
||||
<Typography>{amountInput ? getTtl(amountInput) : '-'}</Typography>
|
||||
</Grid>
|
||||
</Box>
|
||||
<Box display="flex" justifyContent={'right'} mt={0.5}>
|
||||
<Typography style={{ fontSize: '10px', color: 'rgba(0, 0, 0, 0.26)' }}>
|
||||
Current price of 24000 per block
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box mb={4} sx={{ bgcolor: '#fcf2e8' }} p={2}>
|
||||
<Grid container justifyContent="space-between">
|
||||
<Typography>Indicative Price</Typography>
|
||||
<Typography>{getPrice(depthInput, BigInt(amountInput))}</Typography>
|
||||
</Grid>
|
||||
</Box>
|
||||
<Grid container justifyContent="space-between" alignItems="center">
|
||||
<Grid item>
|
||||
<SwarmButton
|
||||
disabled={submitting || !depthInput || !amountInput}
|
||||
onClick={submit}
|
||||
iconType={Check}
|
||||
loading={submitting}
|
||||
>
|
||||
Buy New Stamp
|
||||
</SwarmButton>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Link to={ROUTES.ACCOUNT_STAMPS_NEW_ADVANCED} className={classes.link}>
|
||||
Advanced mode
|
||||
</Link>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
import type { ReactElement } from 'react'
|
||||
import { ReactElement, useContext } from 'react'
|
||||
import TimerFlashFill from 'remixicon-react/TimerFlashFillIcon'
|
||||
import TimerFlashLine from 'remixicon-react/TimerFlashLineIcon'
|
||||
import ExpandableElement from '../../components/ExpandableElement'
|
||||
import ExpandableList from '../../components/ExpandableList'
|
||||
import ExpandableListItem from '../../components/ExpandableListItem'
|
||||
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
|
||||
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
|
||||
import StampExtensionModal from '../../components/StampExtensionModal'
|
||||
import { Context } from '../../providers/Settings'
|
||||
import { EnrichedPostageBatch } from '../../providers/Stamps'
|
||||
import { secondsToTimeString } from '../../utils'
|
||||
import { getHumanReadableFileSize } from '../../utils/file'
|
||||
@@ -13,7 +18,11 @@ interface Props {
|
||||
}
|
||||
|
||||
function StampsTable({ postageStamps }: Props): ReactElement | null {
|
||||
if (postageStamps === null) return null
|
||||
const { beeDebugApi } = useContext(Context)
|
||||
|
||||
if (!postageStamps || !beeDebugApi) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<ExpandableList label="Postage Stamps" defaultOpen>
|
||||
@@ -38,7 +47,22 @@ function StampsTable({ postageStamps }: Props): ReactElement | null {
|
||||
<ExpandableListItem label="Label" value={stamp.label} />
|
||||
<ExpandableListItem label="Usable" value={stamp.usable ? 'yes' : 'no'} />
|
||||
<ExpandableListItem label="Exists" value={stamp.exists ? 'yes' : 'no'} />
|
||||
<ExpandableListItem label="Immutable" value={stamp.immutableFlag ? 'yes' : 'no'} />
|
||||
<ExpandableListItem label="Purchase Block Number" value={stamp.blockNumber} />
|
||||
<ExpandableListItemActions>
|
||||
<StampExtensionModal
|
||||
type="Topup"
|
||||
icon={<TimerFlashFill size="1rem" />}
|
||||
beeDebug={beeDebugApi}
|
||||
stamp={stamp.batchID}
|
||||
/>
|
||||
<StampExtensionModal
|
||||
type="Dilute"
|
||||
icon={<TimerFlashLine size="1rem" />}
|
||||
beeDebug={beeDebugApi}
|
||||
stamp={stamp.batchID}
|
||||
/>
|
||||
</ExpandableListItemActions>
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -44,7 +44,7 @@ export default function Stamp(): ReactElement {
|
||||
if (status.all === CheckState.ERROR) return <TroubleshootConnectionCard />
|
||||
|
||||
function navigateToNewStamp() {
|
||||
navigate(ROUTES.ACCOUNT_STAMPS_NEW)
|
||||
navigate(ROUTES.ACCOUNT_STAMPS_NEW_STANDARD)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function EthereumConnectionCheck(): ReactElement | null {
|
||||
Getblock
|
||||
</a>
|
||||
. By default, Bee expects a local node at http://localhost:8545. To use a provider instead, simply change
|
||||
the <strong>swap-endpoint</strong> in your configuration file.
|
||||
the <strong>blockchain-rpc-endpoint</strong> in your configuration file.
|
||||
</>
|
||||
)}
|
||||
</ExpandableListItemNote>
|
||||
|
||||
@@ -162,12 +162,12 @@ export function Swap({ header }: Props): ReactElement {
|
||||
)
|
||||
}
|
||||
|
||||
if (!desktopConfiguration['swap-endpoint']) {
|
||||
throw new SwapError('Swap endpoint is not configured in Swarm Desktop')
|
||||
if (!desktopConfiguration['blockchain-rpc-endpoint']) {
|
||||
throw new SwapError('Blockchain RPC endpoint is not configured in Swarm Desktop')
|
||||
}
|
||||
await wrapWithSwapError(
|
||||
Rpc.getNetworkChainId(desktopConfiguration['swap-endpoint']),
|
||||
`Swap endpoint not reachable at ${desktopConfiguration['swap-endpoint']}`,
|
||||
Rpc.getNetworkChainId(desktopConfiguration['blockchain-rpc-endpoint']),
|
||||
`Blockchain RPC endpoint not reachable at ${desktopConfiguration['blockchain-rpc-endpoint']}`,
|
||||
)
|
||||
await wrapWithSwapError(sendSwapRequest(daiToSwap), GENERIC_SWAP_FAILED_ERROR_MESSAGE)
|
||||
}
|
||||
|
||||
+11
-1
@@ -9,10 +9,11 @@ import {
|
||||
Peer,
|
||||
Topology,
|
||||
} from '@ethersphere/bee-js'
|
||||
import { createContext, ReactChild, ReactElement, useContext, useEffect, useState } from 'react'
|
||||
import { ReactChild, ReactElement, createContext, useContext, useEffect, useState } from 'react'
|
||||
import semver from 'semver'
|
||||
import PackageJson from '../../package.json'
|
||||
import { useLatestBeeRelease } from '../hooks/apiHooks'
|
||||
import { BzzToken } from '../models/BzzToken'
|
||||
import { Token } from '../models/Token'
|
||||
import type { Balance, ChequebookBalance, Settlements } from '../types'
|
||||
import { Context as SettingsContext } from './Settings'
|
||||
@@ -62,6 +63,7 @@ interface ContextInterface {
|
||||
chequebookAddress: ChequebookAddressResponse | null
|
||||
peers: Peer[] | null
|
||||
chequebookBalance: ChequebookBalance | null
|
||||
stake: BzzToken | null
|
||||
peerBalances: Balance[] | null
|
||||
peerCheques: LastChequesResponse | null
|
||||
settlements: Settlements | null
|
||||
@@ -98,6 +100,7 @@ const initialValues: ContextInterface = {
|
||||
nodeInfo: null,
|
||||
topology: null,
|
||||
chequebookAddress: null,
|
||||
stake: null,
|
||||
peers: null,
|
||||
chequebookBalance: null,
|
||||
peerBalances: null,
|
||||
@@ -225,6 +228,7 @@ export function Provider({ children, isDesktop }: Props): ReactElement {
|
||||
const [chequebookAddress, setChequebookAddress] = useState<ChequebookAddressResponse | null>(null)
|
||||
const [peers, setPeers] = useState<Peer[] | null>(null)
|
||||
const [chequebookBalance, setChequebookBalance] = useState<ChequebookBalance | null>(null)
|
||||
const [stake, setStake] = useState<BzzToken | null>(null)
|
||||
const [peerBalances, setPeerBalances] = useState<Balance[] | null>(null)
|
||||
const [peerCheques, setPeerCheques] = useState<LastChequesResponse | null>(null)
|
||||
const [settlements, setSettlements] = useState<Settlements | null>(null)
|
||||
@@ -388,6 +392,11 @@ export function Provider({ children, isDesktop }: Props): ReactElement {
|
||||
.then(setChequebookBalance)
|
||||
.catch(() => setChequebookBalance(null)),
|
||||
|
||||
beeDebugApi
|
||||
.getStake({ timeout: TIMEOUT })
|
||||
.then(stake => setStake(new BzzToken(stake)))
|
||||
.catch(() => setStake(null)),
|
||||
|
||||
// Peer balances
|
||||
peerBalanceWrapper()
|
||||
.then(setPeerBalances)
|
||||
@@ -471,6 +480,7 @@ export function Provider({ children, isDesktop }: Props): ReactElement {
|
||||
chequebookAddress,
|
||||
peers,
|
||||
chequebookBalance,
|
||||
stake,
|
||||
peerBalances,
|
||||
peerCheques,
|
||||
settlements,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Bee, BeeDebug } from '@ethersphere/bee-js'
|
||||
import { providers } from 'ethers'
|
||||
import { createContext, ReactNode, ReactElement, useEffect, useState } from 'react'
|
||||
import { useGetBeeConfig } from '../hooks/apiHooks'
|
||||
import { ReactElement, ReactNode, createContext, useEffect, useState } from 'react'
|
||||
import { DEFAULT_BEE_API_HOST, DEFAULT_BEE_DEBUG_API_HOST, DEFAULT_RPC_URL } from '../constants'
|
||||
import { useGetBeeConfig } from '../hooks/apiHooks'
|
||||
|
||||
const LocalStorageKeys = {
|
||||
providerUrl: 'json-rpc-provider',
|
||||
@@ -18,7 +18,7 @@ interface ContextInterface {
|
||||
isDesktop: boolean
|
||||
desktopUrl: string
|
||||
rpcProviderUrl: string
|
||||
rpcProvider: providers.JsonRpcProvider
|
||||
rpcProvider: providers.JsonRpcProvider | null
|
||||
cors: string | null
|
||||
dataDir: string | null
|
||||
ensResolver: string | null
|
||||
@@ -42,7 +42,7 @@ const initialValues: ContextInterface = {
|
||||
desktopUrl: window.location.origin,
|
||||
setAndPersistJsonRpcProvider: async () => {}, // eslint-disable-line
|
||||
rpcProviderUrl: '',
|
||||
rpcProvider: new providers.JsonRpcProvider(''),
|
||||
rpcProvider: null,
|
||||
cors: null,
|
||||
dataDir: null,
|
||||
ensResolver: null,
|
||||
|
||||
+11
-4
@@ -2,6 +2,7 @@ import { ReactElement, useContext } from 'react'
|
||||
import { Route, Routes } from 'react-router-dom'
|
||||
import { AccountChequebook } from './pages/account/chequebook/AccountChequebook'
|
||||
import { AccountFeeds } from './pages/account/feeds/AccountFeeds'
|
||||
import { AccountStaking } from './pages/account/staking/AccountStaking'
|
||||
import { AccountStamps } from './pages/account/stamps/AccountStamps'
|
||||
import { AccountWallet } from './pages/account/wallet/AccountWallet'
|
||||
import CreateNewFeed from './pages/feeds/CreateNewFeed'
|
||||
@@ -14,16 +15,17 @@ import { UploadLander } from './pages/files/UploadLander'
|
||||
import GiftCards from './pages/gift-code'
|
||||
import Info from './pages/info'
|
||||
import LightModeRestart from './pages/restart/LightModeRestart'
|
||||
import TopUp from './pages/top-up'
|
||||
import Settings from './pages/settings'
|
||||
import { CreatePostageStampPage } from './pages/stamps/CreatePostageStampPage'
|
||||
import { CreatePostageStampPage } from './pages/stamps/CreatePostageStampAdvancedPage'
|
||||
import Status from './pages/status'
|
||||
import TopUp from './pages/top-up'
|
||||
import { BankCardTopUpIndex } from './pages/top-up/BankCardTopUpIndex'
|
||||
import { CryptoTopUpIndex } from './pages/top-up/CryptoTopUpIndex'
|
||||
import { GiftCardFund } from './pages/top-up/GiftCardFund'
|
||||
import { GiftCardTopUpIndex } from './pages/top-up/GiftCardTopUpIndex'
|
||||
import { Swap } from './pages/top-up/Swap'
|
||||
import { Context as SettingsContext } from './providers/Settings'
|
||||
import { CreatePostageStampBasicPage } from './pages/stamps/CreatePostageStampStandardPage'
|
||||
|
||||
export enum ROUTES {
|
||||
INFO = '/',
|
||||
@@ -45,12 +47,14 @@ export enum ROUTES {
|
||||
ACCOUNT_WALLET = '/account/wallet',
|
||||
ACCOUNT_CHEQUEBOOK = '/account/chequebook',
|
||||
ACCOUNT_STAMPS = '/account/stamps',
|
||||
ACCOUNT_STAMPS_NEW = '/account/stamps/new',
|
||||
ACCOUNT_STAMPS_NEW_STANDARD = '/account/stamps/new',
|
||||
ACCOUNT_STAMPS_NEW_ADVANCED = '/account/stamps/new/advanced',
|
||||
ACCOUNT_FEEDS = '/account/feeds',
|
||||
ACCOUNT_FEEDS_NEW = '/account/feeds/new',
|
||||
ACCOUNT_FEEDS_UPDATE = '/account/feeds/update/:hash',
|
||||
ACCOUNT_FEEDS_VIEW = '/account/feeds/:uuid',
|
||||
ACCOUNT_INVITATIONS = '/account/invitations',
|
||||
ACCOUNT_STAKING = '/account/staking',
|
||||
}
|
||||
|
||||
export const ACCOUNT_TABS = [
|
||||
@@ -58,6 +62,7 @@ export const ACCOUNT_TABS = [
|
||||
ROUTES.ACCOUNT_CHEQUEBOOK,
|
||||
ROUTES.ACCOUNT_STAMPS,
|
||||
ROUTES.ACCOUNT_FEEDS,
|
||||
ROUTES.ACCOUNT_STAKING,
|
||||
]
|
||||
|
||||
const BaseRouter = (): ReactElement => {
|
||||
@@ -83,11 +88,13 @@ const BaseRouter = (): ReactElement => {
|
||||
<Route path={ROUTES.ACCOUNT_WALLET} element={<AccountWallet />} />
|
||||
<Route path={ROUTES.ACCOUNT_CHEQUEBOOK} element={<AccountChequebook />} />
|
||||
<Route path={ROUTES.ACCOUNT_STAMPS} element={<AccountStamps />} />
|
||||
<Route path={ROUTES.ACCOUNT_STAMPS_NEW} element={<CreatePostageStampPage />} />
|
||||
<Route path={ROUTES.ACCOUNT_STAMPS_NEW_STANDARD} element={<CreatePostageStampBasicPage />} />
|
||||
<Route path={ROUTES.ACCOUNT_STAMPS_NEW_ADVANCED} element={<CreatePostageStampPage />} />
|
||||
<Route path={ROUTES.ACCOUNT_FEEDS} element={<AccountFeeds />} />
|
||||
<Route path={ROUTES.ACCOUNT_FEEDS_NEW} element={<CreateNewFeed />} />
|
||||
<Route path={ROUTES.ACCOUNT_FEEDS_UPDATE} element={<UpdateFeed />} />
|
||||
<Route path={ROUTES.ACCOUNT_FEEDS_VIEW} element={<FeedSubpage />} />
|
||||
<Route path={ROUTES.ACCOUNT_STAKING} element={<AccountStaking />} />
|
||||
{isDesktop && <Route path={ROUTES.ACCOUNT_INVITATIONS} element={<GiftCards />} />}
|
||||
</Routes>
|
||||
)
|
||||
|
||||
@@ -159,6 +159,23 @@ const componentsOverrides = (theme: Theme) => ({
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
},
|
||||
MuiSlider: {
|
||||
root: {
|
||||
'& .MuiSlider-valueLabel': {
|
||||
top: '-27px',
|
||||
'& span': {
|
||||
height: '20px',
|
||||
borderRadius: '0px',
|
||||
transform: 'none',
|
||||
'& span': {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
transform: 'none',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const propsOverrides = {
|
||||
|
||||
@@ -17,7 +17,7 @@ export interface BeeConfig {
|
||||
'resolver-options': string
|
||||
'use-postage-snapshot': boolean
|
||||
'data-dir': string
|
||||
'swap-endpoint'?: string
|
||||
'blockchain-rpc-endpoint'?: string
|
||||
}
|
||||
|
||||
export async function getBzzPriceAsDai(desktopUrl: string): Promise<Token> {
|
||||
@@ -29,13 +29,13 @@ export async function getBzzPriceAsDai(desktopUrl: string): Promise<Token> {
|
||||
export function upgradeToLightNode(desktopUrl: string, rpcProvider: string): Promise<BeeConfig> {
|
||||
return updateDesktopConfiguration(desktopUrl, {
|
||||
'swap-enable': true,
|
||||
'swap-endpoint': rpcProvider,
|
||||
'blockchain-rpc-endpoint': rpcProvider,
|
||||
})
|
||||
}
|
||||
|
||||
export async function setJsonRpcInDesktop(desktopUrl: string, value: string): Promise<void> {
|
||||
await updateDesktopConfiguration(desktopUrl, {
|
||||
'swap-endpoint': value,
|
||||
'blockchain-rpc-endpoint': value,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+3
-6
@@ -1,4 +1,4 @@
|
||||
import { BatchId, BeeDebug, BeeResponseError, PostageBatch } from '@ethersphere/bee-js'
|
||||
import { BatchId, BeeDebug, PostageBatch } from '@ethersphere/bee-js'
|
||||
import { decodeCid } from '@ethersphere/swarm-cid'
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
import { BZZ_LINK_DOMAIN } from '../constants'
|
||||
@@ -258,11 +258,8 @@ async function waitForStamp(
|
||||
const stamp = await beeDebug.getPostageBatch(batchId)
|
||||
|
||||
if (stamp[field]) return stamp
|
||||
} catch (e) {
|
||||
// TODO: Workaround for https://github.com/ethersphere/bee/issues/3300
|
||||
if ((e as BeeResponseError).message !== 'Bad Request: cannot get batch') {
|
||||
throw e
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
await sleepMs(pollingFrequency)
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import { Bee, Utils } from '@ethersphere/bee-js'
|
||||
import { loadAllNodes, MantarayNode, MetadataMapping, Reference } from 'mantaray-js'
|
||||
|
||||
interface ValueNode extends MantarayNode {
|
||||
getEntry: Reference
|
||||
}
|
||||
|
||||
/**
|
||||
* The ASCII code of the character `/`.
|
||||
*
|
||||
* This prefix of the root node holds metadata such as the index document.
|
||||
*/
|
||||
const INDEX_DOCUMENT_FORK_PREFIX = '47'
|
||||
|
||||
export class ManifestJs {
|
||||
private bee: Bee
|
||||
|
||||
constructor(bee: Bee) {
|
||||
this.bee = bee
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether a given Swarm hash is a valid mantaray manifest
|
||||
*/
|
||||
public async isManifest(hash: string): Promise<boolean> {
|
||||
try {
|
||||
const data = await this.bee.downloadData(hash)
|
||||
const node = new MantarayNode()
|
||||
node.deserialize(data)
|
||||
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves `website-index-document` from a Swarm hash, or `null` if it is not present
|
||||
*/
|
||||
public async getIndexDocumentPath(hash: string): Promise<string | null> {
|
||||
const metadata = await this.getRootSlashMetadata(hash)
|
||||
|
||||
if (!metadata) {
|
||||
return null
|
||||
}
|
||||
|
||||
return metadata['website-index-document'] || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all paths with the associated hashes from a Swarm manifest
|
||||
*/
|
||||
public async getHashes(hash: string): Promise<Record<string, string>> {
|
||||
const data = await this.bee.downloadData(hash)
|
||||
const node = new MantarayNode()
|
||||
node.deserialize(data)
|
||||
await loadAllNodes(this.load.bind(this), node)
|
||||
const result = {}
|
||||
this.extractHashes(result, node)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves an arbitrary Swarm feed manifest to its latest update reference.
|
||||
* @returns `/bzz` root manifest hash, or `Promise<null>` if hash is not a feed manifest
|
||||
* @throws in case of network errors or bad input
|
||||
*/
|
||||
public async resolveFeedManifest(hash: string): Promise<string | null> {
|
||||
const metadata = await this.getRootSlashMetadata(hash)
|
||||
|
||||
if (!metadata) {
|
||||
return null
|
||||
}
|
||||
|
||||
const owner = metadata['swarm-feed-owner']
|
||||
const topic = metadata['swarm-feed-topic']
|
||||
|
||||
if (!owner || !topic) {
|
||||
return null
|
||||
}
|
||||
|
||||
const reader = this.bee.makeFeedReader('sequence', topic, owner)
|
||||
const response = await reader.download()
|
||||
|
||||
return response.reference
|
||||
}
|
||||
|
||||
private async getRootSlashMetadata(hash: string): Promise<MetadataMapping | null> {
|
||||
const data = await this.bee.downloadData(hash)
|
||||
const node = new MantarayNode()
|
||||
node.deserialize(data)
|
||||
|
||||
if (!node.forks) {
|
||||
return null
|
||||
}
|
||||
const fork = node.forks[INDEX_DOCUMENT_FORK_PREFIX]
|
||||
|
||||
if (!fork) {
|
||||
return null
|
||||
}
|
||||
const metadataNode = fork.node
|
||||
|
||||
if (!metadataNode.IsWithMetadataType()) {
|
||||
return null
|
||||
}
|
||||
const metadata = metadataNode.getMetadata
|
||||
|
||||
if (!metadata) {
|
||||
return null
|
||||
}
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
private extractHashes(result: Record<string, string>, node: MantarayNode, prefix = ''): void {
|
||||
if (!node.forks) {
|
||||
return
|
||||
}
|
||||
for (const fork of Object.values(node.forks)) {
|
||||
const path = prefix + this.bytesToUtf8(fork.prefix)
|
||||
const childNode = fork.node
|
||||
|
||||
if (this.isValueNode(childNode, path)) {
|
||||
result[path] = Utils.bytesToHex(childNode.getEntry)
|
||||
}
|
||||
|
||||
if (childNode.isEdgeType()) {
|
||||
this.extractHashes(result, childNode, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private load(reference: Uint8Array) {
|
||||
return this.bee.downloadData(Utils.bytesToHex(reference))
|
||||
}
|
||||
|
||||
private bytesToUtf8(bytes: Uint8Array): string {
|
||||
return new TextDecoder('utf-8').decode(bytes)
|
||||
}
|
||||
|
||||
private isValueNode(node: MantarayNode, path: string): node is ValueNode {
|
||||
return !this.isRootSlash(node, path) && node.isValueType() && typeof node.getEntry !== 'undefined'
|
||||
}
|
||||
|
||||
private isRootSlash(node: MantarayNode, path: string): boolean {
|
||||
return path === '/' && node.IsWithMetadataType()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user