feat: add bee desktop toolkit (#311)

* feat: add light node upgrade

* refactor: improve upgrade page

* feat: pretty print xdai and add xbzz faucets

* feat: display xBZZ balance (#312)

* refactor: change rpc provider

* fix: remove version alert

* fix: load really xBZZ balance instead of xDAI (#314)

* feat: add bee desktop api key support

* chore: remove dead code

* chore: revert useless change

* refactor: extract desktop utils module (#339)

* refactor: extract desktop utils module

* fix: add 0x prefix if it missing from address

* refactor: extract BalanceProvider

* fix: remove double finally

* fix: remove token fallbacks

* fix: reuse address and handle balance errors

* chore: disable eslint for any

* refactor: remove upgrade page

* refactor: cleanup, debounce and axios

* refactor: change fetch to axios

* chore: remove dead code

* chore: revert import ordering

* refactor: use axios instead of fetch

* refactor: use token instead of string

Co-authored-by: Cafe137 <aron@aronsoos.com>
Co-authored-by: Vojtech Simetka <vojtech@simetka.cz>
This commit is contained in:
Cafe137
2022-04-21 16:29:50 +02:00
committed by GitHub
parent 9b5b2973cb
commit ecaf2054fc
8 changed files with 1366 additions and 1 deletions
+34 -1
View File
@@ -1,4 +1,5 @@
import {
BeeModes,
ChainState,
ChequebookAddressResponse,
Health,
@@ -7,7 +8,6 @@ import {
NodeInfo,
Peer,
Topology,
BeeModes,
} from '@ethersphere/bee-js'
import { createContext, ReactChild, ReactElement, useContext, useEffect, useState } from 'react'
import semver from 'semver'
@@ -15,8 +15,14 @@ import { engines } from '../../package.json'
import { useLatestBeeRelease } from '../hooks/apiHooks'
import { Token } from '../models/Token'
import type { Balance, ChequebookBalance, Settlements } from '../types'
import { Rpc } from '../utils/rpc'
import { Context as SettingsContext } from './Settings'
interface RpcBalance {
bzz: Token
xdai: Token
}
export enum CheckState {
OK = 'OK',
WARNING = 'Warning',
@@ -40,6 +46,7 @@ interface Status {
interface ContextInterface {
status: Status
balance: RpcBalance
latestPublishedVersion?: string
latestUserVersion?: string
latestUserVersionExact?: string
@@ -77,6 +84,10 @@ const initialValues: ContextInterface = {
topology: { isEnabled: false, checkState: CheckState.ERROR },
chequebook: { isEnabled: false, checkState: CheckState.ERROR },
},
balance: {
bzz: new Token('0', 16),
xdai: new Token('0', 18),
},
latestPublishedVersion: undefined,
latestUserVersion: undefined,
latestUserVersionExact: undefined,
@@ -193,6 +204,8 @@ export function Provider({ children }: Props): ReactElement {
const [peerCheques, setPeerCheques] = useState<LastChequesResponse | null>(null)
const [settlements, setSettlements] = useState<Settlements | null>(null)
const [chainState, setChainState] = useState<ChainState | null>(null)
const [bzz, setBzz] = useState<Token>(initialValues.balance.bzz)
const [xdai, setXdai] = useState<Token>(initialValues.balance.xdai)
const { latestBeeRelease } = useLatestBeeRelease()
@@ -232,6 +245,22 @@ export function Provider({ children }: Props): ReactElement {
refresh()
}, [beeDebugApi]) // eslint-disable-line react-hooks/exhaustive-deps
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 Token(balance, 18)))
}
if (bzz?.then) {
bzz.then(balance => setBzz(new Token(balance, 16)))
}
}
}, [nodeAddresses])
const refresh = async () => {
// Don't want to refresh when already refreshing
if (isRefreshing) return
@@ -388,6 +417,10 @@ export function Provider({ children }: Props): ReactElement {
chequebookBalance,
error,
),
balance: {
xdai,
bzz,
},
latestUserVersion,
latestUserVersionExact,
latestPublishedVersion,
+15
View File
@@ -10,6 +10,7 @@ interface ContextInterface {
setApiUrl: (url: string) => void
setDebugApiUrl: (url: string) => void
lockedApiSettings: boolean
desktopApiKey: string
}
const initialValues: ContextInterface = {
@@ -20,6 +21,7 @@ const initialValues: ContextInterface = {
setApiUrl: () => {}, // eslint-disable-line
setDebugApiUrl: () => {}, // eslint-disable-line
lockedApiSettings: false,
desktopApiKey: '',
}
export const Context = createContext<ContextInterface>(initialValues)
@@ -43,10 +45,22 @@ export function Provider({
const [beeApi, setBeeApi] = useState<Bee | null>(null)
const [beeDebugApi, setBeeDebugApi] = useState<BeeDebug | null>(null)
const [lockedApiSettings] = useState<boolean>(Boolean(extLockedApiSettings))
const [desktopApiKey, setDesktopApiKey] = useState<string>(initialValues.desktopApiKey)
const url = beeApiUrl || apiUrl
const debugUrl = beeDebugApiUrl || apiDebugUrl
useEffect(() => {
const urlSearchParams = new URLSearchParams(window.location.search)
const newApiKey = urlSearchParams.get('v')
if (newApiKey) {
localStorage.setItem('apiKey', newApiKey)
window.location.search = ''
setDesktopApiKey(newApiKey)
}
}, [])
useEffect(() => {
try {
setBeeApi(new Bee(url))
@@ -75,6 +89,7 @@ export function Provider({
setApiUrl,
setDebugApiUrl,
lockedApiSettings,
desktopApiKey,
}}
>
{children}
+5
View File
@@ -0,0 +1,5 @@
import axios from 'axios'
export async function requestBzz(address: string): Promise<void> {
await axios.post(`https://xbzz-faucet.apyos.dev/xbzz/${address}`)
}
+39
View File
@@ -0,0 +1,39 @@
import axios from 'axios'
import { getJson, postJson } from './net'
interface DesktopStatus {
status: 0 | 1 | 2
address: string | null
// eslint-disable-next-line @typescript-eslint/no-explicit-any
config: Record<string, any>
}
export async function getDesktopStatus(): Promise<DesktopStatus> {
const response = await getJson(`http://${getDesktopHost()}/status`)
return response as DesktopStatus
}
export async function getGasFromFaucet(address: string): Promise<void> {
await axios.post(`http://getxdai.co/${address}/0.1`)
}
export async function upgradeToLightNode(rpcProvider: string): Promise<void> {
await updateDesktopConfiguration({
'chain-enable': true,
'swap-enable': true,
'swap-endpoint': rpcProvider,
})
}
async function updateDesktopConfiguration(values: Record<string, unknown>): Promise<void> {
await postJson(`http://${getDesktopHost()}/config`, values)
}
export async function restartBeeNode(): Promise<void> {
await postJson(`http://${getDesktopHost()}/restart`)
}
function getDesktopHost(): string {
return window.location.host
}
+33
View File
@@ -0,0 +1,33 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import axios from 'axios'
export function getJson(url: string): Promise<Record<string, any>> {
return sendRequest(url, 'GET')
}
export function postJson(url: string, data?: Record<string, any>): Promise<Record<string, unknown>> {
return sendRequest(url, 'POST', data)
}
async function sendRequest(
url: string,
method: 'GET' | 'POST',
data?: Record<string, unknown>,
): Promise<Record<string, any>> {
const authorization = localStorage.getItem('apiKey')
if (!authorization) {
throw Error('API key not found in local storage')
}
const headers = {
authorization,
}
const response = await axios(url, {
method,
headers,
data,
})
return response.data
}
+66
View File
@@ -0,0 +1,66 @@
import { debounce } from '@material-ui/core'
import axios from 'axios'
import { Contract, providers } from 'ethers'
const PROVIDER = 'https://gno.getblock.io/mainnet/?api_key=d7b92d96-9784-49a8-a800-b3edd1647fc7'
async function eth_getBalance(address: string): Promise<string> {
if (!address.startsWith('0x')) {
address = `0x${address}`
}
const response = await axios(PROVIDER, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
data: {
jsonrpc: '2.0',
method: 'eth_getBalance',
params: [address, 'latest'],
id: 1,
},
})
return response.data.result
}
const partialERC20tokenABI = [
{
constant: true,
inputs: [
{
name: '_owner',
type: 'address',
},
],
name: 'balanceOf',
outputs: [
{
name: 'balance',
type: 'uint256',
},
],
payable: false,
type: 'function',
},
]
const provider = new providers.JsonRpcProvider(PROVIDER)
async function eth_getBalanceERC20(
address: string,
tokenAddress = '0xdbf3ea6f5bee45c02255b2c26a16f300502f68da',
): Promise<string> {
if (!address.startsWith('0x')) {
address = `0x${address}`
}
const contract = new Contract(tokenAddress, partialERC20tokenABI, provider)
const balance = await contract.balanceOf(address)
return balance.toString()
}
export const Rpc = {
eth_getBalance: debounce(eth_getBalance, 1_000),
eth_getBalanceERC20: debounce(eth_getBalanceERC20, 1_000),
}