From a6b4293122ecba476d65685596f9a9fa3bae5a41 Mon Sep 17 00:00:00 2001 From: Cafe137 Date: Tue, 3 May 2022 22:17:25 +0200 Subject: [PATCH 01/15] feat: add top up --- src/App.tsx | 23 ++-- src/components/CashoutModal.tsx | 4 +- src/components/ExpandableListItemKey.tsx | 5 +- src/components/SideBar.tsx | 28 ++++- src/pages/accounting/PeerBalances.tsx | 6 +- src/pages/files/SelectStamp.tsx | 4 +- src/pages/files/Upload.tsx | 6 +- src/pages/rpc/Confirmation.tsx | 20 ++++ src/pages/rpc/index.tsx | 61 +++++++++++ src/pages/top-up/BankCardTopUpIndex.tsx | 24 +++++ src/pages/top-up/CryptoTopUpIndex.tsx | 23 ++++ src/pages/top-up/Fund.tsx | 102 ++++++++++++++++++ src/pages/top-up/Swap.tsx | 112 ++++++++++++++++++++ src/pages/top-up/TopUpProgressIndicator.tsx | 10 ++ src/pages/top-up/index.tsx | 49 +++++++++ src/providers/TopUp.tsx | 93 ++++++++++++++++ src/routes.tsx | 28 +++++ src/utils/bzz-contract-interface.ts | 25 +++++ src/utils/identity.ts | 8 +- src/utils/rpc.ts | 63 ++++++++++- src/utils/swap.ts | 64 +++++++++++ 21 files changed, 727 insertions(+), 31 deletions(-) create mode 100644 src/pages/rpc/Confirmation.tsx create mode 100644 src/pages/rpc/index.tsx create mode 100644 src/pages/top-up/BankCardTopUpIndex.tsx create mode 100644 src/pages/top-up/CryptoTopUpIndex.tsx create mode 100644 src/pages/top-up/Fund.tsx create mode 100644 src/pages/top-up/Swap.tsx create mode 100644 src/pages/top-up/TopUpProgressIndicator.tsx create mode 100644 src/pages/top-up/index.tsx create mode 100644 src/providers/TopUp.tsx create mode 100644 src/utils/bzz-contract-interface.ts create mode 100644 src/utils/swap.ts diff --git a/src/App.tsx b/src/App.tsx index e1a2acfc..430dc45c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,6 +11,7 @@ import { Provider as FileProvider } from './providers/File' import { Provider as PlatformProvider } from './providers/Platform' import { Provider as SettingsProvider } from './providers/Settings' import { Provider as StampsProvider } from './providers/Stamps' +import { Provider as TopUpProvider } from './providers/TopUp' import BaseRouter from './routes' import { theme } from './theme' @@ -29,16 +30,18 @@ const App = ({ beeApiUrl, beeDebugApiUrl, lockedApiSettings }: Props): ReactElem - - - <> - - - - - - - + + + + <> + + + + + + + + diff --git a/src/components/CashoutModal.tsx b/src/components/CashoutModal.tsx index 36a7b915..4e66c551 100644 --- a/src/components/CashoutModal.tsx +++ b/src/components/CashoutModal.tsx @@ -6,7 +6,7 @@ import DialogContent from '@material-ui/core/DialogContent' import DialogContentText from '@material-ui/core/DialogContentText' import DialogTitle from '@material-ui/core/DialogTitle' import { useSnackbar } from 'notistack' -import { ReactElement, useState, useContext } from 'react' +import { ReactElement, useContext, useState } from 'react' import { Zap } from 'react-feather' import { Context as SettingsContext } from '../providers/Settings' import EthereumAddress from './EthereumAddress' @@ -61,7 +61,7 @@ export default function CheckoutModal({ peerId, uncashedAmount }: Props): ReactE return (
Cashout Cheque diff --git a/src/components/ExpandableListItemKey.tsx b/src/components/ExpandableListItemKey.tsx index c874a410..2d4587fe 100644 --- a/src/components/ExpandableListItemKey.tsx +++ b/src/components/ExpandableListItemKey.tsx @@ -38,6 +38,7 @@ const useStyles = makeStyles((theme: Theme) => interface Props { label: string value: string + expanded?: boolean } const lengthWithoutPrefix = (s: string) => s.replace(/^0x/i, '').length @@ -54,9 +55,9 @@ const split = (s: string): string[] => { return s.match(/(0x|.{1,8})/gi) || [] } -export default function ExpandableListItemKey({ label, value }: Props): ReactElement | null { +export default function ExpandableListItemKey({ label, value, expanded }: Props): ReactElement | null { const classes = useStyles() - const [open, setOpen] = useState(false) + const [open, setOpen] = useState(expanded || false) const [copied, setCopied] = useState(false) const toggleOpen = () => setOpen(!open) diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx index dc2f4c21..e13ca677 100644 --- a/src/components/SideBar.tsx +++ b/src/components/SideBar.tsx @@ -2,7 +2,18 @@ import { Divider, Drawer, Grid, Link as MUILink, List } from '@material-ui/core' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import { OpenInNewSharp } from '@material-ui/icons' import type { ReactElement } from 'react' -import { Bookmark, BookOpen, DollarSign, FileText, Home, Layers, Settings } from 'react-feather' +import { + Battery, + BatteryCharging, + Bookmark, + BookOpen, + Briefcase, + DollarSign, + FileText, + Home, + Layers, + Settings, +} from 'react-feather' import { Link } from 'react-router-dom' import Logo from '../assets/logo.svg' import { config } from '../config' @@ -41,6 +52,21 @@ const navBarItems = [ path: ROUTES.SETTINGS, icon: Settings, }, + { + label: 'Wallet', + path: ROUTES.WALLET, + icon: Briefcase, + }, + { + label: 'Crypto', + path: ROUTES.TOP_UP_CRYPTO, + icon: Battery, + }, + { + label: 'Bank Card', + path: ROUTES.TOP_UP_BANK_CARD, + icon: BatteryCharging, + }, ] const drawerWidth = 300 diff --git a/src/pages/accounting/PeerBalances.tsx b/src/pages/accounting/PeerBalances.tsx index 6ed91905..48016d40 100644 --- a/src/pages/accounting/PeerBalances.tsx +++ b/src/pages/accounting/PeerBalances.tsx @@ -1,11 +1,9 @@ import type { ReactElement } from 'react' - +import CashoutModal from '../../components/CashoutModal' import ExpandableList from '../../components/ExpandableList' import ExpandableListItem from '../../components/ExpandableListItem' import ExpandableListItemActions from '../../components/ExpandableListItemActions' import ExpandableListItemKey from '../../components/ExpandableListItemKey' - -import CashoutModal from '../../components/CashoutModal' import { Accounting } from '../../hooks/accounting' import type { Token } from '../../models/Token' @@ -25,7 +23,7 @@ export default function PeerBalances({ accounting, isLoadingUncashed, totalUncas {accounting?.map(({ peer, balance, received, sent, uncashedAmount, total }) => ( diff --git a/src/pages/files/SelectStamp.tsx b/src/pages/files/SelectStamp.tsx index 7fe369d2..16296beb 100644 --- a/src/pages/files/SelectStamp.tsx +++ b/src/pages/files/SelectStamp.tsx @@ -1,5 +1,5 @@ +import { Button, ListItemIcon, Menu, MenuItem, Typography } from '@material-ui/core' import React, { ReactElement } from 'react' -import { Button, ListItemIcon, Typography, Menu, MenuItem } from '@material-ui/core' import { EnrichedPostageBatch } from '../../providers/Stamps' interface Props { @@ -35,7 +35,7 @@ export default function SimpleMenu({ stamps, selectedStamp, setSelected }: Props selected={stamp.batchID === selectedStamp?.batchID} > {stamp.usageText} - {stamp.batchID.substr(0, 8)}[…] + {stamp.batchID.slice(0, 8)}[…] ))} diff --git a/src/pages/files/Upload.tsx b/src/pages/files/Upload.tsx index 6d26f4e7..c01c8252 100644 --- a/src/pages/files/Upload.tsx +++ b/src/pages/files/Upload.tsx @@ -6,6 +6,7 @@ import { DocumentationText } from '../../components/DocumentationText' 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 FileContext } from '../../providers/File' @@ -21,7 +22,6 @@ import { PostageStampSelector } from '../stamps/PostageStampSelector' import { AssetPreview } from './AssetPreview' import { StampPreview } from './StampPreview' import { UploadActionBar } from './UploadActionBar' -import { META_FILE_NAME, PREVIEW_FILE_NAME } from '../../constants' export function Upload(): ReactElement { const [step, setStep] = useState(0) @@ -83,9 +83,9 @@ export function Upload(): ReactElement { // The website is in some directory, remove it if (idx.commonPrefix) { const substrStart = idx.commonPrefix.length - indexDocument = idx.indexPath.substr(substrStart) + indexDocument = idx.indexPath.slice(substrStart) fls = fls.map(f => { - const path = (f.path as string).substr(substrStart) + const path = (f.path as string).slice(substrStart) return { ...f, path, webkitRelativePath: path, fullPath: path } }) diff --git a/src/pages/rpc/Confirmation.tsx b/src/pages/rpc/Confirmation.tsx new file mode 100644 index 00000000..ca9cec36 --- /dev/null +++ b/src/pages/rpc/Confirmation.tsx @@ -0,0 +1,20 @@ +import { Box, Typography } from '@material-ui/core' +import { ReactElement } from 'react' +import { HistoryHeader } from '../../components/HistoryHeader' + +export default function Confirmation(): ReactElement { + return ( + <> + Connect to the blockchain + + Your node's RPC endpoint is set up correctly! + + + + Lastly, you will need to top-up your node wallet. If you're not familiar with cryptocurrencies, you can + start with a bank card. + + + + ) +} diff --git a/src/pages/rpc/index.tsx b/src/pages/rpc/index.tsx new file mode 100644 index 00000000..4547ad8a --- /dev/null +++ b/src/pages/rpc/index.tsx @@ -0,0 +1,61 @@ +import { Box, Typography } from '@material-ui/core' +import { useSnackbar } from 'notistack' +import { ReactElement, useContext, useState } from 'react' +import { Check } from 'react-feather' +import { useNavigate } from 'react-router' +import { HistoryHeader } from '../../components/HistoryHeader' +import { SwarmButton } from '../../components/SwarmButton' +import { SwarmTextInput } from '../../components/SwarmTextInput' +import { Context } from '../../providers/TopUp' +import { ROUTES } from '../../routes' +import { Rpc } from '../../utils/rpc' + +export default function Index(): ReactElement { + const { jsonRpcProvider, setJsonRpcProvider } = useContext(Context) + + const [provider, setProvider] = useState(jsonRpcProvider) + + const { enqueueSnackbar } = useSnackbar() + const navigate = useNavigate() + + async function onSubmit() { + try { + await Rpc.eth_getBlockByNumber(provider) + enqueueSnackbar('Connected to RPC provider successfully.', { variant: 'success' }) + setJsonRpcProvider(provider) + navigate(ROUTES.CONFIRMATION) + } catch (error) { + enqueueSnackbar('Could not connect to RPC provider.', { variant: 'error' }) + } + } + + return ( + <> + Connect to the blockchain + + Set up RPC endpoint + + + + To connect to and retrieve data from the blockchain, you'll need to connect to a publicly-provided node + via the node's RPC endpoint. If you're not familiar with this, you may use{' '} + + https://getblock.io/ + + . + + + + setProvider(event.target.value)} + defaultValue={jsonRpcProvider} + /> + + + Connect + + + ) +} diff --git a/src/pages/top-up/BankCardTopUpIndex.tsx b/src/pages/top-up/BankCardTopUpIndex.tsx new file mode 100644 index 00000000..f02c63f1 --- /dev/null +++ b/src/pages/top-up/BankCardTopUpIndex.tsx @@ -0,0 +1,24 @@ +import { Typography } from '@material-ui/core' +import { ReactElement } from 'react' +import Index from '.' +import { ROUTES } from '../../routes' + +export function BankCardTopUpIndex(): ReactElement { + return ( + + It's recommended to buy an amount equivalent to 5 to 10 EUR maximum. If you're not familiar with + cryptocurrencies, you may use{' '} + + https://ramp.network/buy/ + + . + + } + next={ROUTES.TOP_UP_BANK_CARD_SWAP} + /> + ) +} diff --git a/src/pages/top-up/CryptoTopUpIndex.tsx b/src/pages/top-up/CryptoTopUpIndex.tsx new file mode 100644 index 00000000..07cfbcd5 --- /dev/null +++ b/src/pages/top-up/CryptoTopUpIndex.tsx @@ -0,0 +1,23 @@ +import { Typography } from '@material-ui/core' +import { ReactElement } from 'react' +import Index from '.' +import { ROUTES } from '../../routes' + +export function CryptoTopUpIndex(): ReactElement { + return ( + + For security reasons it is recommended to send maximum 5 to 10 xDAI. To get xDAI from DAI you may use{' '} + + https://bridge.xdaichain.com/ + + . + + } + next={ROUTES.TOP_UP_CRYPTO_SWAP} + /> + ) +} diff --git a/src/pages/top-up/Fund.tsx b/src/pages/top-up/Fund.tsx new file mode 100644 index 00000000..449c010b --- /dev/null +++ b/src/pages/top-up/Fund.tsx @@ -0,0 +1,102 @@ +import { Box, Typography } from '@material-ui/core' +import { useSnackbar } from 'notistack' +import { ReactElement, useContext, useState } from 'react' +import { ArrowDown, Check } from 'react-feather' +import ExpandableListItem from '../../components/ExpandableListItem' +import ExpandableListItemKey from '../../components/ExpandableListItemKey' +import { HistoryHeader } from '../../components/HistoryHeader' +import { SwarmButton } from '../../components/SwarmButton' +import { Context as BeeContext } from '../../providers/Bee' +import { Context as TopUpContext } from '../../providers/TopUp' +import { Rpc } from '../../utils/rpc' +import { TopUpProgressIndicator } from './TopUpProgressIndicator' + +const DUMMY_GAS_PRICE = '300000000000000' + +interface Props { + header: string +} + +export function Fund({ header }: Props): ReactElement { + const { xDaiBalance, xBzzBalance, jsonRpcProvider, wallet } = useContext(TopUpContext) + const { nodeAddresses, balance } = useContext(BeeContext) + + const { enqueueSnackbar } = useSnackbar() + + const [loading, setLoading] = useState(false) + + async function onFund() { + if (!nodeAddresses?.ethereum || !wallet) { + return + } + // eslint-disable-next-line no-console + console.log(xBzzBalance.toBigNumber.minus(DUMMY_GAS_PRICE).toString()) + setLoading(true) + try { + if (xBzzBalance.toBigNumber.gt(DUMMY_GAS_PRICE)) { + await Rpc.sendBzzTransaction( + wallet.getPrivateKeyString(), + nodeAddresses.ethereum, + xBzzBalance.toBigNumber.minus(DUMMY_GAS_PRICE).toString(), + jsonRpcProvider, + ) + } + + if (xDaiBalance.toBigNumber.gt(DUMMY_GAS_PRICE)) { + await Rpc.sendNativeTransaction( + wallet.getPrivateKeyString(), + nodeAddresses.ethereum, + xDaiBalance.toBigNumber.minus(DUMMY_GAS_PRICE).toString(), + jsonRpcProvider, + ) + } + enqueueSnackbar('Successfully funded node', { variant: 'success' }) + } catch (error) { + enqueueSnackbar(`Failed to fund node: ${error}`, { variant: 'error' }) + } finally { + setLoading(false) + } + } + + return ( + <> + {header} + + + + + Send funds to your Bee node + + + + Lastly, deposit all the funds from the funding wallet to your node wallet address. You can use the button + below to transfer all funds to your node. + + + + + + + + + + + + + + + + + + + + + + + + + Send all funds to your node + + + ) +} diff --git a/src/pages/top-up/Swap.tsx b/src/pages/top-up/Swap.tsx new file mode 100644 index 00000000..214c8509 --- /dev/null +++ b/src/pages/top-up/Swap.tsx @@ -0,0 +1,112 @@ +import { Box, Typography } from '@material-ui/core' +import { useSnackbar } from 'notistack' +import { ReactElement, useContext, useState } from 'react' +import { ArrowDown, Check } from 'react-feather' +import { useNavigate } from 'react-router' +import ExpandableListItem from '../../components/ExpandableListItem' +import ExpandableListItemActions from '../../components/ExpandableListItemActions' +import ExpandableListItemKey from '../../components/ExpandableListItemKey' +import { HistoryHeader } from '../../components/HistoryHeader' +import { SwarmButton } from '../../components/SwarmButton' +import { SwarmTextInput } from '../../components/SwarmTextInput' +import { Token } from '../../models/Token' +import { Context } from '../../providers/TopUp' +import { swap } from '../../utils/swap' +import { TopUpProgressIndicator } from './TopUpProgressIndicator' + +interface Props { + header: string + next: string +} + +export function Swap({ header, next }: Props): ReactElement { + const [loading, setLoading] = useState(false) + const [hasSwapped, setSwapped] = useState(false) + + const { wallet, xDaiBalance, xBzzBalance } = useContext(Context) + const navigate = useNavigate() + const { enqueueSnackbar } = useSnackbar() + + const xDaiAfterSwap = new Token(xDaiBalance.toBigNumber.dividedToIntegerBy(2).toString(), 18) + const xBzzAfterSwap = new Token(xDaiBalance.toBigNumber.dividedToIntegerBy(400).toString(), 16) + + async function onSwap() { + if (!wallet || hasSwapped) { + return + } + setLoading(true) + setSwapped(true) + try { + await swap(wallet.getPrivateKeyString(), xDaiBalance.toBigNumber.dividedToIntegerBy(2).toString(), '1') + enqueueSnackbar('Successfully swapped', { variant: 'success' }) + } catch (error) { + enqueueSnackbar(`Failed to swap: ${error}`, { variant: 'error' }) + } finally { + setLoading(false) + } + } + + return ( + <> + {header} + + + + + Swap some xDAI to BZZ + + + + You need to swap xDAI to BZZ in order to use Swarm. Make sure to keep at least 1 xDAI in order to pay for + transaction costs on the network. + + + + + Your current balance is {xDaiBalance.toSignificantDigits(4)} xDAI and {xBzzBalance.toSignificantDigits(4)}{' '} + BZZ. + + + + false} + /> + + + + + + + + + + + + + + + + Swap Now + + { + navigate(next) + }} + disabled={xBzzBalance.toBigNumber.eq(0) || loading} + > + Proceed + + + + ) +} diff --git a/src/pages/top-up/TopUpProgressIndicator.tsx b/src/pages/top-up/TopUpProgressIndicator.tsx new file mode 100644 index 00000000..fbf2a9f0 --- /dev/null +++ b/src/pages/top-up/TopUpProgressIndicator.tsx @@ -0,0 +1,10 @@ +import { ReactElement } from 'react' +import { ProgressIndicator } from '../../components/ProgressIndicator' + +interface Props { + index: number +} + +export function TopUpProgressIndicator({ index }: Props): ReactElement { + return +} diff --git a/src/pages/top-up/index.tsx b/src/pages/top-up/index.tsx new file mode 100644 index 00000000..31601945 --- /dev/null +++ b/src/pages/top-up/index.tsx @@ -0,0 +1,49 @@ +import { Box, Grid, Typography } from '@material-ui/core' +import { ReactElement, useContext } from 'react' +import { Check } from 'react-feather' +import { useNavigate } from 'react-router' +import ExpandableListItem from '../../components/ExpandableListItem' +import ExpandableListItemKey from '../../components/ExpandableListItemKey' +import { HistoryHeader } from '../../components/HistoryHeader' +import { SwarmButton } from '../../components/SwarmButton' +import { Context } from '../../providers/TopUp' +import { TopUpProgressIndicator } from './TopUpProgressIndicator' + +interface Props { + header: string + title: string + p: ReactElement + next: string +} + +export default function Index({ header, title, p, next }: Props): ReactElement { + const { wallet, xDaiBalance } = useContext(Context) + const navigate = useNavigate() + + const disabled = xDaiBalance.toBigNumber.eq(0) + + return ( + <> + {header} + + + + + {title} + + {p} + + + + + + + + navigate(next)} disabled={disabled}> + Proceed + + {disabled ? Please deposit xDAI to the address above in order to proceed. : null} + + + ) +} diff --git a/src/providers/TopUp.tsx b/src/providers/TopUp.tsx new file mode 100644 index 00000000..7f0138b3 --- /dev/null +++ b/src/providers/TopUp.tsx @@ -0,0 +1,93 @@ +import Wallet from 'ethereumjs-wallet' +import { createContext, ReactElement, useEffect, useState } from 'react' +import { Token } from '../models/Token' +import { generateWallet, getWalletFromPrivateKeyString } from '../utils/identity' +import { Rpc } from '../utils/rpc' + +const LocalStorageKeys = { + jsonRpcProvider: 'json-rpc-provider', + depositWallet: 'deposit-wallet', +} + +interface ContextInterface { + jsonRpcProvider: string + wallet: Wallet | null + xDaiBalance: Token + xBzzBalance: Token + setJsonRpcProvider: (jsonRpcProvider: string) => void +} + +const initialValues: ContextInterface = { + jsonRpcProvider: '', + xDaiBalance: new Token('0'), + xBzzBalance: new Token('0'), + wallet: null, + setJsonRpcProvider: () => {}, // eslint-disable-line +} + +export const Context = createContext(initialValues) +export const Consumer = Context.Consumer + +interface Props { + children: ReactElement +} + +export function Provider({ children }: Props): ReactElement { + const [jsonRpcProvider, setJsonRpcProvider] = useState( + localStorage.getItem('json-rpc-provider') || initialValues.jsonRpcProvider, + ) + const [xDaiBalance, setXDaiBalance] = useState(initialValues.xDaiBalance) + const [xBzzBalance, setXBzzBalance] = useState(initialValues.xBzzBalance) + const [wallet, setWallet] = useState(initialValues.wallet) + + useEffect(() => { + const existingWallet = localStorage.getItem(LocalStorageKeys.depositWallet) + const wallet: Wallet = existingWallet ? getWalletFromPrivateKeyString(existingWallet) : generateWallet() + localStorage.setItem(LocalStorageKeys.depositWallet, wallet.getPrivateKeyString()) + setWallet(wallet) + }, []) + + function setAndPersistJsonRpcProvider(jsonRpcProvider: string) { + localStorage.setItem(LocalStorageKeys.jsonRpcProvider, jsonRpcProvider) + setJsonRpcProvider(jsonRpcProvider) + } + + useEffect(() => { + if (!wallet) { + return + } + + function refreshBalances() { + if (!wallet) { + return + } + Rpc._eth_getBalance(wallet.getAddressString()).then(balance => { + setXDaiBalance(new Token(balance, 18)) + }) + Rpc._eth_getBalanceERC20(wallet.getAddressString()).then(balance => { + setXBzzBalance(new Token(balance, 16)) + }) + } + + refreshBalances() + const interval = setInterval(refreshBalances, 10_000) + + return () => { + clearInterval(interval) + } + }, [wallet]) + + return ( + + {children} + + ) +} diff --git a/src/routes.tsx b/src/routes.tsx index ad0aa5ea..2b3c133b 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -10,10 +10,16 @@ import { Share } from './pages/files/Share' import { Upload } from './pages/files/Upload' import { UploadLander } from './pages/files/UploadLander' import Info from './pages/info' +import Wallet from './pages/rpc' +import Confirmation from './pages/rpc/Confirmation' import Settings from './pages/settings' import Stamps from './pages/stamps' import { CreatePostageStampPage } from './pages/stamps/CreatePostageStampPage' import Status from './pages/status' +import { BankCardTopUpIndex } from './pages/top-up/BankCardTopUpIndex' +import { CryptoTopUpIndex } from './pages/top-up/CryptoTopUpIndex' +import { Fund } from './pages/top-up/Fund' +import { Swap } from './pages/top-up/Swap' export enum ROUTES { INFO = '/', @@ -31,6 +37,14 @@ export enum ROUTES { FEEDS_NEW = '/feeds/new', FEEDS_UPDATE = '/feeds/update/:hash', FEEDS_PAGE = '/feeds/:uuid', + WALLET = '/wallet', + CONFIRMATION = '/wallet/confirmation', + TOP_UP_CRYPTO = '/top-up/crypto', + TOP_UP_CRYPTO_SWAP = '/top-up/crypto/swap', + TOP_UP_CRYPTO_FUND = '/top-up/crypto/fund', + TOP_UP_BANK_CARD = '/top-up/bank-card', + TOP_UP_BANK_CARD_SWAP = '/top-up/bank-card/swap', + TOP_UP_BANK_CARD_FUND = '/top-up/bank-card/fund', } const BaseRouter = (): ReactElement => ( @@ -49,6 +63,20 @@ const BaseRouter = (): ReactElement => ( } /> } /> } /> + } /> + } /> + } /> + } + /> + } /> + } /> + } + /> + } /> ) diff --git a/src/utils/bzz-contract-interface.ts b/src/utils/bzz-contract-interface.ts new file mode 100644 index 00000000..2f227072 --- /dev/null +++ b/src/utils/bzz-contract-interface.ts @@ -0,0 +1,25 @@ +export const bzzContractInterface = [ + { + type: 'function', + stateMutability: 'nonpayable', + payable: false, + outputs: [ + { + type: 'bool', + name: '', + }, + ], + name: 'transfer', + inputs: [ + { + type: 'address', + name: '_to', + }, + { + type: 'uint256', + name: '_value', + }, + ], + constant: false, + }, +] diff --git a/src/utils/identity.ts b/src/utils/identity.ts index d83127a9..442a9b5d 100644 --- a/src/utils/identity.ts +++ b/src/utils/identity.ts @@ -79,9 +79,11 @@ function getWalletFromIdentity(identity: Identity, password?: string): Promise { - return type === 'PRIVATE_KEY' - ? Wallet.fromPrivateKey(Buffer.from(trimHexString(data), 'hex')) - : await Wallet.fromV3(data, password as string) + return type === 'PRIVATE_KEY' ? getWalletFromPrivateKeyString(data) : await Wallet.fromV3(data, password as string) +} + +export function getWalletFromPrivateKeyString(privateKey: string): Wallet { + return Wallet.fromPrivateKey(Buffer.from(trimHexString(privateKey), 'hex')) } export async function updateFeed( diff --git a/src/utils/rpc.ts b/src/utils/rpc.ts index 04e701a0..5937a283 100644 --- a/src/utils/rpc.ts +++ b/src/utils/rpc.ts @@ -1,14 +1,16 @@ +import { JsonRpcProvider } from '@ethersproject/providers' import { debounce } from '@material-ui/core' import axios from 'axios' -import { Contract, providers } from 'ethers' +import { BigNumber, Contract, providers, Wallet } from 'ethers' +import { bzzContractInterface } from './bzz-contract-interface' -const PROVIDER = 'https://gno.getblock.io/mainnet/?api_key=d7b92d96-9784-49a8-a800-b3edd1647fc7' +export const JSON_RPC_PROVIDER = 'https://gno.getblock.io/mainnet/?api_key=d7b92d96-9784-49a8-a800-b3edd1647fc7' async function eth_getBalance(address: string): Promise { if (!address.startsWith('0x')) { address = `0x${address}` } - const response = await axios(PROVIDER, { + const response = await axios(JSON_RPC_PROVIDER, { method: 'POST', headers: { 'content-type': 'application/json', @@ -24,6 +26,23 @@ async function eth_getBalance(address: string): Promise { return response.data.result } +async function eth_getBlockByNumber(provider = JSON_RPC_PROVIDER): Promise { + const response = await axios(provider, { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + data: { + jsonrpc: '2.0', + method: 'eth_getBlockByNumber', + params: ['latest', false], + id: 1, + }, + }) + + return response.data.result +} + const partialERC20tokenABI = [ { constant: true, @@ -45,7 +64,7 @@ const partialERC20tokenABI = [ }, ] -const provider = new providers.JsonRpcProvider(PROVIDER) +const provider = new providers.JsonRpcProvider(JSON_RPC_PROVIDER) async function eth_getBalanceERC20( address: string, @@ -60,7 +79,43 @@ async function eth_getBalanceERC20( return balance.toString() } +async function sendNativeTransaction( + privateKey: string, + address: string, + amount: string, + providerHost: string, +): Promise { + const provider = new JsonRpcProvider(providerHost) + const signer = new Wallet(privateKey, provider) + const gasPrice = await signer.getGasPrice() + + return signer.sendTransaction({ + to: address, + value: amount, + gasPrice, + }) +} + +async function sendBzzTransaction( + privateKey: string, + address: string, + amount: string, + providerHost: string, +): Promise { + const provider = new JsonRpcProvider(providerHost) + const signer = new Wallet(privateKey, provider) + const bzz = new Contract('0xdBF3Ea6F5beE45c02255B2c26a16F300502F68da', bzzContractInterface, signer) + const gasPrice = await signer.getGasPrice() + + return bzz.transfer(address, BigNumber.from(amount), { gasPrice }) +} + export const Rpc = { + sendNativeTransaction, + sendBzzTransaction, + _eth_getBalance: eth_getBalance, + _eth_getBalanceERC20: eth_getBalanceERC20, eth_getBalance: debounce(eth_getBalance, 1_000), eth_getBalanceERC20: debounce(eth_getBalanceERC20, 1_000), + eth_getBlockByNumber, } diff --git a/src/utils/swap.ts b/src/utils/swap.ts new file mode 100644 index 00000000..409ab295 --- /dev/null +++ b/src/utils/swap.ts @@ -0,0 +1,64 @@ +import { Contract, ethers, providers } from 'ethers' +import { JSON_RPC_PROVIDER } from './rpc' + +const contractInterface = [ + { + inputs: [ + { + internalType: 'uint256', + name: 'amountOutMin', + type: 'uint256', + }, + { + internalType: 'address[]', + name: 'path', + type: 'address[]', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapExactETHForTokens', + outputs: [ + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + stateMutability: 'payable', + type: 'function', + }, +] + +export async function swap(privateKey: string, xdai: string, minimumBzz: string): Promise { + const contracts = { + UniswapV2Factory: '0xA818b4F111Ccac7AA31D0BCc0806d64F2E0737D7', + UniswapV2Router02: '0x1C232F01118CB8B424793ae03F870aa7D0ac7f77', + } + const provider = new providers.JsonRpcProvider(JSON_RPC_PROVIDER) + const signer = new ethers.Wallet(privateKey, provider) + const gasLimit = 1000000 + const contract = new Contract(contracts.UniswapV2Router02, contractInterface, signer) + const WRAPPED_XDAI_CONTRACT = '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d' + const BZZ_ON_XDAI_CONTRACT = '0xdbf3ea6f5bee45c02255b2c26a16f300502f68da' + const response = await contract.swapExactETHForTokens( + minimumBzz, + [WRAPPED_XDAI_CONTRACT, BZZ_ON_XDAI_CONTRACT], + await signer.getAddress(), + Date.now(), + { + value: xdai, + gasLimit, + }, + ) + + return response +} From 28dc633d99f56b82374b5c9a3290e4c381e2113a Mon Sep 17 00:00:00 2001 From: Cafe137 Date: Tue, 3 May 2022 22:18:07 +0200 Subject: [PATCH 02/15] chore: remove console.log --- src/pages/top-up/Fund.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/top-up/Fund.tsx b/src/pages/top-up/Fund.tsx index 449c010b..25f46601 100644 --- a/src/pages/top-up/Fund.tsx +++ b/src/pages/top-up/Fund.tsx @@ -29,8 +29,6 @@ export function Fund({ header }: Props): ReactElement { if (!nodeAddresses?.ethereum || !wallet) { return } - // eslint-disable-next-line no-console - console.log(xBzzBalance.toBigNumber.minus(DUMMY_GAS_PRICE).toString()) setLoading(true) try { if (xBzzBalance.toBigNumber.gt(DUMMY_GAS_PRICE)) { From 8d63711641c8500bbe068b37f61a1b7f362a0638 Mon Sep 17 00:00:00 2001 From: Cafe137 Date: Tue, 3 May 2022 22:21:41 +0200 Subject: [PATCH 03/15] build: add pseudo-missing dependency --- package-lock.json | 103 +++++++++++++++++++++++++++++++++++++++++++--- package.json | 1 + 2 files changed, 98 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 40daa7c6..15446f3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@ethersphere/bee-js": "^3.3.4", "@ethersphere/manifest-js": "1.1.0", "@ethersphere/swarm-cid": "^0.1.0", + "@ethersproject/providers": "^5.6.5", "@material-ui/core": "4.12.3", "@material-ui/icons": "4.11.2", "@material-ui/lab": "4.0.0-alpha.57", @@ -2686,9 +2687,9 @@ } }, "node_modules/@ethersproject/providers": { - "version": "5.6.4", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.6.4.tgz", - "integrity": "sha512-WAdknnaZ52hpHV3qPiJmKx401BLpup47h36Axxgre9zT+doa/4GC/Ne48ICPxTm0BqndpToHjpLP1ZnaxyE+vw==", + "version": "5.6.5", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.6.5.tgz", + "integrity": "sha512-TRS+c2Ud+cMpWodmGAc9xbnYRPWzRNYt2zkCSnj58nJoamBQ6x4cUbBeo0lTC3y+6RDVIBeJv18OqsDbSktLVg==", "funding": [ { "type": "individual", @@ -11031,6 +11032,62 @@ "@ethersproject/wordlists": "5.6.0" } }, + "node_modules/ethers/node_modules/@ethersproject/providers": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.6.4.tgz", + "integrity": "sha512-WAdknnaZ52hpHV3qPiJmKx401BLpup47h36Axxgre9zT+doa/4GC/Ne48ICPxTm0BqndpToHjpLP1ZnaxyE+vw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-provider": "^5.6.0", + "@ethersproject/abstract-signer": "^5.6.0", + "@ethersproject/address": "^5.6.0", + "@ethersproject/basex": "^5.6.0", + "@ethersproject/bignumber": "^5.6.0", + "@ethersproject/bytes": "^5.6.0", + "@ethersproject/constants": "^5.6.0", + "@ethersproject/hash": "^5.6.0", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/networks": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/random": "^5.6.0", + "@ethersproject/rlp": "^5.6.0", + "@ethersproject/sha2": "^5.6.0", + "@ethersproject/strings": "^5.6.0", + "@ethersproject/transactions": "^5.6.0", + "@ethersproject/web": "^5.6.0", + "bech32": "1.1.4", + "ws": "7.4.6" + } + }, + "node_modules/ethers/node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -31127,9 +31184,9 @@ } }, "@ethersproject/providers": { - "version": "5.6.4", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.6.4.tgz", - "integrity": "sha512-WAdknnaZ52hpHV3qPiJmKx401BLpup47h36Axxgre9zT+doa/4GC/Ne48ICPxTm0BqndpToHjpLP1ZnaxyE+vw==", + "version": "5.6.5", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.6.5.tgz", + "integrity": "sha512-TRS+c2Ud+cMpWodmGAc9xbnYRPWzRNYt2zkCSnj58nJoamBQ6x4cUbBeo0lTC3y+6RDVIBeJv18OqsDbSktLVg==", "requires": { "@ethersproject/abstract-provider": "^5.6.0", "@ethersproject/abstract-signer": "^5.6.0", @@ -37665,6 +37722,40 @@ "@ethersproject/wallet": "5.6.0", "@ethersproject/web": "5.6.0", "@ethersproject/wordlists": "5.6.0" + }, + "dependencies": { + "@ethersproject/providers": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.6.4.tgz", + "integrity": "sha512-WAdknnaZ52hpHV3qPiJmKx401BLpup47h36Axxgre9zT+doa/4GC/Ne48ICPxTm0BqndpToHjpLP1ZnaxyE+vw==", + "requires": { + "@ethersproject/abstract-provider": "^5.6.0", + "@ethersproject/abstract-signer": "^5.6.0", + "@ethersproject/address": "^5.6.0", + "@ethersproject/basex": "^5.6.0", + "@ethersproject/bignumber": "^5.6.0", + "@ethersproject/bytes": "^5.6.0", + "@ethersproject/constants": "^5.6.0", + "@ethersproject/hash": "^5.6.0", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/networks": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/random": "^5.6.0", + "@ethersproject/rlp": "^5.6.0", + "@ethersproject/sha2": "^5.6.0", + "@ethersproject/strings": "^5.6.0", + "@ethersproject/transactions": "^5.6.0", + "@ethersproject/web": "^5.6.0", + "bech32": "1.1.4", + "ws": "7.4.6" + } + }, + "ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "requires": {} + } } }, "event-target-shim": { diff --git a/package.json b/package.json index b162b0a7..9275cb29 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@ethersphere/bee-js": "^3.3.4", "@ethersphere/manifest-js": "1.1.0", "@ethersphere/swarm-cid": "^0.1.0", + "@ethersproject/providers": "^5.6.5", "@material-ui/core": "4.12.3", "@material-ui/icons": "4.11.2", "@material-ui/lab": "4.0.0-alpha.57", From 072e70fba881960f97af62036174dcb407bbbb89 Mon Sep 17 00:00:00 2001 From: Cafe137 Date: Thu, 12 May 2022 14:20:30 +0200 Subject: [PATCH 04/15] feat: add missing top up components --- src/components/SideBar.tsx | 25 ++------------- src/pages/rpc/Confirmation.tsx | 58 ++++++++++++++++++++++++++++------ src/pages/top-up/Fund.tsx | 4 +++ src/providers/TopUp.tsx | 3 ++ src/utils/desktop.ts | 6 ++++ 5 files changed, 63 insertions(+), 33 deletions(-) diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx index e13ca677..c6083a4d 100644 --- a/src/components/SideBar.tsx +++ b/src/components/SideBar.tsx @@ -2,18 +2,7 @@ import { Divider, Drawer, Grid, Link as MUILink, List } from '@material-ui/core' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import { OpenInNewSharp } from '@material-ui/icons' import type { ReactElement } from 'react' -import { - Battery, - BatteryCharging, - Bookmark, - BookOpen, - Briefcase, - DollarSign, - FileText, - Home, - Layers, - Settings, -} from 'react-feather' +import { Bookmark, BookOpen, Briefcase, DollarSign, FileText, Home, Layers, Settings } from 'react-feather' import { Link } from 'react-router-dom' import Logo from '../assets/logo.svg' import { config } from '../config' @@ -53,20 +42,10 @@ const navBarItems = [ icon: Settings, }, { - label: 'Wallet', + label: 'Account', path: ROUTES.WALLET, icon: Briefcase, }, - { - label: 'Crypto', - path: ROUTES.TOP_UP_CRYPTO, - icon: Battery, - }, - { - label: 'Bank Card', - path: ROUTES.TOP_UP_BANK_CARD, - icon: BatteryCharging, - }, ] const drawerWidth = 300 diff --git a/src/pages/rpc/Confirmation.tsx b/src/pages/rpc/Confirmation.tsx index ca9cec36..3cd5ac6c 100644 --- a/src/pages/rpc/Confirmation.tsx +++ b/src/pages/rpc/Confirmation.tsx @@ -1,20 +1,58 @@ -import { Box, Typography } from '@material-ui/core' +import { Box, createStyles, Grid, makeStyles, Typography } from '@material-ui/core' import { ReactElement } from 'react' +import { Battery, BatteryCharging, Check } from 'react-feather' +import { useNavigate } from 'react-router' +import ExpandableListItemActions from '../../components/ExpandableListItemActions' import { HistoryHeader } from '../../components/HistoryHeader' +import { SwarmButton } from '../../components/SwarmButton' +import { ROUTES } from '../../routes' + +const useStyles = makeStyles(() => + createStyles({ + checkWrapper: { + background: 'rgba(0, 230, 118, 0.25)', + borderRadius: 99999, + width: '180px', + height: '180px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + }), +) export default function Confirmation(): ReactElement { + const navigate = useNavigate() + + const styles = useStyles() + return ( <> Connect to the blockchain - - Your node's RPC endpoint is set up correctly! - - - - Lastly, you will need to top-up your node wallet. If you're not familiar with cryptocurrencies, you can - start with a bank card. - - + + +
+ +
+
+ + Your node's RPC endpoint is set up correctly! + + + Lastly, you will need to top-up your node wallet. + + If you're not familiar with cryptocurrencies, you can start with a bank card. + + + + navigate(ROUTES.TOP_UP_BANK_CARD)}> + Get started with bank card + + navigate(ROUTES.TOP_UP_BANK_CARD)}> + Use DAI + + +
) } diff --git a/src/pages/top-up/Fund.tsx b/src/pages/top-up/Fund.tsx index 25f46601..c852a253 100644 --- a/src/pages/top-up/Fund.tsx +++ b/src/pages/top-up/Fund.tsx @@ -2,12 +2,14 @@ import { Box, Typography } from '@material-ui/core' import { useSnackbar } from 'notistack' import { ReactElement, useContext, useState } from 'react' import { ArrowDown, Check } from 'react-feather' +import { useNavigate } from 'react-router' import ExpandableListItem from '../../components/ExpandableListItem' import ExpandableListItemKey from '../../components/ExpandableListItemKey' import { HistoryHeader } from '../../components/HistoryHeader' import { SwarmButton } from '../../components/SwarmButton' import { Context as BeeContext } from '../../providers/Bee' import { Context as TopUpContext } from '../../providers/TopUp' +import { ROUTES } from '../../routes' import { Rpc } from '../../utils/rpc' import { TopUpProgressIndicator } from './TopUpProgressIndicator' @@ -22,6 +24,7 @@ export function Fund({ header }: Props): ReactElement { const { nodeAddresses, balance } = useContext(BeeContext) const { enqueueSnackbar } = useSnackbar() + const navigate = useNavigate() const [loading, setLoading] = useState(false) @@ -48,6 +51,7 @@ export function Fund({ header }: Props): ReactElement { jsonRpcProvider, ) } + navigate(ROUTES.INFO) enqueueSnackbar('Successfully funded node', { variant: 'success' }) } catch (error) { enqueueSnackbar(`Failed to fund node: ${error}`, { variant: 'error' }) diff --git a/src/providers/TopUp.tsx b/src/providers/TopUp.tsx index 7f0138b3..2a10ab29 100644 --- a/src/providers/TopUp.tsx +++ b/src/providers/TopUp.tsx @@ -1,6 +1,7 @@ import Wallet from 'ethereumjs-wallet' import { createContext, ReactElement, useEffect, useState } from 'react' import { Token } from '../models/Token' +import { setJsonRpcInDesktop } from '../utils/desktop' import { generateWallet, getWalletFromPrivateKeyString } from '../utils/identity' import { Rpc } from '../utils/rpc' @@ -50,6 +51,8 @@ export function Provider({ children }: Props): ReactElement { function setAndPersistJsonRpcProvider(jsonRpcProvider: string) { localStorage.setItem(LocalStorageKeys.jsonRpcProvider, jsonRpcProvider) setJsonRpcProvider(jsonRpcProvider) + // eslint-disable-next-line no-console + setJsonRpcInDesktop(jsonRpcProvider).catch(console.error) } useEffect(() => { diff --git a/src/utils/desktop.ts b/src/utils/desktop.ts index 388cb75b..722169be 100644 --- a/src/utils/desktop.ts +++ b/src/utils/desktop.ts @@ -26,6 +26,12 @@ export async function upgradeToLightNode(rpcProvider: string): Promise { }) } +export async function setJsonRpcInDesktop(value: string): Promise { + await updateDesktopConfiguration({ + 'swap-endpoint': value, + }) +} + async function updateDesktopConfiguration(values: Record): Promise { await postJson(`http://${getDesktopHost()}/config`, values) } From 40d3a1601bcfbe07ddf6ddfd5c61453e0dbd0b3c Mon Sep 17 00:00:00 2001 From: Cafe137 Date: Thu, 12 May 2022 14:21:35 +0200 Subject: [PATCH 05/15] fix: crypto route --- src/pages/rpc/Confirmation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/rpc/Confirmation.tsx b/src/pages/rpc/Confirmation.tsx index 3cd5ac6c..476f03aa 100644 --- a/src/pages/rpc/Confirmation.tsx +++ b/src/pages/rpc/Confirmation.tsx @@ -48,7 +48,7 @@ export default function Confirmation(): ReactElement { navigate(ROUTES.TOP_UP_BANK_CARD)}> Get started with bank card - navigate(ROUTES.TOP_UP_BANK_CARD)}> + navigate(ROUTES.TOP_UP_CRYPTO)}> Use DAI From eb540a7f67ca9ee6512fe67f579831dab55df894 Mon Sep 17 00:00:00 2001 From: Cafe137 Date: Tue, 17 May 2022 16:12:53 +0200 Subject: [PATCH 06/15] feat(wip): add gift wallet logic --- src/components/SideBar.tsx | 7 +- src/components/SwarmDivider.tsx | 17 ++++ src/models/BzzToken.ts | 8 ++ src/models/DaiToken.ts | 8 ++ src/models/Token.ts | 2 +- src/pages/gift-code/index.tsx | 92 +++++++++++++++++++++ src/pages/rpc/Confirmation.tsx | 5 +- src/pages/top-up/Fund.tsx | 39 ++++----- src/pages/top-up/GiftCardFund.tsx | 105 ++++++++++++++++++++++++ src/pages/top-up/GiftCardTopUpIndex.tsx | 71 ++++++++++++++++ src/pages/top-up/Swap.tsx | 40 ++++++--- src/pages/top-up/index.tsx | 15 +++- src/providers/Bee.tsx | 10 ++- src/providers/TopUp.tsx | 55 +++++++------ src/routes.tsx | 9 ++ src/utils/desktop.ts | 4 + src/utils/wallet.ts | 52 ++++++++++++ 17 files changed, 464 insertions(+), 75 deletions(-) create mode 100644 src/components/SwarmDivider.tsx create mode 100644 src/models/BzzToken.ts create mode 100644 src/models/DaiToken.ts create mode 100644 src/pages/gift-code/index.tsx create mode 100644 src/pages/top-up/GiftCardFund.tsx create mode 100644 src/pages/top-up/GiftCardTopUpIndex.tsx create mode 100644 src/utils/wallet.ts diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx index c6083a4d..0348828a 100644 --- a/src/components/SideBar.tsx +++ b/src/components/SideBar.tsx @@ -2,7 +2,7 @@ import { Divider, Drawer, Grid, Link as MUILink, List } from '@material-ui/core' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import { OpenInNewSharp } from '@material-ui/icons' import type { ReactElement } from 'react' -import { Bookmark, BookOpen, Briefcase, DollarSign, FileText, Home, Layers, Settings } from 'react-feather' +import { Bookmark, BookOpen, Briefcase, DollarSign, FileText, Gift, Home, Layers, Settings } from 'react-feather' import { Link } from 'react-router-dom' import Logo from '../assets/logo.svg' import { config } from '../config' @@ -46,6 +46,11 @@ const navBarItems = [ path: ROUTES.WALLET, icon: Briefcase, }, + { + label: 'Gift Wallets', + path: ROUTES.GIFT_CODES, + icon: Gift, + }, ] const drawerWidth = 300 diff --git a/src/components/SwarmDivider.tsx b/src/components/SwarmDivider.tsx new file mode 100644 index 00000000..5875a8d8 --- /dev/null +++ b/src/components/SwarmDivider.tsx @@ -0,0 +1,17 @@ +import { Box, Divider } from '@material-ui/core' +import { ReactElement } from 'react' + +interface Props { + my?: number + mt?: number + mb?: number + color?: string +} + +export function SwarmDivider({ my, mt, mb, color = '#cbcbcb' }: Props): ReactElement { + return ( + + + + ) +} diff --git a/src/models/BzzToken.ts b/src/models/BzzToken.ts new file mode 100644 index 00000000..f6a41d6b --- /dev/null +++ b/src/models/BzzToken.ts @@ -0,0 +1,8 @@ +import { BigNumber } from 'bignumber.js' +import { Token } from './Token' + +export class BzzToken extends Token { + constructor(amount: BigNumber | string | BigInt) { + super(amount, 16) + } +} diff --git a/src/models/DaiToken.ts b/src/models/DaiToken.ts new file mode 100644 index 00000000..1a49a4df --- /dev/null +++ b/src/models/DaiToken.ts @@ -0,0 +1,8 @@ +import { BigNumber } from 'bignumber.js' +import { Token } from './Token' + +export class DaiToken extends Token { + constructor(amount: BigNumber | string | BigInt) { + super(amount, 18) + } +} diff --git a/src/models/Token.ts b/src/models/Token.ts index 393bd83f..2f127b3a 100644 --- a/src/models/Token.ts +++ b/src/models/Token.ts @@ -59,7 +59,7 @@ export class Token { } toSignificantDigits(digits = 4): string { - const asString = this.toDecimal.toFixed(16) + const asString = this.toDecimal.toFixed(this.decimals) let indexOfSignificantDigit = -1 let reachedDecimalPoint = false diff --git a/src/pages/gift-code/index.tsx b/src/pages/gift-code/index.tsx new file mode 100644 index 00000000..a65b2399 --- /dev/null +++ b/src/pages/gift-code/index.tsx @@ -0,0 +1,92 @@ +import { Box, Typography } from '@material-ui/core' +import { useSnackbar } from 'notistack' +import { ReactElement, useContext, useEffect, useState } from 'react' +import { Check, X } from 'react-feather' +import { useNavigate } from 'react-router' +import ExpandableListItem from '../../components/ExpandableListItem' +import ExpandableListItemActions from '../../components/ExpandableListItemActions' +import ExpandableListItemKey from '../../components/ExpandableListItemKey' +import { HistoryHeader } from '../../components/HistoryHeader' +import { SwarmButton } from '../../components/SwarmButton' +import { Context as BeeContext } from '../../providers/Bee' +import { Context as TopUpContext } from '../../providers/TopUp' +import { createGiftWallet } from '../../utils/desktop' +import { generateWallet } from '../../utils/identity' +import { ResolvedWallet } from '../../utils/wallet' + +export default function Index(): ReactElement { + const { giftWallets, addGiftWallet } = useContext(TopUpContext) + const { balance } = useContext(BeeContext) + + const [loading, setLoading] = useState(false) + const [balances, setBalances] = useState([]) + + useEffect(() => { + async function mapGiftWallets() { + const results = [] + for (const giftWallet of giftWallets) { + results.push(await ResolvedWallet.make(giftWallet)) + } + setBalances(results) + } + + mapGiftWallets() + }, [giftWallets]) + + const { enqueueSnackbar } = useSnackbar() + const navigate = useNavigate() + + async function onCreate() { + enqueueSnackbar('Sending funds to gift wallet...') + setLoading(true) + try { + const wallet = generateWallet() + addGiftWallet(wallet) + await createGiftWallet(wallet.getAddressString()) + enqueueSnackbar('Succesfully funded gift wallet', { variant: 'success' }) + } catch (error) { + enqueueSnackbar(`Failed to fund gift wallet: ${error}`, { variant: 'error' }) + } finally { + setLoading(false) + } + } + + function onCancel() { + navigate(-1) + } + + return ( + <> + Invite to Swarm... + + + Generate and share a gift wallet that anyone can use to set-up their light node with Swarm Desktop. This will + use 1 XDAI and 5 BZZ from your node wallet. + + + + + + + + + + {balances.map((x, i) => ( + + + + + + ))} + + + + Generate gift wallet + + + Cancel + + + + ) +} diff --git a/src/pages/rpc/Confirmation.tsx b/src/pages/rpc/Confirmation.tsx index 476f03aa..33ddd067 100644 --- a/src/pages/rpc/Confirmation.tsx +++ b/src/pages/rpc/Confirmation.tsx @@ -1,6 +1,6 @@ import { Box, createStyles, Grid, makeStyles, Typography } from '@material-ui/core' import { ReactElement } from 'react' -import { Battery, BatteryCharging, Check } from 'react-feather' +import { Battery, BatteryCharging, Check, Gift } from 'react-feather' import { useNavigate } from 'react-router' import ExpandableListItemActions from '../../components/ExpandableListItemActions' import { HistoryHeader } from '../../components/HistoryHeader' @@ -51,6 +51,9 @@ export default function Confirmation(): ReactElement { navigate(ROUTES.TOP_UP_CRYPTO)}> Use DAI + navigate(ROUTES.TOP_UP_GIFT_CODE)}> + Use a gift code + diff --git a/src/pages/top-up/Fund.tsx b/src/pages/top-up/Fund.tsx index c852a253..8a6b6be4 100644 --- a/src/pages/top-up/Fund.tsx +++ b/src/pages/top-up/Fund.tsx @@ -6,21 +6,21 @@ import { useNavigate } from 'react-router' import ExpandableListItem from '../../components/ExpandableListItem' import ExpandableListItemKey from '../../components/ExpandableListItemKey' import { HistoryHeader } from '../../components/HistoryHeader' +import { Loading } from '../../components/Loading' import { SwarmButton } from '../../components/SwarmButton' +import { SwarmDivider } from '../../components/SwarmDivider' import { Context as BeeContext } from '../../providers/Bee' import { Context as TopUpContext } from '../../providers/TopUp' import { ROUTES } from '../../routes' -import { Rpc } from '../../utils/rpc' +import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop' import { TopUpProgressIndicator } from './TopUpProgressIndicator' -const DUMMY_GAS_PRICE = '300000000000000' - interface Props { header: string } export function Fund({ header }: Props): ReactElement { - const { xDaiBalance, xBzzBalance, jsonRpcProvider, wallet } = useContext(TopUpContext) + const { wallet, jsonRpcProvider } = useContext(TopUpContext) const { nodeAddresses, balance } = useContext(BeeContext) const { enqueueSnackbar } = useSnackbar() @@ -28,29 +28,19 @@ export function Fund({ header }: Props): ReactElement { const [loading, setLoading] = useState(false) + if (!wallet) { + return + } + async function onFund() { if (!nodeAddresses?.ethereum || !wallet) { return } setLoading(true) try { - if (xBzzBalance.toBigNumber.gt(DUMMY_GAS_PRICE)) { - await Rpc.sendBzzTransaction( - wallet.getPrivateKeyString(), - nodeAddresses.ethereum, - xBzzBalance.toBigNumber.minus(DUMMY_GAS_PRICE).toString(), - jsonRpcProvider, - ) - } - - if (xDaiBalance.toBigNumber.gt(DUMMY_GAS_PRICE)) { - await Rpc.sendNativeTransaction( - wallet.getPrivateKeyString(), - nodeAddresses.ethereum, - xDaiBalance.toBigNumber.minus(DUMMY_GAS_PRICE).toString(), - jsonRpcProvider, - ) - } + await wallet.transfer(nodeAddresses.ethereum, jsonRpcProvider) + await upgradeToLightNode(jsonRpcProvider) + await restartBeeNode() navigate(ROUTES.INFO) enqueueSnackbar('Successfully funded node', { variant: 'success' }) } catch (error) { @@ -75,14 +65,15 @@ export function Fund({ header }: Props): ReactElement { below to transfer all funds to your node. + - + - + - + diff --git a/src/pages/top-up/GiftCardFund.tsx b/src/pages/top-up/GiftCardFund.tsx new file mode 100644 index 00000000..4c337adc --- /dev/null +++ b/src/pages/top-up/GiftCardFund.tsx @@ -0,0 +1,105 @@ +import { Box, Typography } from '@material-ui/core' +import { useSnackbar } from 'notistack' +import { ReactElement, useContext, useEffect, useState } from 'react' +import { ArrowDown, Check } from 'react-feather' +import { useNavigate, useParams } from 'react-router' +import ExpandableListItem from '../../components/ExpandableListItem' +import ExpandableListItemKey from '../../components/ExpandableListItemKey' +import { HistoryHeader } from '../../components/HistoryHeader' +import { Loading } from '../../components/Loading' +import { ProgressIndicator } from '../../components/ProgressIndicator' +import { SwarmButton } from '../../components/SwarmButton' +import { SwarmDivider } from '../../components/SwarmDivider' +import { Context as BeeContext } from '../../providers/Bee' +import { Context as TopUpContext } from '../../providers/TopUp' +import { ROUTES } from '../../routes' +import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop' +import { ResolvedWallet } from '../../utils/wallet' + +export function GiftCardFund(): ReactElement { + const { nodeAddresses, balance } = useContext(BeeContext) + const { jsonRpcProvider } = useContext(TopUpContext) + + const [loading, setLoading] = useState(false) + const [wallet, setWallet] = useState(null) + + const { privateKeyString } = useParams() + + const { enqueueSnackbar } = useSnackbar() + const navigate = useNavigate() + + useEffect(() => { + if (!privateKeyString) { + return + } + + ResolvedWallet.make(privateKeyString).then(setWallet) + }, [privateKeyString]) + + if (!wallet) { + return + } + + async function onFund() { + if (!wallet || !nodeAddresses) { + return + } + + setLoading(true) + + try { + await wallet.transfer(nodeAddresses.ethereum) + await upgradeToLightNode(jsonRpcProvider) + await restartBeeNode() + navigate(ROUTES.INFO) + enqueueSnackbar('Successfully funded node', { variant: 'success' }) + } catch (error) { + enqueueSnackbar(`Failed to fund node: ${error}`, { variant: 'error' }) + } finally { + setLoading(false) + } + } + + return ( + <> + Top-up with gift code + + + + + Send funds to your Bee node + + + + Deposit all the funds from the gift wallet to your node wallet address. You can use the button below to + transfer all funds to your node. + + + + + + + + + + + + + + + + + + + + + + + + + + Send all funds to your node + + + ) +} diff --git a/src/pages/top-up/GiftCardTopUpIndex.tsx b/src/pages/top-up/GiftCardTopUpIndex.tsx new file mode 100644 index 00000000..4ca4aab5 --- /dev/null +++ b/src/pages/top-up/GiftCardTopUpIndex.tsx @@ -0,0 +1,71 @@ +import { Box, Typography } from '@material-ui/core' +import { useSnackbar } from 'notistack' +import { ReactElement, useState } from 'react' +import { ArrowRight } from 'react-feather' +import { useNavigate } from 'react-router' +import { HistoryHeader } from '../../components/HistoryHeader' +import { ProgressIndicator } from '../../components/ProgressIndicator' +import { SwarmButton } from '../../components/SwarmButton' +import { SwarmDivider } from '../../components/SwarmDivider' +import { SwarmTextInput } from '../../components/SwarmTextInput' +import { BzzToken } from '../../models/BzzToken' +import { DaiToken } from '../../models/DaiToken' +import { ROUTES } from '../../routes' +import { getWalletFromPrivateKeyString } from '../../utils/identity' +import { Rpc } from '../../utils/rpc' + +export function GiftCardTopUpIndex(): ReactElement { + const [loading, setLoading] = useState(false) + const [giftCode, setGiftCode] = useState('') + + const { enqueueSnackbar } = useSnackbar() + const navigate = useNavigate() + + async function onProceed() { + setLoading(true) + try { + const wallet = getWalletFromPrivateKeyString(giftCode) + const dai = new DaiToken(await Rpc._eth_getBalance(wallet.getAddressString())) + const bzz = new BzzToken(await Rpc._eth_getBalanceERC20(wallet.getAddressString())) + + if (dai.toDecimal.lt(0.1) || bzz.toDecimal.lt(0.1)) { + throw Error('Gift wallet does not enough funds') + } + enqueueSnackbar('Successfully verified gift wallet') + navigate(ROUTES.TOP_UP_GIFT_CODE_FUND.replace(':privateKeyString', giftCode)) + } catch (error) { + enqueueSnackbar(`Gift wallet could not be verified: ${error}`, { variant: 'error' }) + } finally { + setLoading(false) + } + } + + return ( + <> + Top-up with gift code + + + + + Please paste your gift code below + + + A gift code is a unique key to a gift wallet that you can use to fund your node. Please don't share your + gift code as it can only be used once. + + + + { + setGiftCode(event.target.value) + }} + /> + + + Proceed + + + ) +} diff --git a/src/pages/top-up/Swap.tsx b/src/pages/top-up/Swap.tsx index 214c8509..c0cca6c8 100644 --- a/src/pages/top-up/Swap.tsx +++ b/src/pages/top-up/Swap.tsx @@ -7,9 +7,12 @@ 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 { SwarmDivider } from '../../components/SwarmDivider' import { SwarmTextInput } from '../../components/SwarmTextInput' -import { Token } from '../../models/Token' +import { BzzToken } from '../../models/BzzToken' +import { DaiToken } from '../../models/DaiToken' import { Context } from '../../providers/TopUp' import { swap } from '../../utils/swap' import { TopUpProgressIndicator } from './TopUpProgressIndicator' @@ -23,12 +26,18 @@ export function Swap({ header, next }: Props): ReactElement { const [loading, setLoading] = useState(false) const [hasSwapped, setSwapped] = useState(false) - const { wallet, xDaiBalance, xBzzBalance } = useContext(Context) + const { wallet } = useContext(Context) const navigate = useNavigate() const { enqueueSnackbar } = useSnackbar() - const xDaiAfterSwap = new Token(xDaiBalance.toBigNumber.dividedToIntegerBy(2).toString(), 18) - const xBzzAfterSwap = new Token(xDaiBalance.toBigNumber.dividedToIntegerBy(400).toString(), 16) + if (!wallet) { + return + } + + const daiToSwap = DaiToken.fromDecimal(wallet.dai.toDecimal.minus(1)) + + const daiAfterSwap = new DaiToken(wallet.dai.toBigNumber.minus(daiToSwap.toBigNumber)) + const bzzAfterSwap = BzzToken.fromDecimal(daiAfterSwap.toDecimal.dividedBy(2)) async function onSwap() { if (!wallet || hasSwapped) { @@ -37,7 +46,7 @@ export function Swap({ header, next }: Props): ReactElement { setLoading(true) setSwapped(true) try { - await swap(wallet.getPrivateKeyString(), xDaiBalance.toBigNumber.dividedToIntegerBy(2).toString(), '1') + await swap(wallet.privateKey, daiToSwap.toString, '0.1') enqueueSnackbar('Successfully swapped', { variant: 'success' }) } catch (error) { enqueueSnackbar(`Failed to swap: ${error}`, { variant: 'error' }) @@ -61,16 +70,16 @@ export function Swap({ header, next }: Props): ReactElement { transaction costs on the network. + - Your current balance is {xDaiBalance.toSignificantDigits(4)} xDAI and {xBzzBalance.toSignificantDigits(4)}{' '} - BZZ. + Your current balance is {wallet.dai.toSignificantDigits(4)} xDAI and {wallet.bzz.toSignificantDigits(4)} BZZ. false} /> @@ -79,22 +88,27 @@ export function Swap({ header, next }: Props): ReactElement { - + - + Swap Now { navigate(next) }} - disabled={xBzzBalance.toBigNumber.eq(0) || loading} + disabled={loading || wallet.bzz.toDecimal.lte(0.1)} > Proceed diff --git a/src/pages/top-up/index.tsx b/src/pages/top-up/index.tsx index 31601945..5e85ab35 100644 --- a/src/pages/top-up/index.tsx +++ b/src/pages/top-up/index.tsx @@ -5,7 +5,9 @@ import { useNavigate } from 'react-router' import ExpandableListItem from '../../components/ExpandableListItem' import ExpandableListItemKey from '../../components/ExpandableListItemKey' import { HistoryHeader } from '../../components/HistoryHeader' +import { Loading } from '../../components/Loading' import { SwarmButton } from '../../components/SwarmButton' +import { SwarmDivider } from '../../components/SwarmDivider' import { Context } from '../../providers/TopUp' import { TopUpProgressIndicator } from './TopUpProgressIndicator' @@ -17,10 +19,14 @@ interface Props { } export default function Index({ header, title, p, next }: Props): ReactElement { - const { wallet, xDaiBalance } = useContext(Context) + const { wallet } = useContext(Context) const navigate = useNavigate() - const disabled = xDaiBalance.toBigNumber.eq(0) + if (!wallet) { + return + } + + const disabled = wallet.dai.toDecimal.lte(1) return ( <> @@ -32,11 +38,12 @@ export default function Index({ header, title, p, next }: Props): ReactElement { {title} {p} + - + - + navigate(next)} disabled={disabled}> diff --git a/src/providers/Bee.tsx b/src/providers/Bee.tsx index b4b692df..d87d247d 100644 --- a/src/providers/Bee.tsx +++ b/src/providers/Bee.tsx @@ -13,6 +13,8 @@ import { createContext, ReactChild, ReactElement, useContext, useEffect, useStat import semver from 'semver' import { engines } from '../../package.json' import { useLatestBeeRelease } from '../hooks/apiHooks' +import { BzzToken } from '../models/BzzToken' +import { DaiToken } from '../models/DaiToken' import { Token } from '../models/Token' import type { Balance, ChequebookBalance, Settlements } from '../types' import { Rpc } from '../utils/rpc' @@ -85,8 +87,8 @@ const initialValues: ContextInterface = { chequebook: { isEnabled: false, checkState: CheckState.ERROR }, }, balance: { - bzz: new Token('0', 16), - xdai: new Token('0', 18), + bzz: new BzzToken('0'), + xdai: new DaiToken('0'), }, latestPublishedVersion: undefined, latestUserVersion: undefined, @@ -252,11 +254,11 @@ export function Provider({ children }: Props): ReactElement { const bzz = Rpc.eth_getBalanceERC20(nodeAddresses.ethereum) if (xdai?.then) { - xdai.then(balance => setXdai(new Token(balance, 18))) + xdai.then(balance => setXdai(new DaiToken(balance))) } if (bzz?.then) { - bzz.then(balance => setBzz(new Token(balance, 16))) + bzz.then(balance => setBzz(new BzzToken(balance))) } } }, [nodeAddresses]) diff --git a/src/providers/TopUp.tsx b/src/providers/TopUp.tsx index 2a10ab29..6476dd88 100644 --- a/src/providers/TopUp.tsx +++ b/src/providers/TopUp.tsx @@ -1,29 +1,30 @@ import Wallet from 'ethereumjs-wallet' import { createContext, ReactElement, useEffect, useState } from 'react' -import { Token } from '../models/Token' import { setJsonRpcInDesktop } from '../utils/desktop' import { generateWallet, getWalletFromPrivateKeyString } from '../utils/identity' -import { Rpc } from '../utils/rpc' +import { ResolvedWallet } from '../utils/wallet' const LocalStorageKeys = { jsonRpcProvider: 'json-rpc-provider', depositWallet: 'deposit-wallet', + giftWallets: 'gift-wallets', + invitation: 'invitation', } interface ContextInterface { jsonRpcProvider: string - wallet: Wallet | null - xDaiBalance: Token - xBzzBalance: Token + wallet: ResolvedWallet | null + giftWallets: Wallet[] setJsonRpcProvider: (jsonRpcProvider: string) => void + addGiftWallet: (wallet: Wallet) => void } const initialValues: ContextInterface = { jsonRpcProvider: '', - xDaiBalance: new Token('0'), - xBzzBalance: new Token('0'), wallet: null, + giftWallets: [], setJsonRpcProvider: () => {}, // eslint-disable-line + addGiftWallet: () => {}, // eslint-disable-line } export const Context = createContext(initialValues) @@ -37,15 +38,22 @@ export function Provider({ children }: Props): ReactElement { const [jsonRpcProvider, setJsonRpcProvider] = useState( localStorage.getItem('json-rpc-provider') || initialValues.jsonRpcProvider, ) - const [xDaiBalance, setXDaiBalance] = useState(initialValues.xDaiBalance) - const [xBzzBalance, setXBzzBalance] = useState(initialValues.xBzzBalance) const [wallet, setWallet] = useState(initialValues.wallet) + const [giftWallets, setGiftWallets] = useState(initialValues.giftWallets) useEffect(() => { const existingWallet = localStorage.getItem(LocalStorageKeys.depositWallet) const wallet: Wallet = existingWallet ? getWalletFromPrivateKeyString(existingWallet) : generateWallet() localStorage.setItem(LocalStorageKeys.depositWallet, wallet.getPrivateKeyString()) - setWallet(wallet) + ResolvedWallet.make(wallet).then(setWallet) + }, []) + + useEffect(() => { + const existingGiftWallets = localStorage.getItem(LocalStorageKeys.giftWallets) + + if (existingGiftWallets) { + setGiftWallets(JSON.parse(existingGiftWallets).map(getWalletFromPrivateKeyString)) + } }, []) function setAndPersistJsonRpcProvider(jsonRpcProvider: string) { @@ -55,28 +63,21 @@ export function Provider({ children }: Props): ReactElement { setJsonRpcInDesktop(jsonRpcProvider).catch(console.error) } + function addGiftWallet(wallet: Wallet) { + const newArray = [...giftWallets, wallet] + localStorage.setItem(LocalStorageKeys.giftWallets, JSON.stringify(newArray.map(x => x.getPrivateKeyString()))) + setGiftWallets(newArray) + } + useEffect(() => { if (!wallet) { return } - function refreshBalances() { - if (!wallet) { - return - } - Rpc._eth_getBalance(wallet.getAddressString()).then(balance => { - setXDaiBalance(new Token(balance, 18)) - }) - Rpc._eth_getBalanceERC20(wallet.getAddressString()).then(balance => { - setXBzzBalance(new Token(balance, 16)) - }) - } - - refreshBalances() - const interval = setInterval(refreshBalances, 10_000) + const timeout = setTimeout(wallet.refresh.bind(wallet), 30_000) return () => { - clearInterval(interval) + clearTimeout(timeout) } }, [wallet]) @@ -84,10 +85,10 @@ export function Provider({ children }: Props): ReactElement { {children} diff --git a/src/routes.tsx b/src/routes.tsx index 2b3c133b..a7b37eb7 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -9,6 +9,7 @@ import { Download } from './pages/files/Download' import { Share } from './pages/files/Share' import { Upload } from './pages/files/Upload' import { UploadLander } from './pages/files/UploadLander' +import GiftCards from './pages/gift-code' import Info from './pages/info' import Wallet from './pages/rpc' import Confirmation from './pages/rpc/Confirmation' @@ -19,6 +20,8 @@ import Status from './pages/status' import { BankCardTopUpIndex } from './pages/top-up/BankCardTopUpIndex' import { CryptoTopUpIndex } from './pages/top-up/CryptoTopUpIndex' import { Fund } from './pages/top-up/Fund' +import { GiftCardFund } from './pages/top-up/GiftCardFund' +import { GiftCardTopUpIndex } from './pages/top-up/GiftCardTopUpIndex' import { Swap } from './pages/top-up/Swap' export enum ROUTES { @@ -45,6 +48,9 @@ export enum ROUTES { TOP_UP_BANK_CARD = '/top-up/bank-card', TOP_UP_BANK_CARD_SWAP = '/top-up/bank-card/swap', TOP_UP_BANK_CARD_FUND = '/top-up/bank-card/fund', + TOP_UP_GIFT_CODE = '/top-up/gift-code', + TOP_UP_GIFT_CODE_FUND = '/top-up/gift-code/fund/:privateKeyString', + GIFT_CODES = '/gift-codes', } const BaseRouter = (): ReactElement => ( @@ -65,6 +71,7 @@ const BaseRouter = (): ReactElement => ( } /> } /> } /> + } /> } /> ( element={} /> } /> + } /> + } /> ) diff --git a/src/utils/desktop.ts b/src/utils/desktop.ts index 722169be..2adade48 100644 --- a/src/utils/desktop.ts +++ b/src/utils/desktop.ts @@ -40,6 +40,10 @@ export async function restartBeeNode(): Promise { await postJson(`http://${getDesktopHost()}/restart`) } +export async function createGiftWallet(address: string): Promise { + await postJson(`http://${getDesktopHost()}/gift-wallet/${address}`) +} + function getDesktopHost(): string { return window.location.host } diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts new file mode 100644 index 00000000..ee5374b6 --- /dev/null +++ b/src/utils/wallet.ts @@ -0,0 +1,52 @@ +import Wallet from 'ethereumjs-wallet' +import { BzzToken } from '../models/BzzToken' +import { DaiToken } from '../models/DaiToken' +import { getWalletFromPrivateKeyString } from './identity' +import { Rpc } from './rpc' + +export class ResolvedWallet { + public address: string + public privateKey: string + + private constructor(public wallet: Wallet, public bzz: BzzToken, public dai: DaiToken) { + this.address = wallet.getAddressString() + this.privateKey = wallet.getPrivateKeyString() + } + + static async make(privateKeyOrWallet: string | Wallet): Promise { + const wallet = + typeof privateKeyOrWallet === 'string' ? getWalletFromPrivateKeyString(privateKeyOrWallet) : privateKeyOrWallet + const address = wallet.getAddressString() + const bzz = new BzzToken(await Rpc._eth_getBalanceERC20(address)) + const dai = new DaiToken(await Rpc._eth_getBalance(address)) + + return new ResolvedWallet(wallet, bzz, dai) + } + + public async refresh(): Promise { + this.bzz = new BzzToken(await Rpc._eth_getBalanceERC20(this.address)) + this.dai = new DaiToken(await Rpc._eth_getBalance(this.address)) + + return this + } + + public async transfer( + destination: string, + jsonRpcProvider = 'https://gno.getblock.io/mainnet/?api_key=d7b92d96-9784-49a8-a800-b3edd1647fc7', + ): Promise { + const DUMMY_GAS_PRICE = '300000000000000' + + if (this.bzz.toDecimal.gt(0.1)) { + await Rpc.sendBzzTransaction(this.privateKey, destination, this.bzz.toString, jsonRpcProvider) + } + + if (this.dai.toBigNumber.gt(DUMMY_GAS_PRICE)) { + await Rpc.sendNativeTransaction( + this.privateKey, + destination, + this.dai.toBigNumber.minus(DUMMY_GAS_PRICE).toString(), + jsonRpcProvider, + ) + } + } +} From 2eb7672b8e0af224dcb57927c98f806ef370f076 Mon Sep 17 00:00:00 2001 From: Cafe137 Date: Thu, 19 May 2022 12:35:02 +0200 Subject: [PATCH 07/15] fix: fix gift wallet flows --- src/models/Token.ts | 11 ++++++- src/pages/gift-code/index.tsx | 10 ++++-- src/pages/top-up/Fund.tsx | 4 +-- src/pages/top-up/GiftCardFund.tsx | 4 +-- src/pages/top-up/GiftCardTopUpIndex.tsx | 6 ++-- src/pages/top-up/Swap.tsx | 6 ++-- src/providers/Bee.tsx | 42 +++++++------------------ src/utils/rpc.ts | 2 ++ src/utils/wallet.ts | 18 +++++++++++ 9 files changed, 60 insertions(+), 43 deletions(-) diff --git a/src/models/Token.ts b/src/models/Token.ts index 2f127b3a..da4bb6c8 100644 --- a/src/models/Token.ts +++ b/src/models/Token.ts @@ -13,7 +13,9 @@ export class Token { constructor(amount: BigNumber | string | BigInt, decimals: digits = BZZ_DECIMALS) { const a = makeBigNumber(amount) - if (!isInteger(a) || !POSSIBLE_DECIMALS.includes(decimals)) throw new TypeError('Not a valid token values') + if (!isInteger(a) || !POSSIBLE_DECIMALS.includes(decimals)) { + throw new TypeError(`Not a valid token values: ${amount} ${decimals}`) + } this.amount = a this.decimals = decimals @@ -78,4 +80,11 @@ export class Token { return asString.slice(0, indexOfSignificantDigit + digits) } + + minusEther(amount: string): Token { + return new Token( + this.toBigNumber.minus(new BigNumber(amount).multipliedBy(new BigNumber(10).pow(this.decimals))), + this.decimals, + ) + } } diff --git a/src/pages/gift-code/index.tsx b/src/pages/gift-code/index.tsx index a65b2399..4ac48073 100644 --- a/src/pages/gift-code/index.tsx +++ b/src/pages/gift-code/index.tsx @@ -7,6 +7,7 @@ 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 BeeContext } from '../../providers/Bee' import { Context as TopUpContext } from '../../providers/TopUp' @@ -55,6 +56,10 @@ export default function Index(): ReactElement { navigate(-1) } + if (!balance) { + return + } + return ( <> Invite to Swarm... @@ -65,7 +70,7 @@ export default function Index(): ReactElement { - + @@ -73,7 +78,8 @@ export default function Index(): ReactElement { {balances.map((x, i) => ( - + + diff --git a/src/pages/top-up/Fund.tsx b/src/pages/top-up/Fund.tsx index 8a6b6be4..b64ba509 100644 --- a/src/pages/top-up/Fund.tsx +++ b/src/pages/top-up/Fund.tsx @@ -28,7 +28,7 @@ export function Fund({ header }: Props): ReactElement { const [loading, setLoading] = useState(false) - if (!wallet) { + if (!wallet || !balance) { return } @@ -82,7 +82,7 @@ export function Fund({ header }: Props): ReactElement { - + diff --git a/src/pages/top-up/GiftCardFund.tsx b/src/pages/top-up/GiftCardFund.tsx index 4c337adc..3ffa5a42 100644 --- a/src/pages/top-up/GiftCardFund.tsx +++ b/src/pages/top-up/GiftCardFund.tsx @@ -36,7 +36,7 @@ export function GiftCardFund(): ReactElement { ResolvedWallet.make(privateKeyString).then(setWallet) }, [privateKeyString]) - if (!wallet) { + if (!wallet || !balance) { return } @@ -92,7 +92,7 @@ export function GiftCardFund(): ReactElement { - + diff --git a/src/pages/top-up/GiftCardTopUpIndex.tsx b/src/pages/top-up/GiftCardTopUpIndex.tsx index 4ca4aab5..2bd26e8a 100644 --- a/src/pages/top-up/GiftCardTopUpIndex.tsx +++ b/src/pages/top-up/GiftCardTopUpIndex.tsx @@ -28,10 +28,10 @@ export function GiftCardTopUpIndex(): ReactElement { const dai = new DaiToken(await Rpc._eth_getBalance(wallet.getAddressString())) const bzz = new BzzToken(await Rpc._eth_getBalanceERC20(wallet.getAddressString())) - if (dai.toDecimal.lt(0.1) || bzz.toDecimal.lt(0.1)) { - throw Error('Gift wallet does not enough funds') + if (dai.toDecimal.lt(0.001) || bzz.toDecimal.lt(0.001)) { + throw Error('Gift wallet does not have enough funds') } - enqueueSnackbar('Successfully verified gift wallet') + enqueueSnackbar('Successfully verified gift wallet', { variant: 'success' }) navigate(ROUTES.TOP_UP_GIFT_CODE_FUND.replace(':privateKeyString', giftCode)) } catch (error) { enqueueSnackbar(`Gift wallet could not be verified: ${error}`, { variant: 'error' }) diff --git a/src/pages/top-up/Swap.tsx b/src/pages/top-up/Swap.tsx index c0cca6c8..b7f03683 100644 --- a/src/pages/top-up/Swap.tsx +++ b/src/pages/top-up/Swap.tsx @@ -34,10 +34,10 @@ export function Swap({ header, next }: Props): ReactElement { return } - const daiToSwap = DaiToken.fromDecimal(wallet.dai.toDecimal.minus(1)) + const daiToSwap = wallet.dai.minusEther('1') const daiAfterSwap = new DaiToken(wallet.dai.toBigNumber.minus(daiToSwap.toBigNumber)) - const bzzAfterSwap = BzzToken.fromDecimal(daiAfterSwap.toDecimal.dividedBy(2)) + const bzzAfterSwap = new BzzToken(daiToSwap.toBigNumber.dividedToIntegerBy(200)) async function onSwap() { if (!wallet || hasSwapped) { @@ -46,7 +46,7 @@ export function Swap({ header, next }: Props): ReactElement { setLoading(true) setSwapped(true) try { - await swap(wallet.privateKey, daiToSwap.toString, '0.1') + await swap(wallet.privateKey, daiToSwap.toString, '10000') enqueueSnackbar('Successfully swapped', { variant: 'success' }) } catch (error) { enqueueSnackbar(`Failed to swap: ${error}`, { variant: 'error' }) diff --git a/src/providers/Bee.tsx b/src/providers/Bee.tsx index d87d247d..280715c4 100644 --- a/src/providers/Bee.tsx +++ b/src/providers/Bee.tsx @@ -13,18 +13,11 @@ import { createContext, ReactChild, ReactElement, useContext, useEffect, useStat import semver from 'semver' import { engines } from '../../package.json' import { useLatestBeeRelease } from '../hooks/apiHooks' -import { BzzToken } from '../models/BzzToken' -import { DaiToken } from '../models/DaiToken' import { Token } from '../models/Token' import type { Balance, ChequebookBalance, Settlements } from '../types' -import { Rpc } from '../utils/rpc' +import { WalletAddress } from '../utils/wallet' import { Context as SettingsContext } from './Settings' -interface RpcBalance { - bzz: Token - xdai: Token -} - export enum CheckState { OK = 'OK', WARNING = 'Warning', @@ -48,7 +41,7 @@ interface Status { interface ContextInterface { status: Status - balance: RpcBalance + balance: WalletAddress | null latestPublishedVersion?: string latestUserVersion?: string latestUserVersionExact?: string @@ -86,10 +79,7 @@ const initialValues: ContextInterface = { topology: { isEnabled: false, checkState: CheckState.ERROR }, chequebook: { isEnabled: false, checkState: CheckState.ERROR }, }, - balance: { - bzz: new BzzToken('0'), - xdai: new DaiToken('0'), - }, + balance: null, latestPublishedVersion: undefined, latestUserVersion: undefined, latestUserVersionExact: undefined, @@ -206,8 +196,7 @@ export function Provider({ children }: Props): ReactElement { const [peerCheques, setPeerCheques] = useState(null) const [settlements, setSettlements] = useState(null) const [chainState, setChainState] = useState(null) - const [bzz, setBzz] = useState(initialValues.balance.bzz) - const [xdai, setXdai] = useState(initialValues.balance.xdai) + const [walletAddress, setWalletAddress] = useState(initialValues.balance) const { latestBeeRelease } = useLatestBeeRelease() @@ -249,20 +238,16 @@ export function Provider({ children }: Props): ReactElement { useEffect(() => { if (nodeAddresses?.ethereum) { - // debounced calls - const xdai = Rpc.eth_getBalance(nodeAddresses.ethereum) - const bzz = Rpc.eth_getBalanceERC20(nodeAddresses.ethereum) - - if (xdai?.then) { - xdai.then(balance => setXdai(new DaiToken(balance))) - } - - if (bzz?.then) { - bzz.then(balance => setBzz(new BzzToken(balance))) - } + WalletAddress.make(nodeAddresses.ethereum).then(setWalletAddress) } }, [nodeAddresses]) + useEffect(() => { + const interval = setInterval(() => walletAddress?.refresh().then(setWalletAddress), 30_000) + + return () => clearInterval(interval) + }, [walletAddress]) + const refresh = async () => { // Don't want to refresh when already refreshing if (isRefreshing) return @@ -419,10 +404,7 @@ export function Provider({ children }: Props): ReactElement { chequebookBalance, error, ), - balance: { - xdai, - bzz, - }, + balance: walletAddress, latestUserVersion, latestUserVersionExact, latestPublishedVersion, diff --git a/src/utils/rpc.ts b/src/utils/rpc.ts index 5937a283..0105f3db 100644 --- a/src/utils/rpc.ts +++ b/src/utils/rpc.ts @@ -86,6 +86,7 @@ async function sendNativeTransaction( providerHost: string, ): Promise { const provider = new JsonRpcProvider(providerHost) + await provider.ready const signer = new Wallet(privateKey, provider) const gasPrice = await signer.getGasPrice() @@ -103,6 +104,7 @@ async function sendBzzTransaction( providerHost: string, ): Promise { const provider = new JsonRpcProvider(providerHost) + await provider.ready const signer = new Wallet(privateKey, provider) const bzz = new Contract('0xdBF3Ea6F5beE45c02255B2c26a16F300502F68da', bzzContractInterface, signer) const gasPrice = await signer.getGasPrice() diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index ee5374b6..5cb14a9e 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -4,6 +4,24 @@ import { DaiToken } from '../models/DaiToken' import { getWalletFromPrivateKeyString } from './identity' import { Rpc } from './rpc' +export class WalletAddress { + private constructor(public address: string, public bzz: BzzToken, public dai: DaiToken) {} + + static async make(address: string): Promise { + const bzz = new BzzToken(await Rpc._eth_getBalanceERC20(address)) + const dai = new DaiToken(await Rpc._eth_getBalance(address)) + + return new WalletAddress(address, bzz, dai) + } + + public async refresh(): Promise { + this.bzz = new BzzToken(await Rpc._eth_getBalanceERC20(this.address)) + this.dai = new DaiToken(await Rpc._eth_getBalance(this.address)) + + return this + } +} + export class ResolvedWallet { public address: string public privateKey: string From 16e3be8acb903cda9cca64bca79b5d0191fa4693 Mon Sep 17 00:00:00 2001 From: Cafe137 Date: Mon, 23 May 2022 12:25:10 +0200 Subject: [PATCH 08/15] feat: simplify flow without fund step --- src/pages/top-up/Fund.tsx | 95 --------------------- src/pages/top-up/Swap.tsx | 43 +++++----- src/pages/top-up/TopUpProgressIndicator.tsx | 2 +- src/pages/top-up/index.tsx | 12 +-- src/providers/TopUp.tsx | 26 +----- src/routes.tsx | 15 +--- src/utils/desktop.ts | 4 + src/utils/swap.ts | 64 -------------- 8 files changed, 35 insertions(+), 226 deletions(-) delete mode 100644 src/pages/top-up/Fund.tsx delete mode 100644 src/utils/swap.ts diff --git a/src/pages/top-up/Fund.tsx b/src/pages/top-up/Fund.tsx deleted file mode 100644 index b64ba509..00000000 --- a/src/pages/top-up/Fund.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { Box, Typography } from '@material-ui/core' -import { useSnackbar } from 'notistack' -import { ReactElement, useContext, useState } from 'react' -import { ArrowDown, Check } from 'react-feather' -import { useNavigate } from 'react-router' -import ExpandableListItem from '../../components/ExpandableListItem' -import ExpandableListItemKey from '../../components/ExpandableListItemKey' -import { HistoryHeader } from '../../components/HistoryHeader' -import { Loading } from '../../components/Loading' -import { SwarmButton } from '../../components/SwarmButton' -import { SwarmDivider } from '../../components/SwarmDivider' -import { Context as BeeContext } from '../../providers/Bee' -import { Context as TopUpContext } from '../../providers/TopUp' -import { ROUTES } from '../../routes' -import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop' -import { TopUpProgressIndicator } from './TopUpProgressIndicator' - -interface Props { - header: string -} - -export function Fund({ header }: Props): ReactElement { - const { wallet, jsonRpcProvider } = useContext(TopUpContext) - const { nodeAddresses, balance } = useContext(BeeContext) - - const { enqueueSnackbar } = useSnackbar() - const navigate = useNavigate() - - const [loading, setLoading] = useState(false) - - if (!wallet || !balance) { - return - } - - async function onFund() { - if (!nodeAddresses?.ethereum || !wallet) { - return - } - setLoading(true) - try { - await wallet.transfer(nodeAddresses.ethereum, jsonRpcProvider) - await upgradeToLightNode(jsonRpcProvider) - await restartBeeNode() - navigate(ROUTES.INFO) - enqueueSnackbar('Successfully funded node', { variant: 'success' }) - } catch (error) { - enqueueSnackbar(`Failed to fund node: ${error}`, { variant: 'error' }) - } finally { - setLoading(false) - } - } - - return ( - <> - {header} - - - - - Send funds to your Bee node - - - - Lastly, deposit all the funds from the funding wallet to your node wallet address. You can use the button - below to transfer all funds to your node. - - - - - - - - - - - - - - - - - - - - - - - - - - Send all funds to your node - - - ) -} diff --git a/src/pages/top-up/Swap.tsx b/src/pages/top-up/Swap.tsx index b7f03683..498ece05 100644 --- a/src/pages/top-up/Swap.tsx +++ b/src/pages/top-up/Swap.tsx @@ -13,41 +13,48 @@ import { SwarmDivider } from '../../components/SwarmDivider' import { SwarmTextInput } from '../../components/SwarmTextInput' import { BzzToken } from '../../models/BzzToken' import { DaiToken } from '../../models/DaiToken' -import { Context } from '../../providers/TopUp' -import { swap } from '../../utils/swap' +import { Context as BeeContext } from '../../providers/Bee' +import { Context as TopUpContext } from '../../providers/TopUp' +import { ROUTES } from '../../routes' +import { performSwap, restartBeeNode, upgradeToLightNode } from '../../utils/desktop' import { TopUpProgressIndicator } from './TopUpProgressIndicator' interface Props { header: string - next: string } -export function Swap({ header, next }: Props): ReactElement { +export function Swap({ header }: Props): ReactElement { const [loading, setLoading] = useState(false) const [hasSwapped, setSwapped] = useState(false) - const { wallet } = useContext(Context) + const { jsonRpcProvider } = useContext(TopUpContext) + const { balance } = useContext(BeeContext) + const navigate = useNavigate() const { enqueueSnackbar } = useSnackbar() - if (!wallet) { + if (!balance) { return } - const daiToSwap = wallet.dai.minusEther('1') + const daiToSwap = balance.dai.minusEther('1') - const daiAfterSwap = new DaiToken(wallet.dai.toBigNumber.minus(daiToSwap.toBigNumber)) + const daiAfterSwap = new DaiToken(balance.dai.toBigNumber.minus(daiToSwap.toBigNumber)) const bzzAfterSwap = new BzzToken(daiToSwap.toBigNumber.dividedToIntegerBy(200)) async function onSwap() { - if (!wallet || hasSwapped) { + if (hasSwapped) { return } setLoading(true) setSwapped(true) try { - await swap(wallet.privateKey, daiToSwap.toString, '10000') + await performSwap(daiToSwap.toString) enqueueSnackbar('Successfully swapped', { variant: 'success' }) + await upgradeToLightNode(jsonRpcProvider) + await restartBeeNode() + navigate(ROUTES.INFO) + enqueueSnackbar('Upgraded to light node', { variant: 'success' }) } catch (error) { enqueueSnackbar(`Failed to swap: ${error}`, { variant: 'error' }) } finally { @@ -73,7 +80,8 @@ export function Swap({ header, next }: Props): ReactElement { - Your current balance is {wallet.dai.toSignificantDigits(4)} xDAI and {wallet.bzz.toSignificantDigits(4)} BZZ. + Your current balance is {balance.dai.toSignificantDigits(4)} xDAI and {balance.bzz.toSignificantDigits(4)}{' '} + BZZ. @@ -88,7 +96,7 @@ export function Swap({ header, next }: Props): ReactElement { - + Swap Now - { - navigate(next) - }} - disabled={loading || wallet.bzz.toDecimal.lte(0.1)} - > - Proceed - ) diff --git a/src/pages/top-up/TopUpProgressIndicator.tsx b/src/pages/top-up/TopUpProgressIndicator.tsx index fbf2a9f0..0de6465e 100644 --- a/src/pages/top-up/TopUpProgressIndicator.tsx +++ b/src/pages/top-up/TopUpProgressIndicator.tsx @@ -6,5 +6,5 @@ interface Props { } export function TopUpProgressIndicator({ index }: Props): ReactElement { - return + return } diff --git a/src/pages/top-up/index.tsx b/src/pages/top-up/index.tsx index 5e85ab35..556f09be 100644 --- a/src/pages/top-up/index.tsx +++ b/src/pages/top-up/index.tsx @@ -8,7 +8,7 @@ import { HistoryHeader } from '../../components/HistoryHeader' import { Loading } from '../../components/Loading' import { SwarmButton } from '../../components/SwarmButton' import { SwarmDivider } from '../../components/SwarmDivider' -import { Context } from '../../providers/TopUp' +import { Context } from '../../providers/Bee' import { TopUpProgressIndicator } from './TopUpProgressIndicator' interface Props { @@ -19,14 +19,14 @@ interface Props { } export default function Index({ header, title, p, next }: Props): ReactElement { - const { wallet } = useContext(Context) + const { nodeAddresses, balance } = useContext(Context) const navigate = useNavigate() - if (!wallet) { + if (!balance || !nodeAddresses) { return } - const disabled = wallet.dai.toDecimal.lte(1) + const disabled = balance.dai.toDecimal.lte(1) return ( <> @@ -40,10 +40,10 @@ export default function Index({ header, title, p, next }: Props): ReactElement { {p} - + - + navigate(next)} disabled={disabled}> diff --git a/src/providers/TopUp.tsx b/src/providers/TopUp.tsx index 6476dd88..bd3a051d 100644 --- a/src/providers/TopUp.tsx +++ b/src/providers/TopUp.tsx @@ -1,8 +1,7 @@ import Wallet from 'ethereumjs-wallet' import { createContext, ReactElement, useEffect, useState } from 'react' import { setJsonRpcInDesktop } from '../utils/desktop' -import { generateWallet, getWalletFromPrivateKeyString } from '../utils/identity' -import { ResolvedWallet } from '../utils/wallet' +import { getWalletFromPrivateKeyString } from '../utils/identity' const LocalStorageKeys = { jsonRpcProvider: 'json-rpc-provider', @@ -13,7 +12,6 @@ const LocalStorageKeys = { interface ContextInterface { jsonRpcProvider: string - wallet: ResolvedWallet | null giftWallets: Wallet[] setJsonRpcProvider: (jsonRpcProvider: string) => void addGiftWallet: (wallet: Wallet) => void @@ -21,7 +19,6 @@ interface ContextInterface { const initialValues: ContextInterface = { jsonRpcProvider: '', - wallet: null, giftWallets: [], setJsonRpcProvider: () => {}, // eslint-disable-line addGiftWallet: () => {}, // eslint-disable-line @@ -38,16 +35,8 @@ export function Provider({ children }: Props): ReactElement { const [jsonRpcProvider, setJsonRpcProvider] = useState( localStorage.getItem('json-rpc-provider') || initialValues.jsonRpcProvider, ) - const [wallet, setWallet] = useState(initialValues.wallet) const [giftWallets, setGiftWallets] = useState(initialValues.giftWallets) - useEffect(() => { - const existingWallet = localStorage.getItem(LocalStorageKeys.depositWallet) - const wallet: Wallet = existingWallet ? getWalletFromPrivateKeyString(existingWallet) : generateWallet() - localStorage.setItem(LocalStorageKeys.depositWallet, wallet.getPrivateKeyString()) - ResolvedWallet.make(wallet).then(setWallet) - }, []) - useEffect(() => { const existingGiftWallets = localStorage.getItem(LocalStorageKeys.giftWallets) @@ -69,23 +58,10 @@ export function Provider({ children }: Props): ReactElement { setGiftWallets(newArray) } - useEffect(() => { - if (!wallet) { - return - } - - const timeout = setTimeout(wallet.refresh.bind(wallet), 30_000) - - return () => { - clearTimeout(timeout) - } - }, [wallet]) - return ( ( } /> } /> } /> - } - /> - } /> + } /> } /> - } - /> - } /> + } /> } /> } /> diff --git a/src/utils/desktop.ts b/src/utils/desktop.ts index 2adade48..ddc267c7 100644 --- a/src/utils/desktop.ts +++ b/src/utils/desktop.ts @@ -44,6 +44,10 @@ export async function createGiftWallet(address: string): Promise { await postJson(`http://${getDesktopHost()}/gift-wallet/${address}`) } +export async function performSwap(daiAmount: string): Promise { + await postJson(`http://${getDesktopHost()}/swap`, { dai: daiAmount }) +} + function getDesktopHost(): string { return window.location.host } diff --git a/src/utils/swap.ts b/src/utils/swap.ts deleted file mode 100644 index 409ab295..00000000 --- a/src/utils/swap.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Contract, ethers, providers } from 'ethers' -import { JSON_RPC_PROVIDER } from './rpc' - -const contractInterface = [ - { - inputs: [ - { - internalType: 'uint256', - name: 'amountOutMin', - type: 'uint256', - }, - { - internalType: 'address[]', - name: 'path', - type: 'address[]', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'deadline', - type: 'uint256', - }, - ], - name: 'swapExactETHForTokens', - outputs: [ - { - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]', - }, - ], - stateMutability: 'payable', - type: 'function', - }, -] - -export async function swap(privateKey: string, xdai: string, minimumBzz: string): Promise { - const contracts = { - UniswapV2Factory: '0xA818b4F111Ccac7AA31D0BCc0806d64F2E0737D7', - UniswapV2Router02: '0x1C232F01118CB8B424793ae03F870aa7D0ac7f77', - } - const provider = new providers.JsonRpcProvider(JSON_RPC_PROVIDER) - const signer = new ethers.Wallet(privateKey, provider) - const gasLimit = 1000000 - const contract = new Contract(contracts.UniswapV2Router02, contractInterface, signer) - const WRAPPED_XDAI_CONTRACT = '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d' - const BZZ_ON_XDAI_CONTRACT = '0xdbf3ea6f5bee45c02255b2c26a16f300502f68da' - const response = await contract.swapExactETHForTokens( - minimumBzz, - [WRAPPED_XDAI_CONTRACT, BZZ_ON_XDAI_CONTRACT], - await signer.getAddress(), - Date.now(), - { - value: xdai, - gasLimit, - }, - ) - - return response -} From ade85b1e6440a0e5d1dd9d5a91620a6dfb32adae Mon Sep 17 00:00:00 2001 From: Cafe137 Date: Wed, 25 May 2022 22:21:08 +0200 Subject: [PATCH 09/15] feat: add loading screens --- src/pages/restart/LightModeRestart.tsx | 32 +++++++++++++++ src/pages/restart/Restart.tsx | 41 +++++++++++++++++++ src/pages/top-up/GiftCardFund.tsx | 8 ++-- src/pages/top-up/Swap.tsx | 6 ++- src/routes.tsx | 6 +++ src/utils/rpc.ts | 56 +++++++++++++++----------- src/utils/wallet.ts | 2 + 7 files changed, 122 insertions(+), 29 deletions(-) create mode 100644 src/pages/restart/LightModeRestart.tsx create mode 100644 src/pages/restart/Restart.tsx diff --git a/src/pages/restart/LightModeRestart.tsx b/src/pages/restart/LightModeRestart.tsx new file mode 100644 index 00000000..dcc540bf --- /dev/null +++ b/src/pages/restart/LightModeRestart.tsx @@ -0,0 +1,32 @@ +import { BeeModes } from '@ethersphere/bee-js' +import { Box, Typography } from '@material-ui/core' +import { ReactElement, useContext, useEffect, useState } from 'react' +import { useNavigate } from 'react-router' +import { Loading } from '../../components/Loading' +import { Context } from '../../providers/Bee' +import { ROUTES } from '../../routes' + +export default function Settings(): ReactElement { + const [startedAt, setStartedAt] = useState(Date.now()) + const { apiHealth, nodeInfo } = useContext(Context) + const navigate = useNavigate() + + useEffect(() => { + if (Date.now() - startedAt < 45_000) { + return + } + + if (apiHealth && nodeInfo?.beeMode === BeeModes.LIGHT) { + navigate(ROUTES.INFO) + } + }, [startedAt, navigate, nodeInfo, apiHealth]) + + return ( + <> + + + + Your node is being upgraded to light mode... postage syncing may take up to 10 minutes. + + ) +} diff --git a/src/pages/restart/Restart.tsx b/src/pages/restart/Restart.tsx new file mode 100644 index 00000000..c51fe9a5 --- /dev/null +++ b/src/pages/restart/Restart.tsx @@ -0,0 +1,41 @@ +import { Box, Typography } from '@material-ui/core' +import { ReactElement, useContext, useEffect, useState } from 'react' +import { useNavigate } from 'react-router' +import { Loading } from '../../components/Loading' +import { Context } from '../../providers/Bee' +import { ROUTES } from '../../routes' + +export default function Settings(): ReactElement { + const [waited, setWaited] = useState(false) + const { apiHealth } = useContext(Context) + const navigate = useNavigate() + + useEffect(() => { + if (waited) { + return + } + + const timeout = setTimeout(() => setWaited(true), 5_000) + + return () => clearTimeout(timeout) + }, [waited]) + + useEffect(() => { + if (!waited) { + return + } + + if (apiHealth) { + navigate(ROUTES.INFO) + } + }, [navigate, waited, apiHealth]) + + return ( + <> + + + + You will be redirected automatically once your node is up and running. + + ) +} diff --git a/src/pages/top-up/GiftCardFund.tsx b/src/pages/top-up/GiftCardFund.tsx index 3ffa5a42..c761002d 100644 --- a/src/pages/top-up/GiftCardFund.tsx +++ b/src/pages/top-up/GiftCardFund.tsx @@ -13,6 +13,7 @@ import { SwarmDivider } from '../../components/SwarmDivider' import { Context as BeeContext } from '../../providers/Bee' import { Context as TopUpContext } from '../../providers/TopUp' import { ROUTES } from '../../routes' +import { sleepMs } from '../../utils' import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop' import { ResolvedWallet } from '../../utils/wallet' @@ -49,12 +50,13 @@ export function GiftCardFund(): ReactElement { try { await wallet.transfer(nodeAddresses.ethereum) + enqueueSnackbar('Successfully funded node, restarting...', { variant: 'success' }) + await sleepMs(5_000) await upgradeToLightNode(jsonRpcProvider) await restartBeeNode() - navigate(ROUTES.INFO) - enqueueSnackbar('Successfully funded node', { variant: 'success' }) + navigate(ROUTES.RESTART_LIGHT) } catch (error) { - enqueueSnackbar(`Failed to fund node: ${error}`, { variant: 'error' }) + enqueueSnackbar(`Failed to fund/restart node: ${error}`, { variant: 'error' }) } finally { setLoading(false) } diff --git a/src/pages/top-up/Swap.tsx b/src/pages/top-up/Swap.tsx index 498ece05..a94c22c4 100644 --- a/src/pages/top-up/Swap.tsx +++ b/src/pages/top-up/Swap.tsx @@ -16,6 +16,7 @@ import { DaiToken } from '../../models/DaiToken' import { Context as BeeContext } from '../../providers/Bee' import { Context as TopUpContext } from '../../providers/TopUp' import { ROUTES } from '../../routes' +import { sleepMs } from '../../utils' import { performSwap, restartBeeNode, upgradeToLightNode } from '../../utils/desktop' import { TopUpProgressIndicator } from './TopUpProgressIndicator' @@ -50,10 +51,11 @@ export function Swap({ header }: Props): ReactElement { setSwapped(true) try { await performSwap(daiToSwap.toString) - enqueueSnackbar('Successfully swapped', { variant: 'success' }) + enqueueSnackbar('Successfully swapped, restarting...', { variant: 'success' }) + await sleepMs(5_000) await upgradeToLightNode(jsonRpcProvider) await restartBeeNode() - navigate(ROUTES.INFO) + navigate(ROUTES.RESTART_LIGHT) enqueueSnackbar('Upgraded to light node', { variant: 'success' }) } catch (error) { enqueueSnackbar(`Failed to swap: ${error}`, { variant: 'error' }) diff --git a/src/routes.tsx b/src/routes.tsx index 9e19695f..165fed38 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -11,6 +11,8 @@ import { Upload } from './pages/files/Upload' import { UploadLander } from './pages/files/UploadLander' import GiftCards from './pages/gift-code' import Info from './pages/info' +import LightModeRestart from './pages/restart/LightModeRestart' +import Restart from './pages/restart/Restart' import Wallet from './pages/rpc' import Confirmation from './pages/rpc/Confirmation' import Settings from './pages/settings' @@ -48,6 +50,8 @@ export enum ROUTES { TOP_UP_GIFT_CODE = '/top-up/gift-code', TOP_UP_GIFT_CODE_FUND = '/top-up/gift-code/fund/:privateKeyString', GIFT_CODES = '/gift-codes', + RESTART = '/restart', + RESTART_LIGHT = '/light-mode-restart', } const BaseRouter = (): ReactElement => ( @@ -75,6 +79,8 @@ const BaseRouter = (): ReactElement => ( } /> } /> } /> + } /> + } /> ) diff --git a/src/utils/rpc.ts b/src/utils/rpc.ts index 0105f3db..d9bb5c4a 100644 --- a/src/utils/rpc.ts +++ b/src/utils/rpc.ts @@ -1,7 +1,6 @@ -import { JsonRpcProvider } from '@ethersproject/providers' import { debounce } from '@material-ui/core' import axios from 'axios' -import { BigNumber, Contract, providers, Wallet } from 'ethers' +import { Contract, providers, Wallet } from 'ethers' import { bzzContractInterface } from './bzz-contract-interface' export const JSON_RPC_PROVIDER = 'https://gno.getblock.io/mainnet/?api_key=d7b92d96-9784-49a8-a800-b3edd1647fc7' @@ -79,37 +78,46 @@ async function eth_getBalanceERC20( return balance.toString() } -async function sendNativeTransaction( +interface TransferResponse { + transaction: providers.TransactionResponse + receipt: providers.TransactionReceipt +} + +export async function sendNativeTransaction( privateKey: string, - address: string, - amount: string, - providerHost: string, -): Promise { - const provider = new JsonRpcProvider(providerHost) - await provider.ready - const signer = new Wallet(privateKey, provider) + to: string, + value: string, + jsonRpcProvider: string, +): Promise { + const signer = await makeReadySigner(privateKey, jsonRpcProvider) const gasPrice = await signer.getGasPrice() + const transaction = await signer.sendTransaction({ to, value, gasPrice }) + const receipt = await transaction.wait(1) - return signer.sendTransaction({ - to: address, - value: amount, - gasPrice, - }) + return { transaction, receipt } } -async function sendBzzTransaction( +export async function sendBzzTransaction( privateKey: string, - address: string, - amount: string, - providerHost: string, -): Promise { - const provider = new JsonRpcProvider(providerHost) + to: string, + value: string, + jsonRpcProvider: string, +): Promise { + const signer = await makeReadySigner(privateKey, jsonRpcProvider) + const gasPrice = await signer.getGasPrice() + const bzz = new Contract('0xdBF3Ea6F5beE45c02255B2c26a16F300502F68da', bzzContractInterface, signer) + const transaction = await bzz.transfer(to, value, { gasPrice }) + const receipt = await transaction.wait(1) + + return { transaction, receipt } +} + +async function makeReadySigner(privateKey: string, jsonRpcProvider: string) { + const provider = new providers.JsonRpcProvider(jsonRpcProvider, 100) await provider.ready const signer = new Wallet(privateKey, provider) - const bzz = new Contract('0xdBF3Ea6F5beE45c02255B2c26a16F300502F68da', bzzContractInterface, signer) - const gasPrice = await signer.getGasPrice() - return bzz.transfer(address, BigNumber.from(amount), { gasPrice }) + return signer } export const Rpc = { diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index 5cb14a9e..bcef241d 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -1,4 +1,5 @@ import Wallet from 'ethereumjs-wallet' +import { sleepMs } from '.' import { BzzToken } from '../models/BzzToken' import { DaiToken } from '../models/DaiToken' import { getWalletFromPrivateKeyString } from './identity' @@ -56,6 +57,7 @@ export class ResolvedWallet { if (this.bzz.toDecimal.gt(0.1)) { await Rpc.sendBzzTransaction(this.privateKey, destination, this.bzz.toString, jsonRpcProvider) + await sleepMs(5_000) } if (this.dai.toBigNumber.gt(DUMMY_GAS_PRICE)) { From 2824bdc40803a390148ee80ec3f5e80abe4de1a0 Mon Sep 17 00:00:00 2001 From: Cafe137 Date: Thu, 26 May 2022 13:08:55 +0200 Subject: [PATCH 10/15] fix: remove alert --- src/components/AlertVersion.tsx | 54 --------------------------------- src/layout/Dashboard.tsx | 11 ++----- 2 files changed, 3 insertions(+), 62 deletions(-) delete mode 100644 src/components/AlertVersion.tsx diff --git a/src/components/AlertVersion.tsx b/src/components/AlertVersion.tsx deleted file mode 100644 index 44fea3ac..00000000 --- a/src/components/AlertVersion.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { ReactElement, useState, useContext } from 'react' -import { makeStyles, Theme, createStyles } from '@material-ui/core/styles' -import { Alert, AlertTitle } from '@material-ui/lab' -import Collapse from '@material-ui/core/Collapse' -import IconButton from '@material-ui/core/IconButton' -import CloseIcon from '@material-ui/icons/Close' -import { Context } from '../providers/Bee' -import { SUPPORTED_BEE_VERSION_EXACT } from '@ethersphere/bee-js' - -const useStyles = makeStyles((theme: Theme) => - createStyles({ - root: { - width: '100%', - marginBottom: theme.spacing(2), - }, - }), -) - -export default function VersionAlert(): ReactElement | null { - const classes = useStyles() - const { isLoading, latestUserVersionExact } = useContext(Context) - const [open, setOpen] = useState(true) - - const isExactlySupportedBeeVersion = SUPPORTED_BEE_VERSION_EXACT === latestUserVersionExact - - if (isLoading || !latestUserVersionExact) return null - - return ( - -
- { - setOpen(false) - }} - > - - - } - > - Warning - Your Bee node version ({latestUserVersionExact}) does not exactly match the Bee version we tested - the Bee Dashboard against ({SUPPORTED_BEE_VERSION_EXACT}). Please note that some functionality - may not work properly. - -
-
- ) -} diff --git a/src/layout/Dashboard.tsx b/src/layout/Dashboard.tsx index 89486adc..b1de3452 100644 --- a/src/layout/Dashboard.tsx +++ b/src/layout/Dashboard.tsx @@ -1,12 +1,8 @@ -import { useContext, ReactElement } from 'react' +import { CircularProgress, Container } from '@material-ui/core' +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' +import { ReactElement, useContext } from 'react' import ErrorBoundary from '../components/ErrorBoundary' -import AlertVersion from '../components/AlertVersion' -import { Container, CircularProgress } from '@material-ui/core' - -import { createStyles, Theme, makeStyles } from '@material-ui/core/styles' - import SideBar from '../components/SideBar' - import { Context } from '../providers/Bee' const useStyles = makeStyles((theme: Theme) => @@ -33,7 +29,6 @@ const Dashboard = (props: Props): ReactElement => { <> - {isLoading ? (
From bac8c3f3ab1c425bd524d9994dc827bb94721b9b Mon Sep 17 00:00:00 2001 From: Cafe137 Date: Tue, 31 May 2022 14:52:36 +0200 Subject: [PATCH 11/15] fix: prepend http if needed --- src/providers/Settings.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/providers/Settings.tsx b/src/providers/Settings.tsx index 036a6424..36800307 100644 --- a/src/providers/Settings.tsx +++ b/src/providers/Settings.tsx @@ -55,8 +55,16 @@ export function Provider({ const [desktopApiKey, setDesktopApiKey] = useState(initialValues.desktopApiKey) const { config, isLoading, error } = useGetBeeConfig() - const url = config?.['api-addr'] || beeApiUrl || apiUrl - const debugUrl = config?.['debug-api-addr'] || beeDebugApiUrl || apiDebugUrl + function makeHttpUrl(string: string): string { + if (!string.startsWith('http')) { + return `http://${string}` + } + + return string + } + + const url = makeHttpUrl(config?.['api-addr'] || beeApiUrl || apiUrl) + const debugUrl = makeHttpUrl(config?.['debug-api-addr'] || beeDebugApiUrl || apiDebugUrl) useEffect(() => { const urlSearchParams = new URLSearchParams(window.location.search) From bb15c1fa602259e98d33044c6e6da89665131a2c Mon Sep 17 00:00:00 2001 From: Cafe137 Date: Wed, 1 Jun 2022 10:31:32 +0200 Subject: [PATCH 12/15] fix: fix bug that was reintroduced with merge --- src/pages/files/Upload.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/files/Upload.tsx b/src/pages/files/Upload.tsx index b5bef5b2..2c3937fd 100644 --- a/src/pages/files/Upload.tsx +++ b/src/pages/files/Upload.tsx @@ -84,7 +84,7 @@ export function Upload(): ReactElement { if (idx.commonPrefix) { const substrStart = idx.commonPrefix.length indexDocument = idx.indexPath.slice(substrStart) - fls = fls.map(f => { + fls = files.map(f => { const path = (f.path as string).slice(substrStart) return packageFile(f, path) From 23b11c149bc65ace13d75954b5fa87f760b643bb Mon Sep 17 00:00:00 2001 From: Cafe137 Date: Wed, 1 Jun 2022 10:35:32 +0200 Subject: [PATCH 13/15] refactor: rename minusEther to minusBaseUnits --- src/models/Token.ts | 2 +- src/pages/top-up/Swap.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/Token.ts b/src/models/Token.ts index da4bb6c8..0ce2ed7c 100644 --- a/src/models/Token.ts +++ b/src/models/Token.ts @@ -81,7 +81,7 @@ export class Token { return asString.slice(0, indexOfSignificantDigit + digits) } - minusEther(amount: string): Token { + minusBaseUnits(amount: string): Token { return new Token( this.toBigNumber.minus(new BigNumber(amount).multipliedBy(new BigNumber(10).pow(this.decimals))), this.decimals, diff --git a/src/pages/top-up/Swap.tsx b/src/pages/top-up/Swap.tsx index a94c22c4..e43e3ddf 100644 --- a/src/pages/top-up/Swap.tsx +++ b/src/pages/top-up/Swap.tsx @@ -38,7 +38,7 @@ export function Swap({ header }: Props): ReactElement { return } - const daiToSwap = balance.dai.minusEther('1') + const daiToSwap = balance.dai.minusBaseUnits('1') const daiAfterSwap = new DaiToken(balance.dai.toBigNumber.minus(daiToSwap.toBigNumber)) const bzzAfterSwap = new BzzToken(daiToSwap.toBigNumber.dividedToIntegerBy(200)) From 8339d362a4ea198de9e787f294e5955cb77c3a19 Mon Sep 17 00:00:00 2001 From: Cafe137 Date: Wed, 1 Jun 2022 11:03:21 +0200 Subject: [PATCH 14/15] fix: remove unused setStartedAt --- src/pages/restart/LightModeRestart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/restart/LightModeRestart.tsx b/src/pages/restart/LightModeRestart.tsx index dcc540bf..da1cf440 100644 --- a/src/pages/restart/LightModeRestart.tsx +++ b/src/pages/restart/LightModeRestart.tsx @@ -7,7 +7,7 @@ import { Context } from '../../providers/Bee' import { ROUTES } from '../../routes' export default function Settings(): ReactElement { - const [startedAt, setStartedAt] = useState(Date.now()) + const [startedAt] = useState(Date.now()) const { apiHealth, nodeInfo } = useContext(Context) const navigate = useNavigate() From 25c11581b631f50d780c62978344ab243b09642c Mon Sep 17 00:00:00 2001 From: Cafe137 Date: Wed, 1 Jun 2022 11:26:50 +0200 Subject: [PATCH 15/15] build: remove unused dependency --- package-lock.json | 91 ----------------------------------------------- package.json | 1 - 2 files changed, 92 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5cbbdefc..c3f760e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "@ethersphere/bee-js": "^3.3.4", "@ethersphere/manifest-js": "1.1.0", "@ethersphere/swarm-cid": "^0.1.0", - "@ethersproject/providers": "^5.6.5", "@material-ui/core": "4.12.3", "@material-ui/icons": "4.11.2", "@material-ui/lab": "4.0.0-alpha.57", @@ -2686,62 +2685,6 @@ "@ethersproject/logger": "^5.6.0" } }, - "node_modules/@ethersproject/providers": { - "version": "5.6.5", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.6.5.tgz", - "integrity": "sha512-TRS+c2Ud+cMpWodmGAc9xbnYRPWzRNYt2zkCSnj58nJoamBQ6x4cUbBeo0lTC3y+6RDVIBeJv18OqsDbSktLVg==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abstract-provider": "^5.6.0", - "@ethersproject/abstract-signer": "^5.6.0", - "@ethersproject/address": "^5.6.0", - "@ethersproject/basex": "^5.6.0", - "@ethersproject/bignumber": "^5.6.0", - "@ethersproject/bytes": "^5.6.0", - "@ethersproject/constants": "^5.6.0", - "@ethersproject/hash": "^5.6.0", - "@ethersproject/logger": "^5.6.0", - "@ethersproject/networks": "^5.6.0", - "@ethersproject/properties": "^5.6.0", - "@ethersproject/random": "^5.6.0", - "@ethersproject/rlp": "^5.6.0", - "@ethersproject/sha2": "^5.6.0", - "@ethersproject/strings": "^5.6.0", - "@ethersproject/transactions": "^5.6.0", - "@ethersproject/web": "^5.6.0", - "bech32": "1.1.4", - "ws": "7.4.6" - } - }, - "node_modules/@ethersproject/providers/node_modules/ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/@ethersproject/random": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.6.0.tgz", @@ -31191,40 +31134,6 @@ "@ethersproject/logger": "^5.6.0" } }, - "@ethersproject/providers": { - "version": "5.6.5", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.6.5.tgz", - "integrity": "sha512-TRS+c2Ud+cMpWodmGAc9xbnYRPWzRNYt2zkCSnj58nJoamBQ6x4cUbBeo0lTC3y+6RDVIBeJv18OqsDbSktLVg==", - "requires": { - "@ethersproject/abstract-provider": "^5.6.0", - "@ethersproject/abstract-signer": "^5.6.0", - "@ethersproject/address": "^5.6.0", - "@ethersproject/basex": "^5.6.0", - "@ethersproject/bignumber": "^5.6.0", - "@ethersproject/bytes": "^5.6.0", - "@ethersproject/constants": "^5.6.0", - "@ethersproject/hash": "^5.6.0", - "@ethersproject/logger": "^5.6.0", - "@ethersproject/networks": "^5.6.0", - "@ethersproject/properties": "^5.6.0", - "@ethersproject/random": "^5.6.0", - "@ethersproject/rlp": "^5.6.0", - "@ethersproject/sha2": "^5.6.0", - "@ethersproject/strings": "^5.6.0", - "@ethersproject/transactions": "^5.6.0", - "@ethersproject/web": "^5.6.0", - "bech32": "1.1.4", - "ws": "7.4.6" - }, - "dependencies": { - "ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "requires": {} - } - } - }, "@ethersproject/random": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.6.0.tgz", diff --git a/package.json b/package.json index aa6270d8..619fda49 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "@ethersphere/bee-js": "^3.3.4", "@ethersphere/manifest-js": "1.1.0", "@ethersphere/swarm-cid": "^0.1.0", - "@ethersproject/providers": "^5.6.5", "@material-ui/core": "4.12.3", "@material-ui/icons": "4.11.2", "@material-ui/lab": "4.0.0-alpha.57",