feat: sync and update with all changes from fork (#720)
* feat: sync and update with all changes from fork * refactor: extract clipboard copy logic into custom hook * fix: correct spelling of DEFAULT_REFRESH_FREQUENCY_MS in Stamps and WalletBalance providers * refactor(ui-tests): replace fixed sleeps with condition-based waits * fix: handle null values for size and granteeCount in infoGroups * fix(lint): add newline at end of file in useClipboardCopy hook * fix(ui-tests): page.goto URL * refactor: update import paths for useClipboardCopy --------- Co-authored-by: Ferenc Sárai <sarai.ferenc@gmail.com>
This commit is contained in:
@@ -1,10 +0,0 @@
|
||||
export class AuthError extends Error {
|
||||
constructor() {
|
||||
super('Bad API key')
|
||||
this.name = 'AuthError'
|
||||
}
|
||||
}
|
||||
|
||||
export function isAuthError(error: unknown): error is AuthError {
|
||||
return error instanceof Error && error.name === 'AuthError'
|
||||
}
|
||||
+15
-1
@@ -1,3 +1,9 @@
|
||||
import { EthAddress } from '@ethersphere/bee-js'
|
||||
import { getAddress, JsonRpcProvider, Networkish } from 'ethers'
|
||||
|
||||
export const GNOIS_NETWORK_ID = 100
|
||||
export const GnosisNetwork: Networkish = { chainId: GNOIS_NETWORK_ID, name: 'gnosis', ensAddress: undefined }
|
||||
|
||||
const chains = [
|
||||
{
|
||||
name: 'Ethereum Mainnet',
|
||||
@@ -21,10 +27,18 @@ const chains = [
|
||||
},
|
||||
{
|
||||
name: 'Gnosis Chain',
|
||||
chainId: 100,
|
||||
chainId: GNOIS_NETWORK_ID,
|
||||
},
|
||||
]
|
||||
|
||||
export function chainIdToName(chainId: number): string {
|
||||
return chains.find(record => record.chainId === chainId)?.name || 'Unknown'
|
||||
}
|
||||
|
||||
export function ethAddressString(address: EthAddress | string): string {
|
||||
return typeof address === 'string' ? getAddress(address) : getAddress(address.toHex())
|
||||
}
|
||||
|
||||
export function newGnosisProvider(url: string): JsonRpcProvider {
|
||||
return new JsonRpcProvider(url, GnosisNetwork, { staticNetwork: true })
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { DAI } from '@ethersphere/bee-js'
|
||||
import axios from 'axios'
|
||||
|
||||
import { BEE_DESKTOP_LATEST_RELEASE_PAGE_API } from '../constants'
|
||||
|
||||
import { getJson, postJson } from './net'
|
||||
|
||||
export interface BeeConfig {
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { isAuthError } from './AuthError'
|
||||
export class AuthError extends Error {
|
||||
constructor() {
|
||||
super('Bad API key')
|
||||
this.name = 'AuthError'
|
||||
}
|
||||
}
|
||||
|
||||
export function isAuthError(error: unknown): error is AuthError {
|
||||
return error instanceof Error && error.name === 'AuthError'
|
||||
}
|
||||
|
||||
export class SwapError extends Error {
|
||||
snackbarMessage: string
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
import { isSupportedAudioType } from './audio'
|
||||
import { isSupportedImageType } from './image'
|
||||
import { isSupportedVideoType } from './video'
|
||||
import { isSupportedAudioType } from './audio'
|
||||
|
||||
const indexHtmls = ['index.html', 'index.htm']
|
||||
|
||||
@@ -134,5 +134,6 @@ export function packageFile(file: FilePath, pathOverwrite?: string): FilePath {
|
||||
slice: (start: number, end: number) => file.slice(start, end),
|
||||
text: file.text,
|
||||
arrayBuffer: async () => await file.arrayBuffer(),
|
||||
bytes: file.bytes,
|
||||
}
|
||||
}
|
||||
|
||||
+32
-16
@@ -1,10 +1,15 @@
|
||||
import { BatchId, Bee, NULL_TOPIC, Reference } from '@ethersphere/bee-js'
|
||||
import { Wallet } from 'ethers'
|
||||
import { uuidV4, waitUntilStampUsable } from '.'
|
||||
import { BatchId, Bee, NULL_TOPIC, PrivateKey, Reference } from '@ethersphere/bee-js'
|
||||
import { randomBytes, Wallet } from 'ethers'
|
||||
|
||||
import { Identity, IdentityType } from '../providers/Feeds'
|
||||
|
||||
import { LocalStorageKeys } from './localStorage'
|
||||
import { uuidV4, waitUntilStampUsable } from '.'
|
||||
|
||||
export function generateWallet(): Wallet {
|
||||
return Wallet.createRandom()
|
||||
const privateKey = randomBytes(PrivateKey.LENGTH).toString()
|
||||
|
||||
return new Wallet(privateKey)
|
||||
}
|
||||
|
||||
export function persistIdentity(identities: Identity[], identity: Identity): void {
|
||||
@@ -14,11 +19,11 @@ export function persistIdentity(identities: Identity[], identity: Identity): voi
|
||||
identities.splice(existingIndex, 1)
|
||||
}
|
||||
identities.unshift(identity)
|
||||
localStorage.setItem('feeds', JSON.stringify(identities))
|
||||
localStorage.setItem(LocalStorageKeys.feeds, JSON.stringify(identities))
|
||||
}
|
||||
|
||||
export function persistIdentitiesWithoutUpdate(identities: Identity[]): void {
|
||||
localStorage.setItem('feeds', JSON.stringify(identities))
|
||||
localStorage.setItem(LocalStorageKeys.feeds, JSON.stringify(identities))
|
||||
}
|
||||
|
||||
export async function convertWalletToIdentity(
|
||||
@@ -27,16 +32,17 @@ export async function convertWalletToIdentity(
|
||||
name: string,
|
||||
password?: string,
|
||||
): Promise<Identity> {
|
||||
if (type === 'V3' && !password) {
|
||||
if (type === IdentityType.V3 && !password) {
|
||||
throw Error('V3 passwords require password')
|
||||
}
|
||||
|
||||
const identityString = type === 'PRIVATE_KEY' ? identity.privateKey : await identity.encrypt(password as string)
|
||||
const identityString =
|
||||
type === IdentityType.PrivateKey ? identity.privateKey : await identity.encrypt(password as string)
|
||||
|
||||
return {
|
||||
uuid: uuidV4(),
|
||||
name,
|
||||
type: password ? 'V3' : 'PRIVATE_KEY',
|
||||
type: password ? IdentityType.V3 : IdentityType.PrivateKey,
|
||||
address: identity.address,
|
||||
identity: identityString,
|
||||
}
|
||||
@@ -44,26 +50,26 @@ export async function convertWalletToIdentity(
|
||||
|
||||
export async function importIdentity(name: string, data: string): Promise<Identity | null> {
|
||||
if (data.length === 64) {
|
||||
const wallet = await getWallet('PRIVATE_KEY', data)
|
||||
const wallet = await getWallet(IdentityType.PrivateKey, data)
|
||||
|
||||
return {
|
||||
uuid: uuidV4(),
|
||||
name,
|
||||
type: 'PRIVATE_KEY',
|
||||
type: IdentityType.PrivateKey,
|
||||
identity: data,
|
||||
address: wallet.address,
|
||||
}
|
||||
}
|
||||
|
||||
if (data.length === 66 && data.toLowerCase().startsWith('0x')) {
|
||||
const wallet = await getWallet('PRIVATE_KEY', data.slice(2))
|
||||
const wallet = await getWallet(IdentityType.PrivateKey, data.slice(2))
|
||||
|
||||
return { uuid: uuidV4(), name, type: 'PRIVATE_KEY', identity: data, address: wallet.address }
|
||||
return { uuid: uuidV4(), name, type: IdentityType.PrivateKey, identity: data, address: wallet.address }
|
||||
}
|
||||
try {
|
||||
const { address } = JSON.parse(data)
|
||||
|
||||
return { uuid: uuidV4(), name, type: 'V3', identity: data, address }
|
||||
return { uuid: uuidV4(), name, type: IdentityType.V3, identity: data, address }
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
@@ -74,7 +80,17 @@ function getWalletFromIdentity(identity: Identity, password?: string): Promise<W
|
||||
}
|
||||
|
||||
async function getWallet(type: IdentityType, data: string, password?: string): Promise<Wallet> {
|
||||
return type === 'PRIVATE_KEY' ? new Wallet(data) : await Wallet.fromEncryptedJson(data, password as string)
|
||||
if (type === IdentityType.PrivateKey) {
|
||||
return new Wallet(data)
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
throw new Error('password is required for wallet')
|
||||
}
|
||||
|
||||
const w = await Wallet.fromEncryptedJson(data, password)
|
||||
|
||||
return new Wallet(w.privateKey)
|
||||
}
|
||||
|
||||
export async function updateFeed(
|
||||
@@ -93,5 +109,5 @@ export async function updateFeed(
|
||||
const writer = beeApi.makeFeedWriter(NULL_TOPIC, wallet.privateKey)
|
||||
|
||||
await waitUntilStampUsable(stamp, beeApi)
|
||||
await writer.upload(stamp, hash)
|
||||
await writer.uploadReference(stamp, hash)
|
||||
}
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
import { extractSwarmHash, extractSwarmCid, extractEns, recognizeEnsOrSwarmHash } from './index'
|
||||
|
||||
interface TestObject {
|
||||
input: string
|
||||
expectedOutput: string | undefined
|
||||
}
|
||||
|
||||
const correctHashes: TestObject[] = [
|
||||
// non-encrypted
|
||||
{
|
||||
input: 'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||
expectedOutput: 'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||
},
|
||||
{
|
||||
input: 'http://gateway.org/bzz/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||
expectedOutput: 'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||
},
|
||||
{
|
||||
input: 'https://gateway.org/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||
expectedOutput: 'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||
},
|
||||
{
|
||||
input: 'http://gateway.org/bzz/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f/',
|
||||
expectedOutput: 'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||
},
|
||||
{
|
||||
input: 'https://gateway.org/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f/',
|
||||
expectedOutput: 'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||
},
|
||||
// encrypted
|
||||
{
|
||||
input:
|
||||
'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||
expectedOutput:
|
||||
'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||
},
|
||||
{
|
||||
input:
|
||||
'http://gateway.org/bzz/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||
expectedOutput:
|
||||
'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||
},
|
||||
{
|
||||
input:
|
||||
'https://gateway.org/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||
expectedOutput:
|
||||
'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||
},
|
||||
{
|
||||
input:
|
||||
'http://gateway.org/bzz/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f/',
|
||||
expectedOutput:
|
||||
'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||
},
|
||||
{
|
||||
input:
|
||||
'https://gateway.org/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f/',
|
||||
expectedOutput:
|
||||
'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||
},
|
||||
]
|
||||
|
||||
const wrongHashes: string[] = [
|
||||
// one character too long
|
||||
'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fa',
|
||||
'http://gateway.org/bzz/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fa',
|
||||
'https://gateway.org/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fa',
|
||||
'http://gateway.org/bzz/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fa/',
|
||||
'https://gateway.org/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fa/',
|
||||
|
||||
// a bit shorter
|
||||
'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d',
|
||||
]
|
||||
|
||||
describe('extractSwarmHash', () => {
|
||||
test('should correctly extract hash', () => {
|
||||
correctHashes.forEach(({ input, expectedOutput }) => {
|
||||
const hash = extractSwarmHash(input)
|
||||
expect(hash).toBe(expectedOutput)
|
||||
})
|
||||
})
|
||||
test('should not extract hash from incorrect inputs', () => {
|
||||
wrongHashes.forEach(input => {
|
||||
const hash = extractSwarmHash(input)
|
||||
expect(hash).toBe(undefined)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const correctCids: TestObject[] = [
|
||||
{
|
||||
input: 'https://bah5acgza5afd34lfvo7sowxfjahj4ujeduxgg2ge5u3zo4kcjlzjzi23fhka.bzz.link',
|
||||
expectedOutput: 'e80a3df165abbf275ae5480e9e51241d2e6368c4ed379771424af29ca35b29d4',
|
||||
},
|
||||
{
|
||||
input: 'https://bah5acgza2lzgtqfztvn3xtnzhv7qvbmblljd7bi52l5jiueretcad55vookq.bzz.link',
|
||||
expectedOutput: 'd2f269c0b99d5bbbcdb93d7f0a85815ad23f851dd2fa94509124c401f7b57395',
|
||||
},
|
||||
]
|
||||
const wrongCids: string[] = [
|
||||
'https://bah5acgza5afd34lfvo7sowxfjahj4ujeduxgg2ge5u3zo4kcjlzjzi23fhka.another.domain',
|
||||
'http://bah5acgza2lzgtqfztvn3xtnzhv7qvbmblljd7bi52l5jiueretcad55vookq.bzz.link',
|
||||
'https://not_cid.bzz.link',
|
||||
'https://bah5acgza5afd34lfvo7sowxfjahj4ujeduxgg2ge5u3zo4kcjlzjzi23fhka.subdomain.bzz.link',
|
||||
'https://bah5acgza5afd34lfvo7sowxfjahj4ujeduxgg2ge5u3zo4kcjlzjzi23fhka.subdomain.bzz.link',
|
||||
'https://bah5acgza2lzgtqfztvn3xtnzhv7qvbmblljd7bi52l5jiueretcad55vook.bzz.link',
|
||||
'https://aah5acgza2lzgtqfztvn3xtnzhv7qvbmblljd7bi52l5jiueretcad55vookq.bzz.link',
|
||||
]
|
||||
|
||||
describe('extractSwarmCid', () => {
|
||||
test('should correctly extract hash', () => {
|
||||
correctCids.forEach(({ input, expectedOutput }) => {
|
||||
const hash = extractSwarmCid(input)
|
||||
expect(hash).toBe(expectedOutput)
|
||||
})
|
||||
})
|
||||
test('should not extract cid from incorrect urls', () => {
|
||||
wrongCids.forEach(url => {
|
||||
const hash = extractSwarmCid(url)
|
||||
expect(hash).toBe(undefined)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const correctEns: TestObject[] = [
|
||||
{
|
||||
input: 'test.eth',
|
||||
expectedOutput: 'test.eth',
|
||||
},
|
||||
{
|
||||
input: 't-est.eth',
|
||||
expectedOutput: 't-est.eth',
|
||||
},
|
||||
{
|
||||
input: 'http://test.eth/whatever',
|
||||
expectedOutput: 'test.eth',
|
||||
},
|
||||
{
|
||||
input: 'https://alice.test.eth?whatever',
|
||||
expectedOutput: 'alice.test.eth',
|
||||
},
|
||||
{
|
||||
input: 'swarm.example.eth/?id=1&page=2',
|
||||
expectedOutput: 'swarm.example.eth',
|
||||
},
|
||||
{
|
||||
input: 'http://swarm.example.eth#up',
|
||||
expectedOutput: 'swarm.example.eth',
|
||||
},
|
||||
{
|
||||
input: 'http://site.eth:8008',
|
||||
expectedOutput: 'site.eth',
|
||||
},
|
||||
]
|
||||
|
||||
const wrongEns: string[] = ['http://test.ethereum/whatever']
|
||||
|
||||
describe('extractEns', () => {
|
||||
test('should correctly extract ens domain', () => {
|
||||
correctEns.forEach(({ input, expectedOutput }) => {
|
||||
const hash = extractEns(input)
|
||||
expect(hash).toBe(expectedOutput)
|
||||
})
|
||||
})
|
||||
test('should not extract ens from incorrect inputs', () => {
|
||||
wrongEns.forEach(url => {
|
||||
const hash = extractEns(url)
|
||||
expect(hash).toBe(undefined)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('recognizeEnsOrSwarmHash', () => {
|
||||
test('should correctly extract hash or ens', () => {
|
||||
;[...correctHashes, ...correctCids, ...correctEns].forEach(({ input, expectedOutput }) => {
|
||||
const hash = recognizeEnsOrSwarmHash(input)
|
||||
expect(hash).toBe(expectedOutput)
|
||||
})
|
||||
})
|
||||
test('should not extract hash or ens from incorrect inputs but instead return them', () => {
|
||||
;[...wrongHashes, ...wrongCids, ...wrongEns].forEach(url => {
|
||||
const hash = recognizeEnsOrSwarmHash(url)
|
||||
expect(hash).toBe(url)
|
||||
})
|
||||
})
|
||||
})
|
||||
+17
-13
@@ -1,5 +1,6 @@
|
||||
import { BatchId, Bee, PostageBatch, Reference } from '@ethersphere/bee-js'
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
|
||||
import { BZZ_LINK_DOMAIN } from '../constants'
|
||||
|
||||
/**
|
||||
@@ -90,21 +91,25 @@ export function unwrapPromiseSettlements<T>(
|
||||
* If all attempts fail, then this `Promise<T>` also rejects.
|
||||
*/
|
||||
export function makeRetriablePromise<T>(fn: () => Promise<T>, maxRetries = 3, delayMs = 1000): Promise<T> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
for (let tries = 0; tries < maxRetries; tries++) {
|
||||
try {
|
||||
const results = await fn()
|
||||
resolve(results)
|
||||
return new Promise((resolve, reject) => {
|
||||
const attempt = async () => {
|
||||
for (let tries = 0; tries < maxRetries; tries++) {
|
||||
try {
|
||||
const results = await fn()
|
||||
resolve(results)
|
||||
|
||||
return
|
||||
} catch (error) {
|
||||
if (tries < maxRetries - 1) {
|
||||
await sleepMs(delayMs)
|
||||
} else {
|
||||
reject(error)
|
||||
return
|
||||
} catch (error) {
|
||||
if (tries < maxRetries - 1) {
|
||||
await sleepMs(delayMs)
|
||||
} else {
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attempt()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -130,7 +135,7 @@ export function extractSwarmCid(s: string): string | undefined {
|
||||
const cid = matches[1]
|
||||
try {
|
||||
return new Reference(cid).toHex()
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -211,7 +216,6 @@ interface Options {
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
// TODO: merge this with FM component getUsableStamps()
|
||||
export function waitUntilStampUsable(batchId: BatchId | string, bee: Bee, options?: Options): Promise<PostageBatch> {
|
||||
return waitForStamp(batchId, bee, options)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
import { shortenHash } from './hash'
|
||||
|
||||
export enum HISTORY_KEYS {
|
||||
UPLOAD_HISTORY = 'UPLOAD_HISTORY',
|
||||
DOWNLOAD_HISTORY = 'DOWNLOAD_HISTORY',
|
||||
}
|
||||
export const LocalStorageKeys = {
|
||||
providerUrl: 'json-rpc-provider',
|
||||
apiHost: 'api_host',
|
||||
apiKey: 'apiKey',
|
||||
fmClickStorage: 'fm_click_count_v1',
|
||||
fmSurveyTriggered: 'fm_survey_triggered_v1',
|
||||
fmSortKey: 'fm.sort.v1',
|
||||
fmPrivateKey: 'privateKey',
|
||||
feeds: 'feeds',
|
||||
depositWallet: 'deposit-wallet',
|
||||
giftWallets: 'gift-wallets',
|
||||
invitation: 'invitation',
|
||||
uploadHistory: 'UPLOAD_HISTORY',
|
||||
downloadHistory: 'DOWNLOAD_HISTORY',
|
||||
} as const
|
||||
|
||||
export type HISTORY_KEYS = typeof LocalStorageKeys.uploadHistory | typeof LocalStorageKeys.downloadHistory
|
||||
|
||||
export interface HistoryItem {
|
||||
createdAt: number
|
||||
@@ -11,7 +24,7 @@ export interface HistoryItem {
|
||||
hash: string
|
||||
}
|
||||
|
||||
export function putHistory(key: string, hash: string, name: string): void {
|
||||
export function putHistory(key: HISTORY_KEYS, hash: string, name: string): void {
|
||||
const history = getHistorySafe(key)
|
||||
|
||||
const existingIndex = history.findIndex(x => x.hash === hash)
|
||||
@@ -32,7 +45,7 @@ export function putHistory(key: string, hash: string, name: string): void {
|
||||
localStorage.setItem(key, JSON.stringify(history))
|
||||
}
|
||||
|
||||
export function getHistorySafe(key: string): HistoryItem[] {
|
||||
export function getHistorySafe(key: HISTORY_KEYS): HistoryItem[] {
|
||||
const items = localStorage.getItem(key)
|
||||
|
||||
if (!items) {
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { Bee, DownloadOptions } from '@ethersphere/bee-js'
|
||||
import { BeeRequestOptions, MantarayNode, NULL_ADDRESS } from '@ethersphere/bee-js'
|
||||
|
||||
export async function loadManifest(
|
||||
beeApi: Bee,
|
||||
hash: string,
|
||||
options?: DownloadOptions,
|
||||
requestOptions?: BeeRequestOptions,
|
||||
): Promise<MantarayNode> {
|
||||
let manifest = await MantarayNode.unmarshal(beeApi, hash, options, requestOptions)
|
||||
await manifest.loadRecursively(beeApi, options, requestOptions)
|
||||
|
||||
// If the manifest is a feed, resolve it and overwrite the manifest
|
||||
const feed = await manifest.resolveFeed(beeApi, requestOptions)
|
||||
await feed.ifPresentAsync(async feedUpdate => {
|
||||
manifest = MantarayNode.unmarshalFromData(feedUpdate.payload.toUint8Array(), NULL_ADDRESS)
|
||||
await manifest.loadRecursively(beeApi, options, requestOptions)
|
||||
})
|
||||
|
||||
return manifest
|
||||
}
|
||||
+7
-4
@@ -1,12 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import axios from 'axios'
|
||||
import { AuthError } from './AuthError'
|
||||
|
||||
import { AuthError } from './errors'
|
||||
import { LocalStorageKeys } from './localStorage'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function getJson<T extends Record<string, any>>(url: string): Promise<T> {
|
||||
return sendRequest(url, 'GET') as Promise<T>
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function postJson<T extends Record<string, any>>(url: string, data?: Record<string, any>): Promise<T> {
|
||||
return sendRequest(url, 'POST', data) as Promise<T>
|
||||
}
|
||||
@@ -15,8 +17,9 @@ export async function sendRequest(
|
||||
url: string,
|
||||
method: 'GET' | 'POST',
|
||||
data?: Record<string, unknown>,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): Promise<Record<string, any>> {
|
||||
const authorization = localStorage.getItem('apiKey')
|
||||
const authorization = localStorage.getItem(LocalStorageKeys.apiKey)
|
||||
|
||||
if (!authorization) {
|
||||
throw Error('API key not found in local storage. Please reopen via Swarm Desktop > Open Web UI.')
|
||||
|
||||
+86
-37
@@ -1,27 +1,25 @@
|
||||
import { debounce } from '@material-ui/core'
|
||||
import { BZZ, DAI, EthAddress, PrivateKey } from '@ethersphere/bee-js'
|
||||
import { BigNumber as BN, Contract, providers, Wallet } from 'ethers'
|
||||
import { BZZ_TOKEN_ADDRESS, bzzABI } from './bzz-abi'
|
||||
import { debounce } from '@mui/material'
|
||||
import { Contract, JsonRpcProvider, TransactionReceipt, TransactionResponse, Wallet } from 'ethers'
|
||||
|
||||
const NETWORK_ID = 100
|
||||
import { BZZ_TOKEN_ADDRESS, bzzABI } from './bzzAbi'
|
||||
import { ethAddressString, newGnosisProvider } from './chain'
|
||||
|
||||
async function getNetworkChainId(url: string): Promise<number> {
|
||||
const provider = new providers.JsonRpcProvider(url, NETWORK_ID)
|
||||
await provider.ready
|
||||
async function getNetworkChainId(url: string): Promise<bigint> {
|
||||
const provider = newGnosisProvider(url)
|
||||
const network = await provider.getNetwork()
|
||||
|
||||
return network.chainId
|
||||
}
|
||||
|
||||
async function eth_getBalance(address: EthAddress | string, provider: providers.JsonRpcProvider): Promise<DAI> {
|
||||
address = new EthAddress(address)
|
||||
|
||||
const balance = await provider.getBalance(address.toHex())
|
||||
async function eth_getBalance(address: EthAddress | string, provider: JsonRpcProvider): Promise<DAI> {
|
||||
const addressString = ethAddressString(address)
|
||||
const balance = await provider.getBalance(addressString)
|
||||
|
||||
return DAI.fromWei(balance.toString())
|
||||
}
|
||||
|
||||
async function eth_getBlockByNumber(provider: providers.JsonRpcProvider): Promise<string> {
|
||||
async function eth_getBlockByNumber(provider: JsonRpcProvider): Promise<string> {
|
||||
const blockNumber = await provider.getBlockNumber()
|
||||
|
||||
return blockNumber.toString()
|
||||
@@ -29,56 +27,75 @@ async function eth_getBlockByNumber(provider: providers.JsonRpcProvider): Promis
|
||||
|
||||
async function eth_getBalanceERC20(
|
||||
address: EthAddress | string,
|
||||
provider: providers.JsonRpcProvider,
|
||||
provider: JsonRpcProvider,
|
||||
tokenAddress = BZZ_TOKEN_ADDRESS,
|
||||
): Promise<BZZ> {
|
||||
address = new EthAddress(address)
|
||||
|
||||
const addressString = ethAddressString(address)
|
||||
const contract = new Contract(tokenAddress, bzzABI, provider)
|
||||
const balance = await contract.balanceOf(address.toHex())
|
||||
// Use staticCall directly to bypass argument resolution
|
||||
const balance = await contract.balanceOf.staticCall(addressString)
|
||||
|
||||
return BZZ.fromPLUR(balance.toString())
|
||||
}
|
||||
|
||||
interface TransferResponse {
|
||||
transaction: providers.TransactionResponse
|
||||
receipt: providers.TransactionReceipt
|
||||
transaction: TransactionResponse
|
||||
receipt: TransactionReceipt
|
||||
}
|
||||
|
||||
export async function estimateNativeTransferTransactionCost(
|
||||
privateKey: PrivateKey | string,
|
||||
jsonRpcProvider: string,
|
||||
jsonRpcProviderUrl: string,
|
||||
): Promise<{ gasPrice: DAI; totalCost: DAI }> {
|
||||
privateKey = new PrivateKey(privateKey)
|
||||
|
||||
const signer = await makeReadySigner(privateKey, jsonRpcProvider)
|
||||
const gasLimit = '21000'
|
||||
const gasPrice = await signer.getGasPrice()
|
||||
const signer = await makeReadySigner(privateKey, jsonRpcProviderUrl)
|
||||
|
||||
return { gasPrice: DAI.fromWei(gasPrice.toString()), totalCost: DAI.fromWei(gasPrice.mul(gasLimit).toString()) }
|
||||
if (!signer.provider) {
|
||||
throw new Error('Signer provider is invalid!')
|
||||
}
|
||||
|
||||
const gasLimit = BigInt(21000)
|
||||
const feeData = await signer.provider.getFeeData()
|
||||
const gasPrice = feeData.gasPrice || BigInt(0)
|
||||
|
||||
return {
|
||||
gasPrice: DAI.fromWei(gasPrice.toString()),
|
||||
totalCost: DAI.fromWei((gasPrice * gasLimit).toString()),
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendNativeTransaction(
|
||||
privateKey: PrivateKey | string,
|
||||
to: EthAddress | string,
|
||||
value: DAI,
|
||||
jsonRpcProvider: string,
|
||||
jsonRpcProviderUrl: string,
|
||||
externalGasPrice?: DAI,
|
||||
): Promise<TransferResponse> {
|
||||
privateKey = new PrivateKey(privateKey)
|
||||
to = new EthAddress(to)
|
||||
|
||||
const signer = await makeReadySigner(privateKey, jsonRpcProvider)
|
||||
const gasPrice = externalGasPrice ?? DAI.fromWei((await signer.getGasPrice()).toString())
|
||||
const signer = await makeReadySigner(privateKey, jsonRpcProviderUrl)
|
||||
|
||||
if (!signer.provider) {
|
||||
throw new Error('Signer provider is invalid!')
|
||||
}
|
||||
|
||||
const feedData = await signer.provider.getFeeData()
|
||||
const gasPrice = externalGasPrice ?? DAI.fromWei(feedData.gasPrice?.toString() || '0')
|
||||
const transaction = await signer.sendTransaction({
|
||||
to: to.toHex(),
|
||||
value: BN.from(value.toWeiString()),
|
||||
gasPrice: BN.from(gasPrice.toWeiString()),
|
||||
gasLimit: BN.from(21000),
|
||||
value: BigInt(value.toWeiString()),
|
||||
gasPrice: BigInt(gasPrice.toWeiString()),
|
||||
gasLimit: BigInt(21000),
|
||||
type: 0,
|
||||
})
|
||||
const receipt = await transaction.wait(1)
|
||||
|
||||
if (!receipt) {
|
||||
throw new Error('Invalid receipt!')
|
||||
}
|
||||
|
||||
return { transaction, receipt }
|
||||
}
|
||||
|
||||
@@ -86,29 +103,61 @@ export async function sendBzzTransaction(
|
||||
privateKey: PrivateKey | string,
|
||||
to: EthAddress | string,
|
||||
value: BZZ,
|
||||
jsonRpcProvider: string,
|
||||
jsonRpcProviderUrl: string,
|
||||
): Promise<TransferResponse> {
|
||||
privateKey = new PrivateKey(privateKey)
|
||||
to = new EthAddress(to)
|
||||
|
||||
const signer = await makeReadySigner(privateKey, jsonRpcProvider)
|
||||
const gasPrice = await signer.getGasPrice()
|
||||
const signer = await makeReadySigner(privateKey, jsonRpcProviderUrl)
|
||||
|
||||
if (!signer.provider) {
|
||||
throw new Error('Signer provider is invalid!')
|
||||
}
|
||||
|
||||
const feeData = await signer.provider.getFeeData()
|
||||
const gasPrice = feeData.gasPrice || BigInt(0)
|
||||
const bzz = new Contract(BZZ_TOKEN_ADDRESS, bzzABI, signer)
|
||||
const transaction = await bzz.transfer(to, value, { gasPrice })
|
||||
const receipt = await transaction.wait(1)
|
||||
|
||||
if (!receipt) {
|
||||
throw new Error('Invalid receipt!')
|
||||
}
|
||||
|
||||
return { transaction, receipt }
|
||||
}
|
||||
|
||||
async function makeReadySigner(privateKey: PrivateKey, jsonRpcProvider: string) {
|
||||
const provider = new providers.JsonRpcProvider(jsonRpcProvider, NETWORK_ID)
|
||||
await provider.ready
|
||||
const signer = new Wallet(privateKey.toUint8Array(), provider)
|
||||
async function makeReadySigner(privateKey: PrivateKey, jsonRpcProviderUrl: string) {
|
||||
const provider = newGnosisProvider(jsonRpcProviderUrl)
|
||||
await provider.getNetwork()
|
||||
const signer = new Wallet(privateKey.toString(), provider)
|
||||
|
||||
return signer
|
||||
}
|
||||
|
||||
export const Rpc = {
|
||||
export interface Rpc {
|
||||
getNetworkChainId: (url: string) => Promise<bigint>
|
||||
sendNativeTransaction: (
|
||||
privateKey: PrivateKey | string,
|
||||
to: EthAddress | string,
|
||||
value: DAI,
|
||||
jsonRpcProviderUrl: string,
|
||||
externalGasPrice?: DAI,
|
||||
) => Promise<TransferResponse>
|
||||
sendBzzTransaction: (
|
||||
privateKey: PrivateKey | string,
|
||||
to: EthAddress | string,
|
||||
value: BZZ,
|
||||
jsonRpcProviderUrl: string,
|
||||
) => Promise<TransferResponse>
|
||||
_eth_getBalance: (address: EthAddress | string, provider: JsonRpcProvider) => Promise<DAI>
|
||||
_eth_getBalanceERC20: (address: EthAddress | string, provider: JsonRpcProvider, tokenAddress?: string) => Promise<BZZ>
|
||||
eth_getBalance: (address: EthAddress | string, provider: JsonRpcProvider) => Promise<DAI>
|
||||
eth_getBalanceERC20: (address: EthAddress | string, provider: JsonRpcProvider, tokenAddress: string) => Promise<BZZ>
|
||||
eth_getBlockByNumber: (provider: JsonRpcProvider) => Promise<string>
|
||||
}
|
||||
|
||||
export const RPC: Rpc = {
|
||||
getNetworkChainId,
|
||||
sendNativeTransaction,
|
||||
sendBzzTransaction,
|
||||
|
||||
+20
-19
@@ -1,25 +1,26 @@
|
||||
import { BZZ, DAI, EthAddress } from '@ethersphere/bee-js'
|
||||
import { providers, Wallet } from 'ethers'
|
||||
import { estimateNativeTransferTransactionCost, Rpc } from './rpc'
|
||||
import { JsonRpcProvider, Wallet } from 'ethers'
|
||||
|
||||
import { estimateNativeTransferTransactionCost, RPC } from './rpc'
|
||||
|
||||
export class WalletAddress {
|
||||
private constructor(
|
||||
public address: string,
|
||||
public bzz: BZZ,
|
||||
public dai: DAI,
|
||||
public provider: providers.JsonRpcProvider,
|
||||
public provider: JsonRpcProvider,
|
||||
) {}
|
||||
|
||||
static async make(address: string, provider: providers.JsonRpcProvider): Promise<WalletAddress> {
|
||||
const bzz = await Rpc._eth_getBalanceERC20(address, provider)
|
||||
const dai = await Rpc._eth_getBalance(address, provider)
|
||||
static async make(address: string, provider: JsonRpcProvider): Promise<WalletAddress> {
|
||||
const bzz = await RPC._eth_getBalanceERC20(address, provider)
|
||||
const dai = await RPC._eth_getBalance(address, provider)
|
||||
|
||||
return new WalletAddress(address, bzz, dai, provider)
|
||||
}
|
||||
|
||||
public async refresh(): Promise<WalletAddress> {
|
||||
this.bzz = await Rpc._eth_getBalanceERC20(this.address, this.provider)
|
||||
this.dai = await Rpc._eth_getBalance(this.address, this.provider)
|
||||
this.bzz = await RPC._eth_getBalanceERC20(this.address, this.provider)
|
||||
this.dai = await RPC._eth_getBalance(this.address, this.provider)
|
||||
|
||||
return this
|
||||
}
|
||||
@@ -33,43 +34,43 @@ export class ResolvedWallet {
|
||||
public wallet: Wallet,
|
||||
public bzz: BZZ,
|
||||
public dai: DAI,
|
||||
public provider: providers.JsonRpcProvider,
|
||||
public provider: JsonRpcProvider,
|
||||
) {
|
||||
this.address = wallet.address
|
||||
this.privateKey = wallet.privateKey
|
||||
}
|
||||
|
||||
static async make(privateKeyOrWallet: string | Wallet, provider: providers.JsonRpcProvider): Promise<ResolvedWallet> {
|
||||
static async make(privateKeyOrWallet: string | Wallet, provider: JsonRpcProvider): Promise<ResolvedWallet> {
|
||||
const wallet =
|
||||
typeof privateKeyOrWallet === 'string' ? new Wallet(privateKeyOrWallet, provider) : privateKeyOrWallet
|
||||
const address = wallet.address
|
||||
const bzz = await Rpc._eth_getBalanceERC20(address, provider)
|
||||
const dai = await Rpc._eth_getBalance(address, provider)
|
||||
const bzz = await RPC._eth_getBalanceERC20(address, provider)
|
||||
const dai = await RPC._eth_getBalance(address, provider)
|
||||
|
||||
return new ResolvedWallet(wallet, bzz, dai, provider)
|
||||
}
|
||||
|
||||
public async refresh(): Promise<ResolvedWallet> {
|
||||
this.bzz = await Rpc._eth_getBalanceERC20(this.address, this.provider)
|
||||
this.dai = await Rpc._eth_getBalance(this.address, this.provider)
|
||||
this.bzz = await RPC._eth_getBalanceERC20(this.address, this.provider)
|
||||
this.dai = await RPC._eth_getBalance(this.address, this.provider)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public async transfer(destination: EthAddress | string, jsonRpcProvider: string): Promise<void> {
|
||||
public async transfer(destination: EthAddress | string, jsonRpcProviderUrl: string): Promise<void> {
|
||||
if (this.bzz.gt(BZZ.fromDecimalString('0.05'))) {
|
||||
await Rpc.sendBzzTransaction(this.privateKey, destination, this.bzz, jsonRpcProvider)
|
||||
await RPC.sendBzzTransaction(this.privateKey, destination, this.bzz, jsonRpcProviderUrl)
|
||||
await this.refresh()
|
||||
}
|
||||
|
||||
const { gasPrice, totalCost } = await estimateNativeTransferTransactionCost(this.privateKey, jsonRpcProvider)
|
||||
const { gasPrice, totalCost } = await estimateNativeTransferTransactionCost(this.privateKey, jsonRpcProviderUrl)
|
||||
|
||||
if (this.dai.gt(totalCost)) {
|
||||
await Rpc.sendNativeTransaction(
|
||||
await RPC.sendNativeTransaction(
|
||||
this.privateKey,
|
||||
destination,
|
||||
this.dai.minus(totalCost),
|
||||
jsonRpcProvider,
|
||||
jsonRpcProviderUrl,
|
||||
gasPrice,
|
||||
)
|
||||
await this.refresh()
|
||||
|
||||
Reference in New Issue
Block a user