From 5c00c37a336c71299ab4a04a39188c5e1d295820 Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Mon, 27 Sep 2021 23:15:42 +0800 Subject: [PATCH] chore: token db --- packages/dashboard/src/web3/context.ts | 52 +++---- .../Wallet/HideTokenConfirm.tsx | 9 +- .../src/plugins/ITO/SNSAdaptor/SwapDialog.tsx | 4 +- .../src/plugins/Wallet/database/Plugin.db.ts | 20 ++- .../src/plugins/Wallet/services/erc1155.ts | 51 +------ .../src/plugins/Wallet/services/erc20.ts | 46 +------ .../src/plugins/Wallet/services/erc721.ts | 55 +------- .../src/plugins/Wallet/services/index.ts | 3 - .../Wallet/services/wallet/database/index.ts | 1 + .../Wallet/services/wallet/database/token.ts | 127 ++++++++++++++++++ .../Wallet/services/wallet/database/wallet.ts | 90 ++++++++++--- .../plugins/Wallet/services/wallet/index.ts | 3 +- .../plugins/Wallet/services/wallet/type.ts | 23 +++- packages/maskbook/src/web3/context.ts | 51 +++---- packages/web3-shared/src/context/index.tsx | 6 +- packages/web3-shared/src/context/type.ts | 9 +- packages/web3-shared/src/hooks/index.ts | 2 - .../src/hooks/useERC20TokensPaged.ts | 24 ---- packages/web3-shared/src/hooks/useNonce.ts | 9 -- 19 files changed, 315 insertions(+), 270 deletions(-) create mode 100644 packages/maskbook/src/plugins/Wallet/services/wallet/database/token.ts delete mode 100644 packages/web3-shared/src/hooks/useERC20TokensPaged.ts delete mode 100644 packages/web3-shared/src/hooks/useNonce.ts diff --git a/packages/dashboard/src/web3/context.ts b/packages/dashboard/src/web3/context.ts index 39f654142267..21bfea32dbe1 100644 --- a/packages/dashboard/src/web3/context.ts +++ b/packages/dashboard/src/web3/context.ts @@ -1,6 +1,12 @@ import { omit, noop } from 'lodash-es' import type { Subscription } from 'use-subscription' -import { ChainId, PortfolioProvider, ProviderType } from '@masknet/web3-shared' +import { + ChainId, + ERC1155TokenDetailed, + ERC721TokenDetailed, + PortfolioProvider, + ProviderType, +} from '@masknet/web3-shared' import { ERC20TokenDetailed, EthereumTokenType, NetworkType, Web3ProviderType } from '@masknet/web3-shared' import { Messages, PluginMessages, PluginServices, Services } from '../API' @@ -47,15 +53,21 @@ export const Web3Context: Web3ProviderType = { ), walletPrimary: createSubscriptionFromAsync(getWalletPrimary, null, PluginMessages.Wallet.events.walletsUpdated.on), wallets: createSubscriptionFromAsync(getWallets, [], PluginMessages.Wallet.events.walletsUpdated.on), - erc20Tokens: createSubscriptionFromAsync(getERC20Tokens, [], PluginMessages.Wallet.events.erc20TokensUpdated.on), - addERC20Token: PluginServices.Wallet.addERC20Token, - trustERC20Token: PluginServices.Wallet.trustERC20Token, - erc20TokensCount: createSubscriptionFromAsync( - PluginServices.Wallet.getERC20TokensCount, - 0, + erc20Tokens: createSubscriptionFromAsync( + () => PluginServices.Wallet.getTokens(EthereumTokenType.ERC20), + [], PluginMessages.Wallet.events.erc20TokensUpdated.on, ), - getERC20TokensPaged, + erc721Tokens: createSubscriptionFromAsync( + () => PluginServices.Wallet.getTokens(EthereumTokenType.ERC721), + [], + PluginMessages.Wallet.events.erc721TokensUpdated.on, + ), + erc1155Tokens: createSubscriptionFromAsync( + () => PluginServices.Wallet.getTokens(EthereumTokenType.ERC1155), + [], + PluginMessages.Wallet.events.erc1155TokensUpdated.on, + ), portfolioProvider: createSubscriptionFromAsync( Services.Settings.getCurrentPortfolioDataProvider, PortfolioProvider.DEBANK, @@ -64,11 +76,9 @@ export const Web3Context: Web3ProviderType = { getAssetsList: PluginServices.Wallet.getAssetsList, getAssetsListNFT: PluginServices.Wallet.getAssetsListNFT, getAddressNamesList: PluginServices.Wallet.getAddressNames, - getERC721TokensPaged, getTransactionList: PluginServices.Wallet.getTransactionList, fetchERC20TokensFromTokenLists: Services.Ethereum.fetchERC20TokensFromTokenLists, createMnemonicWords: PluginServices.Wallet.createMnemonicWords, - getNonce: Services.Ethereum.getNonce, } export function createExternalProvider() { @@ -92,7 +102,7 @@ async function getWallets() { })) } -export async function getWalletPrimary() { +async function getWalletPrimary() { const wallet = await PluginServices.Wallet.getWalletPrimary() if (!wallet) return null return { @@ -102,26 +112,6 @@ export async function getWalletPrimary() { } } -async function getERC20Tokens() { - const raw = await PluginServices.Wallet.getERC20Tokens() - return raw.map((x) => ({ - type: EthereumTokenType.ERC20, - ...x, - })) -} - -async function getERC20TokensPaged(index: number, count: number, query?: string) { - const raw = await PluginServices.Wallet.getERC20TokensPaged(index, count, query) - return raw.map((x) => ({ - type: EthereumTokenType.ERC20, - ...x, - })) -} - -async function getERC721TokensPaged(index: number, count: number, query?: string) { - return PluginServices.Wallet.getERC721TokensPaged(index, count, query) -} - // double check function createSubscriptionFromAsync( f: () => Promise, diff --git a/packages/maskbook/src/extension/options-page/DashboardDialogs/Wallet/HideTokenConfirm.tsx b/packages/maskbook/src/extension/options-page/DashboardDialogs/Wallet/HideTokenConfirm.tsx index a54811450498..a30cabf18a7d 100644 --- a/packages/maskbook/src/extension/options-page/DashboardDialogs/Wallet/HideTokenConfirm.tsx +++ b/packages/maskbook/src/extension/options-page/DashboardDialogs/Wallet/HideTokenConfirm.tsx @@ -35,9 +35,14 @@ export function DashboardWalletHideTokenConfirmDialog( case EthereumTokenType.Native: throw new Error('Unable to hide the native token.') case EthereumTokenType.ERC20: - return WalletRPC.blockERC20Token(wallet.address, token as ERC20TokenDetailed) + return WalletRPC.updateWalletToken(wallet.address, token as ERC20TokenDetailed, { + strategy: 'block', + }) case EthereumTokenType.ERC721: - return WalletRPC.removeERC721Token(token as ERC721TokenDetailed) + return WalletRPC.removeToken( + EthereumTokenType.ERC721, + (token as ERC721TokenDetailed).contractDetailed.address, + ) default: unreachable(type) } diff --git a/packages/maskbook/src/plugins/ITO/SNSAdaptor/SwapDialog.tsx b/packages/maskbook/src/plugins/ITO/SNSAdaptor/SwapDialog.tsx index cd13cce409c1..506a67b28d92 100644 --- a/packages/maskbook/src/plugins/ITO/SNSAdaptor/SwapDialog.tsx +++ b/packages/maskbook/src/plugins/ITO/SNSAdaptor/SwapDialog.tsx @@ -216,8 +216,8 @@ export function SwapDialog(props: SwapDialogProps) { const onSwap = useCallback(async () => { await swapCallback() if (payload.token.type !== EthereumTokenType.ERC20) return - await WalletRPC.addERC20Token(payload.token) - await WalletRPC.trustERC20Token(account, payload.token) + await WalletRPC.addToken(EthereumTokenType.ERC20, payload.token) + await WalletRPC.updateWalletToken(account, payload.token, { strategy: 'trust' }) }, [swapCallback, payload.token.address]) const { setDialog: setTransactionDialog } = useRemoteControlledDialog( diff --git a/packages/maskbook/src/plugins/Wallet/database/Plugin.db.ts b/packages/maskbook/src/plugins/Wallet/database/Plugin.db.ts index 87d41b51aba2..d8144fd699c7 100644 --- a/packages/maskbook/src/plugins/Wallet/database/Plugin.db.ts +++ b/packages/maskbook/src/plugins/Wallet/database/Plugin.db.ts @@ -2,8 +2,20 @@ import { PLUGIN_IDENTIFIER } from '../constants' import { createPluginDatabase } from '../../../database/Plugin/wrap-plugin-database' import type { AddressBookChunk } from '../services/addressBook' import type { RecentTransactionChunk } from '../services/recentTransactions/database' -import type { SecretRecord, WalletRecord } from '../services/wallet/type' +import type { + ERC1155TokenRecord, + ERC20TokenRecord, + ERC721TokenRecord, + SecretRecord, + WalletRecord, +} from '../services/wallet/type' -export const PluginDB = createPluginDatabase( - PLUGIN_IDENTIFIER, -) +export const PluginDB = createPluginDatabase< + | AddressBookChunk + | RecentTransactionChunk + | WalletRecord + | SecretRecord + | ERC20TokenRecord + | ERC721TokenRecord + | ERC1155TokenRecord +>(PLUGIN_IDENTIFIER) diff --git a/packages/maskbook/src/plugins/Wallet/services/erc1155.ts b/packages/maskbook/src/plugins/Wallet/services/erc1155.ts index 4d7871bc98a1..4d2deafec40c 100644 --- a/packages/maskbook/src/plugins/Wallet/services/erc1155.ts +++ b/packages/maskbook/src/plugins/Wallet/services/erc1155.ts @@ -1,69 +1,26 @@ import { createTransaction } from '../../../database/helpers/openDB' import { createWalletDBAccess } from '../database/Wallet.db' import { WalletMessages } from '../messages' -import { assert } from '../../../utils/utils' import type { ERC1155TokenDetailed } from '@masknet/web3-shared' import { formatEthereumAddress } from '@masknet/web3-shared' -import { ERC1155TokenRecordIntoDB, getWalletByAddress, LegacyWalletRecordIntoDB } from './helpers' +import { ERC1155TokenRecordIntoDB } from './helpers' +/** @deprecated */ export async function getERC1155Tokens() { const t = createTransaction(await createWalletDBAccess(), 'readonly')('ERC1155Token', 'Wallet') return t.objectStore('ERC1155Token').getAll() } +/** @deprecated */ export async function addERC1155Token(token: ERC1155TokenDetailed) { const t = createTransaction(await createWalletDBAccess(), 'readwrite')('ERC1155Token', 'Wallet') await t.objectStore('ERC1155Token').put(ERC1155TokenRecordIntoDB(token)) WalletMessages.events.erc1155TokensUpdated.sendToAll(undefined) } +/** @deprecated */ export async function removeERC1155Token(token: PartialRequired) { const t = createTransaction(await createWalletDBAccess(), 'readwrite')('ERC1155Token', 'Wallet') await t.objectStore('ERC1155Token').delete(formatEthereumAddress(token.address)) WalletMessages.events.erc1155TokensUpdated.sendToAll(undefined) } - -export async function trustERC1155Token( - address: string, - token: PartialRequired, -) { - const t = createTransaction(await createWalletDBAccess(), 'readwrite')('ERC1155Token', 'Wallet') - const wallet = await getWalletByAddress(t, formatEthereumAddress(address)) - assert(wallet) - let updated = false - const key = `${formatEthereumAddress(token.address)}_${token.tokenId}` - if (!wallet.erc1155_token_whitelist.has(key)) { - wallet.erc1155_token_whitelist.add(key) - updated = true - } - if (wallet.erc1155_token_blacklist.has(key)) { - wallet.erc1155_token_blacklist.delete(key) - updated = true - } - if (!updated) return false - await t.objectStore('Wallet').put(LegacyWalletRecordIntoDB(wallet)) - WalletMessages.events.walletsUpdated.sendToAll(undefined) - return updated -} - -export async function blockERC1155Token( - address: string, - token: PartialRequired, -) { - const t = createTransaction(await createWalletDBAccess(), 'readwrite')('ERC1155Token', 'Wallet') - const wallet = await getWalletByAddress(t, formatEthereumAddress(address)) - assert(wallet) - let updated = false - const key = `${formatEthereumAddress(token.address)}_${token.tokenId}` - if (wallet.erc1155_token_whitelist.has(key)) { - wallet.erc1155_token_whitelist.delete(key) - updated = true - } - if (!wallet.erc1155_token_blacklist.has(key)) { - wallet.erc1155_token_blacklist.add(key) - updated = true - } - if (!updated) return - await t.objectStore('Wallet').put(LegacyWalletRecordIntoDB(wallet)) - WalletMessages.events.walletsUpdated.sendToAll(undefined) -} diff --git a/packages/maskbook/src/plugins/Wallet/services/erc20.ts b/packages/maskbook/src/plugins/Wallet/services/erc20.ts index 3e71c198ccf2..a7efd931a0e9 100644 --- a/packages/maskbook/src/plugins/Wallet/services/erc20.ts +++ b/packages/maskbook/src/plugins/Wallet/services/erc20.ts @@ -3,17 +3,18 @@ import { EthereumAddress } from 'wallet.ts' import { createTransaction } from '../../../database/helpers/openDB' import { createWalletDBAccess } from '../database/Wallet.db' import { WalletMessages } from '../messages' -import { assert } from '../../../utils/utils' -import { ERC20TokenRecordIntoDB, getWalletByAddress, LegacyWalletRecordIntoDB } from './helpers' +import { ERC20TokenRecordIntoDB } from './helpers' import type { ERC20TokenRecord } from '../database/types' import { ERC20TokenDetailed, formatEthereumAddress, isSameAddress } from '@masknet/web3-shared' import { queryTransactionPaged } from '../../../database/helpers/pagination' +/** @deprecated */ export async function getERC20TokensCount() { const t = createTransaction(await createWalletDBAccess(), 'readonly')('ERC20Token', 'Wallet') return t.objectStore('ERC20Token').count() } +/** @deprecated */ export async function getERC20Tokens() { const t = createTransaction(await createWalletDBAccess(), 'readonly')('ERC20Token', 'Wallet') return t.objectStore('ERC20Token').getAll() @@ -29,6 +30,7 @@ const fuse = new Fuse([] as ERC20TokenRecord[], { ], }) +/** @deprecated */ export async function getERC20TokensPaged(index: number, count: number, query?: string) { const t = createTransaction(await createWalletDBAccess(), 'readonly')('ERC20Token') return queryTransactionPaged(t, 'ERC20Token', { @@ -43,6 +45,7 @@ export async function getERC20TokensPaged(index: number, count: number, query?: }) } +/** @deprecated */ export async function addERC20Token(token: ERC20TokenDetailed) { const t = createTransaction(await createWalletDBAccess(), 'readwrite')('ERC20Token', 'Wallet') await t.objectStore('ERC20Token').put( @@ -56,46 +59,9 @@ export async function addERC20Token(token: ERC20TokenDetailed) { WalletMessages.events.erc20TokensUpdated.sendToAll(undefined) } +/** @deprecated */ export async function removeERC20Token(token: PartialRequired) { const t = createTransaction(await createWalletDBAccess(), 'readwrite')('ERC20Token', 'Wallet') await t.objectStore('ERC20Token').delete(formatEthereumAddress(token.address)) WalletMessages.events.erc20TokensUpdated.sendToAll(undefined) } - -export async function trustERC20Token(address: string, token: ERC20TokenDetailed) { - const t = createTransaction(await createWalletDBAccess(), 'readwrite')('ERC20Token', 'Wallet') - const wallet = await getWalletByAddress(t, formatEthereumAddress(address)) - assert(wallet) - const tokenAddressChecksummed = formatEthereumAddress(token.address) - let updated = false - if (!wallet.erc20_token_whitelist.has(tokenAddressChecksummed)) { - wallet.erc20_token_whitelist.add(tokenAddressChecksummed) - updated = true - } - if (wallet.erc20_token_blacklist.has(tokenAddressChecksummed)) { - wallet.erc20_token_blacklist.delete(tokenAddressChecksummed) - updated = true - } - if (!updated) return - await t.objectStore('Wallet').put(LegacyWalletRecordIntoDB(wallet)) - WalletMessages.events.walletsUpdated.sendToAll(undefined) -} - -export async function blockERC20Token(address: string, token: PartialRequired) { - const t = createTransaction(await createWalletDBAccess(), 'readwrite')('ERC20Token', 'Wallet') - const wallet = await getWalletByAddress(t, formatEthereumAddress(address)) - assert(wallet) - let updated = false - const tokenAddressChecksummed = formatEthereumAddress(token.address) - if (wallet.erc20_token_whitelist.has(tokenAddressChecksummed)) { - wallet.erc20_token_whitelist.delete(tokenAddressChecksummed) - updated = true - } - if (!wallet.erc20_token_blacklist.has(tokenAddressChecksummed)) { - wallet.erc20_token_blacklist.add(tokenAddressChecksummed) - updated = true - } - if (!updated) return - await t.objectStore('Wallet').put(LegacyWalletRecordIntoDB(wallet)) - WalletMessages.events.walletsUpdated.sendToAll(undefined) -} diff --git a/packages/maskbook/src/plugins/Wallet/services/erc721.ts b/packages/maskbook/src/plugins/Wallet/services/erc721.ts index 6ae3ea6a844b..6a5527fca241 100644 --- a/packages/maskbook/src/plugins/Wallet/services/erc721.ts +++ b/packages/maskbook/src/plugins/Wallet/services/erc721.ts @@ -1,24 +1,19 @@ import Fuse from 'fuse.js' import { EthereumAddress } from 'wallet.ts' -import { ERC721TokenDetailed, formatEthereumAddress, isSameAddress } from '@masknet/web3-shared' +import { ERC721TokenDetailed, isSameAddress } from '@masknet/web3-shared' import { createTransaction } from '../../../database/helpers/openDB' import { createWalletDBAccess } from '../database/Wallet.db' import { WalletMessages } from '../messages' -import { assert } from '../../../utils/utils' -import { - ERC721TokenRecordIntoDB, - ERC721TokenRecordOutDB, - getERC721TokenRecordIntoDBKey, - getWalletByAddress, - LegacyWalletRecordIntoDB, -} from './helpers' +import { ERC721TokenRecordIntoDB, ERC721TokenRecordOutDB, getERC721TokenRecordIntoDBKey } from './helpers' import { queryTransactionPaged } from '../../../database/helpers/pagination' +/** @deprecated */ export async function getERC721Tokens() { const t = createTransaction(await createWalletDBAccess(), 'readonly')('ERC721Token') return t.objectStore('ERC721Token').getAll() } +/** @deprecated */ export async function getERC721Token(address: string, tokenId: string) { const t = createTransaction(await createWalletDBAccess(), 'readonly')('ERC721Token') return t.objectStore('ERC721Token').get(getERC721TokenRecordIntoDBKey(address, tokenId)) @@ -34,6 +29,7 @@ const fuse = new Fuse([] as ERC721TokenDetailed[], { ], }) +/** @deprecated */ export async function getERC721TokensPaged(index: number, count: number, query?: string) { const t = createTransaction(await createWalletDBAccess(), 'readonly')('ERC721Token') const records = await queryTransactionPaged(t, 'ERC721Token', { @@ -49,53 +45,16 @@ export async function getERC721TokensPaged(index: number, count: number, query?: return records.map(ERC721TokenRecordOutDB) } +/** @deprecated */ export async function addERC721Token(token: ERC721TokenDetailed) { const t = createTransaction(await createWalletDBAccess(), 'readwrite')('ERC721Token', 'Wallet') await t.objectStore('ERC721Token').put(ERC721TokenRecordIntoDB(token)) WalletMessages.events.erc721TokensUpdated.sendToAll(undefined) } +/** @deprecated */ export async function removeERC721Token(token: ERC721TokenDetailed) { const t = createTransaction(await createWalletDBAccess(), 'readwrite')('ERC721Token', 'Wallet') await t.objectStore('ERC721Token').delete(ERC721TokenRecordIntoDB(token).record_id) WalletMessages.events.erc721TokensUpdated.sendToAll(undefined) } - -export async function trustERC721Token(address: string, token: ERC721TokenDetailed) { - const t = createTransaction(await createWalletDBAccess(), 'readwrite')('ERC721Token', 'Wallet') - const wallet = await getWalletByAddress(t, formatEthereumAddress(address)) - assert(wallet) - let updated = false - const key = `${formatEthereumAddress(token.contractDetailed.address)}_${token.tokenId}` - if (!wallet.erc721_token_whitelist.has(key)) { - wallet.erc721_token_whitelist.add(key) - updated = true - } - if (wallet.erc721_token_blacklist.has(key)) { - wallet.erc721_token_blacklist.delete(key) - updated = true - } - if (!updated) return false - await t.objectStore('Wallet').put(LegacyWalletRecordIntoDB(wallet)) - WalletMessages.events.walletsUpdated.sendToAll(undefined) - return updated -} - -export async function blockERC721Token(address: string, token: ERC721TokenDetailed) { - const t = createTransaction(await createWalletDBAccess(), 'readwrite')('ERC721Token', 'Wallet') - const wallet = await getWalletByAddress(t, formatEthereumAddress(address)) - assert(wallet) - let updated = false - const key = `${formatEthereumAddress(token.contractDetailed.address)}_${token.tokenId}` - if (wallet.erc721_token_whitelist.has(key)) { - wallet.erc721_token_whitelist.delete(key) - updated = true - } - if (!wallet.erc721_token_blacklist.has(key)) { - wallet.erc721_token_blacklist.add(key) - updated = true - } - if (!updated) return - await t.objectStore('Wallet').put(LegacyWalletRecordIntoDB(wallet)) - WalletMessages.events.walletsUpdated.sendToAll(undefined) -} diff --git a/packages/maskbook/src/plugins/Wallet/services/index.ts b/packages/maskbook/src/plugins/Wallet/services/index.ts index fe4bb34c0f18..ec74617e97cc 100644 --- a/packages/maskbook/src/plugins/Wallet/services/index.ts +++ b/packages/maskbook/src/plugins/Wallet/services/index.ts @@ -1,9 +1,6 @@ export * from './addressBook' export * from './account' export * from './chain' -export * from './erc20' -export * from './erc721' -export * from './erc1155' export * from './assets' export * from './transactions' export * from './recentTransactions/index' diff --git a/packages/maskbook/src/plugins/Wallet/services/wallet/database/index.ts b/packages/maskbook/src/plugins/Wallet/services/wallet/database/index.ts index f17ce3916e9a..b4c244e75950 100644 --- a/packages/maskbook/src/plugins/Wallet/services/wallet/database/index.ts +++ b/packages/maskbook/src/plugins/Wallet/services/wallet/database/index.ts @@ -1,2 +1,3 @@ +export * from './token' export * from './wallet' export * from './secret' diff --git a/packages/maskbook/src/plugins/Wallet/services/wallet/database/token.ts b/packages/maskbook/src/plugins/Wallet/services/wallet/database/token.ts new file mode 100644 index 000000000000..24cd58b782cc --- /dev/null +++ b/packages/maskbook/src/plugins/Wallet/services/wallet/database/token.ts @@ -0,0 +1,127 @@ +import { omit } from 'lodash-es' +import { + ERC1155TokenDetailed, + ERC20TokenDetailed, + ERC721TokenDetailed, + EthereumTokenType, + NonFungibleTokenDetailed, +} from '@masknet/web3-shared' +import { unreachable } from '@dimensiondev/kit' +import { WalletMessages } from '@masknet/plugin-wallet' +import { PluginDB } from '../../../database/Plugin.db' +import { asyncIteratorToArray } from '../../../../../utils' +import { currentChainIdSettings } from '../../../settings' +import type { ERC20TokenRecord, ERC721TokenRecord, ERC1155TokenRecord } from '../type' + +type DatabaseTokenType = EthereumTokenType.ERC20 | EthereumTokenType.ERC721 | EthereumTokenType.ERC1155 +type DatabaseTokenDetailed = ERC20TokenDetailed | NonFungibleTokenDetailed +type DatabaseTokenRecord = ERC20TokenRecord | ERC721TokenRecord | ERC1155TokenRecord + +const MAX_TOKEN_COUNT = 49 + +function getRecordId(address: string) { + return `${currentChainIdSettings.value}_${address}` +} + +function getTokenAddress(tokenDetailed: DatabaseTokenDetailed) { + return ( + (tokenDetailed as ERC20TokenDetailed | ERC1155TokenDetailed).address || + (tokenDetailed as ERC721TokenDetailed).contractDetailed.address + ) +} + +function getDatabaseType(type: DatabaseTokenType) { + switch (type) { + case EthereumTokenType.ERC20: + return 'erc20' + case EthereumTokenType.ERC721: + return 'erc721' + case EthereumTokenType.ERC1155: + return 'erc1155' + default: + unreachable(type) + } +} + +function getEventMessage(type: DatabaseTokenType) { + switch (type) { + case EthereumTokenType.ERC20: + return WalletMessages.events.erc20TokensUpdated + case EthereumTokenType.ERC721: + return WalletMessages.events.erc721TokensUpdated + case EthereumTokenType.ERC1155: + return WalletMessages.events.erc1155TokensUpdated + default: + unreachable(type) + } +} + +function TokenRecordOutDatabase(type: DatabaseTokenType, token: DatabaseTokenRecord) { + const token_ = { + ...omit(token, ['id', 'type', 'created', 'updated']), + type, + } + switch (type) { + case EthereumTokenType.ERC20: + return token_ as ERC20TokenDetailed + case EthereumTokenType.ERC721: + return token_ as ERC721TokenDetailed + case EthereumTokenType.ERC1155: + return token_ as ERC1155TokenDetailed + default: + throw new Error('Unknown token type.') + } +} + +export async function hasToken(type: DatabaseTokenType, address: string) { + return PluginDB.has(getDatabaseType(type), getRecordId(address)) +} + +export async function getToken(type: DatabaseTokenType, address: string) { + return PluginDB.get(getDatabaseType(type), getRecordId(address)) +} + +export async function getTokens(type: DatabaseTokenType) { + const tokens = await asyncIteratorToArray(PluginDB.iterate(getDatabaseType(type))) + return tokens + .sort((a, z) => z.createdAt.getTime() - a.createdAt.getTime()) + .slice(0, MAX_TOKEN_COUNT) + .map((x) => TokenRecordOutDatabase(type, x) as T) +} + +export async function getTokensCount(type: DatabaseTokenType) { + return (await getTokens(type)).length +} + +export async function getTokensPaged(type: DatabaseTokenType, index: number, count: number) { + let read = 0 + const records: DatabaseTokenRecord[] = [] + for await (const record of PluginDB.iterate(getDatabaseType(type))) { + if (read > (index + 1) * count) break + if (read < index * count) continue + records.push(record) + read += 1 + } + return records.map((x) => TokenRecordOutDatabase(type, x)) +} + +export async function addToken(type: DatabaseTokenType, tokenDetailed: DatabaseTokenDetailed) { + if (await hasToken(type, getTokenAddress(tokenDetailed))) + throw new Error(`Token ${getTokenAddress(tokenDetailed)} already exists.`) + const now = new Date() + // @ts-ignore + await PluginDB.add({ + ...tokenDetailed, + id: getRecordId(getTokenAddress(tokenDetailed)), + type: getDatabaseType(type), + createdAt: now, + updatedAt: now, + }) + getEventMessage(type).sendToAll() +} + +export async function removeToken(type: DatabaseTokenType, address: string) { + if (!(await hasToken(type, address))) throw new Error(`Failed to remove token ${address}.`) + await PluginDB.remove(getDatabaseType(type), getRecordId(address)) + getEventMessage(type).sendToAll() +} diff --git a/packages/maskbook/src/plugins/Wallet/services/wallet/database/wallet.ts b/packages/maskbook/src/plugins/Wallet/services/wallet/database/wallet.ts index e41c88b749f0..8ada11567cc4 100644 --- a/packages/maskbook/src/plugins/Wallet/services/wallet/database/wallet.ts +++ b/packages/maskbook/src/plugins/Wallet/services/wallet/database/wallet.ts @@ -1,6 +1,17 @@ +import { pick } from 'lodash-es' import type { api } from '@dimensiondev/mask-wallet-core/proto' import { WalletMessages } from '@masknet/plugin-wallet' -import { currySameAddress, formatEthereumAddress, isSameAddress, ProviderType } from '@masknet/web3-shared' +import { + currySameAddress, + NonFungibleTokenDetailed, + ERC1155TokenDetailed, + ERC20TokenDetailed, + ERC721TokenDetailed, + EthereumTokenType, + formatEthereumAddress, + isSameAddress, + ProviderType, +} from '@masknet/web3-shared' import { EthereumAddress } from 'wallet.ts' import { asyncIteratorToArray } from '../../../../../utils' import { PluginDB } from '../../../database/Plugin.db' @@ -102,23 +113,7 @@ export async function addWallet( export async function updateWallet( address: string, - updates: Partial< - Omit< - WalletRecord, - | 'id' - | 'type' - | 'address' - | 'createdAt' - | 'updatedAt' - | 'storedKeyInfo' - | 'erc20_token_whitelist' - | 'erc20_token_blacklist' - | 'erc721_token_whitelist' - | 'erc721_token_blacklist' - | 'erc1155_token_whitelist' - | 'erc1155_token_blacklist' - > - >, + updates: Partial>, ) { const wallet = await getWallet(address) const now = new Date() @@ -146,3 +141,62 @@ export async function deleteWallet(address: string) { await PluginDB.remove('wallet', address) WalletMessages.events.walletsUpdated.sendToAll(undefined) } + +export async function updateWalletToken( + address: string, + token: ERC20TokenDetailed | NonFungibleTokenDetailed, + { strategy }: { strategy: 'block' | 'trust' }, +) { + const wallet = await getWalletRequired(address) + const tokenAddress = + (token as ERC20TokenDetailed | ERC1155TokenDetailed).address || + (token as ERC721TokenDetailed).contractDetailed.address + const tokenAddressChecksummed = formatEthereumAddress(tokenAddress) + const tokenType = + (token as ERC20TokenDetailed | ERC1155TokenDetailed).type || + (token as ERC721TokenDetailed).contractDetailed.type + + const operationMap: Record< + EthereumTokenType.ERC20 | EthereumTokenType.ERC721 | EthereumTokenType.ERC1155, + Record<'block' | 'trust', Set> + > = { + [EthereumTokenType.ERC20]: { + block: wallet.erc20_token_blacklist, + trust: wallet.erc20_token_whitelist, + }, + [EthereumTokenType.ERC721]: { + block: wallet.erc721_token_blacklist, + trust: wallet.erc721_token_whitelist, + }, + [EthereumTokenType.ERC1155]: { + block: wallet.erc1155_token_blacklist, + trust: wallet.erc1155_token_whitelist, + }, + } + + const set = operationMap[tokenType][strategy] + const reverseSet = operationMap[tokenType][strategy === 'block' ? 'trust' : 'block'] + + let updated = false + if (!set.has(tokenAddressChecksummed)) { + set.add(tokenAddressChecksummed) + updated = true + } + if (reverseSet.has(tokenAddressChecksummed)) { + set.delete(tokenAddressChecksummed) + updated = true + } + if (!updated) return + await updateWallet( + address, + pick(wallet, [ + 'erc20_token_blacklist', + 'erc20_token_whitelist', + 'erc721_token_blacklist', + 'erc721_token_whitelist', + 'erc1155_token_blacklist', + 'erc1155_token_whitelist', + ]), + ) + WalletMessages.events.walletsUpdated.sendToAll(undefined) +} diff --git a/packages/maskbook/src/plugins/Wallet/services/wallet/index.ts b/packages/maskbook/src/plugins/Wallet/services/wallet/index.ts index e6abe71e386e..d9ed2e3dd8a7 100644 --- a/packages/maskbook/src/plugins/Wallet/services/wallet/index.ts +++ b/packages/maskbook/src/plugins/Wallet/services/wallet/index.ts @@ -18,7 +18,8 @@ function bumpDerivationPath(path = `${HD_PATH_WITHOUT_INDEX_ETHEREUM}/0`) { } // db -export { getWallet, getWallets, updateWallet, hasWallet } from './database/wallet' +export { getToken, getTokens, getTokensCount, getTokensPaged, hasToken, addToken, removeToken } from './database/token' +export { getWallet, getWallets, hasWallet, updateWallet, updateWalletToken } from './database/wallet' // password export { setPassword, hasPassword, verifyPassword, changePassword, validatePassword, clearPassword } from './password' diff --git a/packages/maskbook/src/plugins/Wallet/services/wallet/type.ts b/packages/maskbook/src/plugins/Wallet/services/wallet/type.ts index 11058c9f029f..353f5d836c08 100644 --- a/packages/maskbook/src/plugins/Wallet/services/wallet/type.ts +++ b/packages/maskbook/src/plugins/Wallet/services/wallet/type.ts @@ -1,5 +1,26 @@ import type { api } from '@dimensiondev/mask-wallet-core/proto' -import type { Wallet } from '@masknet/web3-shared' +import type { ERC1155TokenDetailed, ERC20TokenDetailed, ERC721TokenDetailed, Wallet } from '@masknet/web3-shared' + +export interface ERC20TokenRecord extends Omit { + id: string + type: 'erc20' + createdAt: Date + updatedAt: Date +} + +export interface ERC721TokenRecord extends ERC721TokenDetailed { + id: string + type: 'erc721' + createdAt: Date + updatedAt: Date +} + +export interface ERC1155TokenRecord extends Omit { + id: string + type: 'erc1155' + createdAt: Date + updatedAt: Date +} export interface WalletRecord extends Omit { id: string diff --git a/packages/maskbook/src/web3/context.ts b/packages/maskbook/src/web3/context.ts index edb5f57b0493..2b90d140342e 100644 --- a/packages/maskbook/src/web3/context.ts +++ b/packages/maskbook/src/web3/context.ts @@ -1,6 +1,13 @@ import { noop, omit } from 'lodash-es' import type { Subscription } from 'use-subscription' -import { ERC20TokenDetailed, EthereumTokenType, ProviderType, Web3ProviderType } from '@masknet/web3-shared' +import { + ERC20TokenDetailed, + ERC721TokenDetailed, + ERC1155TokenDetailed, + EthereumTokenType, + ProviderType, + Web3ProviderType, +} from '@masknet/web3-shared' import { WalletMessages, WalletRPC } from '../plugins/Wallet/messages' import { currentBlockNumberSettings, @@ -58,24 +65,28 @@ function createWeb3Context(disablePopup = false, isMask = false): Web3ProviderTy ? createStaticSubscription(() => ProviderType.MaskWallet) : createSubscriptionFromSettings(currentProviderSettings), networkType: createSubscriptionFromSettings(isMask ? currentMaskWalletNetworkSettings : currentNetworkSettings), - erc20Tokens: createSubscriptionFromAsync(getERC20Tokens, [], WalletMessages.events.erc20TokensUpdated.on), - erc20TokensCount: createSubscriptionFromAsync( - WalletRPC.getERC20TokensCount, - 0, + erc20Tokens: createSubscriptionFromAsync( + () => WalletRPC.getTokens(EthereumTokenType.ERC20), + [], WalletMessages.events.erc20TokensUpdated.on, ), + erc721Tokens: createSubscriptionFromAsync( + () => WalletRPC.getTokens(EthereumTokenType.ERC721), + [], + WalletMessages.events.erc721TokensUpdated.on, + ), + erc1155Tokens: createSubscriptionFromAsync( + () => WalletRPC.getTokens(EthereumTokenType.ERC1155), + [], + WalletMessages.events.erc1155TokensUpdated.on, + ), portfolioProvider: createSubscriptionFromSettings(currentPortfolioDataProviderSettings), - addERC20Token: WalletRPC.addERC20Token, - trustERC20Token: WalletRPC.trustERC20Token, - getERC20TokensPaged, getAssetsList: WalletRPC.getAssetsList, getAssetsListNFT: WalletRPC.getAssetsListNFT, getAddressNamesList: WalletRPC.getAddressNames, - getERC721TokensPaged, fetchERC20TokensFromTokenLists: Services.Ethereum.fetchERC20TokensFromTokenLists, getTransactionList: WalletRPC.getTransactionList, createMnemonicWords: WalletRPC.createMnemonicWords, - getNonce: Services.Ethereum.getNonce, } } @@ -102,26 +113,6 @@ export async function getWalletPrimary() { } } -async function getERC20Tokens() { - const raw = await WalletRPC.getERC20Tokens() - return raw.map((x) => ({ - type: EthereumTokenType.ERC20, - ...x, - })) -} - -async function getERC20TokensPaged(index: number, count: number, query?: string) { - const raw = await WalletRPC.getERC20TokensPaged(index, count, query) - return raw.map((x) => ({ - type: EthereumTokenType.ERC20, - ...x, - })) -} - -async function getERC721TokensPaged(index: number, count: number, query?: string) { - return WalletRPC.getERC721TokensPaged(index, count, query) -} - // utils function createSubscriptionFromSettings(settings: InternalSettings): Subscription { const { trigger, subscribe } = getEventTarget() diff --git a/packages/web3-shared/src/context/index.tsx b/packages/web3-shared/src/context/index.tsx index 4c8490881e79..ce7af3cb5a22 100644 --- a/packages/web3-shared/src/context/index.tsx +++ b/packages/web3-shared/src/context/index.tsx @@ -29,7 +29,8 @@ export function useWeb3State() { const chainId = useSubscription(_.chainId) const chainDetailed = useMemo(() => getChainDetailed(chainId), [chainId]) const erc20Tokens = useSubscription(_.erc20Tokens) - const erc20TokensCount = useSubscription(_.erc20TokensCount) + const erc721Tokens = useSubscription(_.erc721Tokens) + const erc1155Tokens = useSubscription(_.erc1155Tokens) const portfolioProvider = useSubscription(_.portfolioProvider) return { allowTestnet, @@ -44,7 +45,8 @@ export function useWeb3State() { chainId, chainDetailed, erc20Tokens, - erc20TokensCount, + erc721Tokens, + erc1155Tokens, portfolioProvider, chainIdValid: !account || isChainIdValid(chainId, allowTestnet), } diff --git a/packages/web3-shared/src/context/type.ts b/packages/web3-shared/src/context/type.ts index 257427e03981..21478713016f 100644 --- a/packages/web3-shared/src/context/type.ts +++ b/packages/web3-shared/src/context/type.ts @@ -4,6 +4,7 @@ import type { ChainId, ERC20TokenDetailed, ERC721TokenDetailed, + ERC1155TokenDetailed, NetworkType, ProviderType, Wallet, @@ -28,11 +29,9 @@ export interface Web3ProviderType { wallets: Subscription providerType: Subscription networkType: Subscription - erc20TokensCount: Subscription erc20Tokens: Subscription - addERC20Token: (token: ERC20TokenDetailed) => Promise - trustERC20Token: (address: string, token: ERC20TokenDetailed) => Promise - getERC20TokensPaged: (index: number, count: number, query?: string) => Promise + erc721Tokens: Subscription + erc1155Tokens: Subscription portfolioProvider: Subscription getAssetsList: (address: string, network: NetworkType, provider: PortfolioProvider) => Promise getAssetsListNFT: ( @@ -43,7 +42,6 @@ export interface Web3ProviderType { size?: number, ) => Promise<{ assets: ERC721TokenDetailed[]; hasNextPage: boolean }> getAddressNamesList: (twitterId: string, addressNameType: AddressNameType) => Promise - getERC721TokensPaged: (index: number, count: number, query?: string) => Promise getTransactionList: ( address: string, network: NetworkType, @@ -56,5 +54,4 @@ export interface Web3ProviderType { }> fetchERC20TokensFromTokenLists: (urls: string[], chainId: ChainId) => Promise createMnemonicWords: () => Promise - getNonce: (address: string) => Promise } diff --git a/packages/web3-shared/src/hooks/index.ts b/packages/web3-shared/src/hooks/index.ts index f95a11c26bdf..7fdaf04b093c 100644 --- a/packages/web3-shared/src/hooks/index.ts +++ b/packages/web3-shared/src/hooks/index.ts @@ -6,7 +6,6 @@ export * from './useChainId' export * from './useChainDetailed' export * from './useContract' export * from './useCurrentBlockTimestamp' -export * from './useNonce' export * from './useGasPrice' export * from './useEtherPrice' export * from './useENSLabel' @@ -64,7 +63,6 @@ export * from './useWalletPrimary' export * from './useWallet' export * from './useWallets' export * from './useERC20Tokens' -export * from './useERC20TokensPaged' export * from './useTrustedERC20Tokens' export * from './useTrustERC20TokenCallback' export * from './useAddERC20TokenCallback' diff --git a/packages/web3-shared/src/hooks/useERC20TokensPaged.ts b/packages/web3-shared/src/hooks/useERC20TokensPaged.ts deleted file mode 100644 index cbfa61f783ca..000000000000 --- a/packages/web3-shared/src/hooks/useERC20TokensPaged.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useWallet } from './useWallet' -import { useAsyncRetry } from 'react-use' -import { useWeb3Context, useWeb3State } from '../context' - -export function useERC20TokensPaged(index: number, count: number) { - const wallet = useWallet() - const { getERC20TokensPaged } = useWeb3Context() - const { erc20TokensCount } = useWeb3State() - return useAsyncRetry(async () => { - if (!wallet) - return { - tokens: [], - count: 0, - } - const erc20Tokens = await getERC20TokensPaged(index, count) - - return { - tokens: erc20Tokens.filter( - (x) => wallet.erc20_token_whitelist.has(x.address) && !wallet.erc20_token_blacklist.has(x.address), - ), - count: erc20TokensCount, - } - }, [index, count]) -} diff --git a/packages/web3-shared/src/hooks/useNonce.ts b/packages/web3-shared/src/hooks/useNonce.ts deleted file mode 100644 index 8fa739d6baec..000000000000 --- a/packages/web3-shared/src/hooks/useNonce.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useAsyncRetry } from 'react-use' -import { useWeb3Context } from '../context' - -export function useNonce(address: string) { - const { getNonce } = useWeb3Context() - return useAsyncRetry(async () => { - return getNonce(address) - }, [address]) -}