diff --git a/packages/maskbook/src/extension/background-script/EthereumServices/network.ts b/packages/maskbook/src/extension/background-script/EthereumServices/network.ts index 299635d803e1..8d4a78fd036e 100644 --- a/packages/maskbook/src/extension/background-script/EthereumServices/network.ts +++ b/packages/maskbook/src/extension/background-script/EthereumServices/network.ts @@ -6,82 +6,116 @@ import type { PastLogsOptions, Log, } from 'web3-core' -import { first } from 'lodash-es' -import { ChainId, EthereumChainDetailed, EthereumMethodType, getRPCConstants } from '@masknet/web3-shared' +import { ChainId, EthereumChainDetailed, EthereumMethodType } from '@masknet/web3-shared' import { request } from './request' +import type { SendOverrides } from './send' -export async function getGasPrice() { - return request({ - method: EthereumMethodType.ETH_GAS_PRICE, - }) +export async function getGasPrice(overrides?: SendOverrides) { + return request( + { + method: EthereumMethodType.ETH_GAS_PRICE, + }, + overrides, + ) } -export async function getBlockNumber() { - const blockNumber = await request({ - method: EthereumMethodType.ETH_BLOCK_NUMBER, - }) +export async function getBlockNumber(overrides?: SendOverrides) { + const blockNumber = await request( + { + method: EthereumMethodType.ETH_BLOCK_NUMBER, + }, + overrides, + ) return Number.parseInt(blockNumber, 16) } -export async function getBalance(address: string) { - return request({ - method: EthereumMethodType.ETH_GET_BALANCE, - params: [address, 'latest'], - }) +export async function getBalance(address: string, overrides?: SendOverrides) { + return request( + { + method: EthereumMethodType.ETH_GET_BALANCE, + params: [address, 'latest'], + }, + overrides, + ) } -export async function getTransactionByHash(hash: string) { - return request({ - method: EthereumMethodType.ETH_GET_TRANSACTION_BY_HASH, - params: [hash], - }) +export async function getTransactionByHash(hash: string, overrides?: SendOverrides) { + return request( + { + method: EthereumMethodType.ETH_GET_TRANSACTION_BY_HASH, + params: [hash], + }, + overrides, + ) } -export async function getTransactionReceipt(hash: string) { - return request({ - method: EthereumMethodType.ETH_GET_TRANSACTION_RECEIPT, - params: [hash], - }) +export async function getTransactionReceipt(hash: string, overrides?: SendOverrides) { + return request( + { + method: EthereumMethodType.ETH_GET_TRANSACTION_RECEIPT, + params: [hash], + }, + overrides, + ) } -export async function getTransactionCount(address: string) { - const count = await request({ - method: EthereumMethodType.ETH_GET_TRANSACTION_COUNT, - params: [address, 'latest'], - }) +export async function getTransactionCount(address: string, overrides?: SendOverrides) { + const count = await request( + { + method: EthereumMethodType.ETH_GET_TRANSACTION_COUNT, + params: [address, 'latest'], + }, + overrides, + ) return Number.parseInt(count, 16) } -export async function estimateGas(config: TransactionConfig) { - const gas = await request({ - method: EthereumMethodType.ETH_ESTIMATE_GAS, - params: [config], - }) +export async function estimateGas(config: TransactionConfig, overrides?: SendOverrides) { + const gas = await request( + { + method: EthereumMethodType.ETH_ESTIMATE_GAS, + params: [config], + }, + overrides, + ) return Number.parseInt(gas, 16) } -export async function sign(dataToSign: string, address: string) { - return request({ - method: EthereumMethodType.ETH_SIGN, - params: [dataToSign, address], - }) +export async function sign(dataToSign: string, address: string, overrides?: SendOverrides) { + return request( + { + method: EthereumMethodType.ETH_SIGN, + params: [dataToSign, address], + }, + overrides, + ) } -export async function personalSign(dataToSign: string, address: string, password?: string) { - return request({ - method: EthereumMethodType.PERSONAL_SIGN, - params: [dataToSign, address, password].filter((x) => typeof x !== 'undefined'), - }) +export async function personalSign(dataToSign: string, address: string, password?: string, overrides?: SendOverrides) { + return request( + { + method: EthereumMethodType.PERSONAL_SIGN, + params: [dataToSign, address, password].filter((x) => typeof x !== 'undefined'), + }, + overrides, + ) } -export async function addEthereumChain(chainDetailed: EthereumChainDetailed, address?: string) { - return request({ - method: EthereumMethodType.WALLET_ADD_ETHEREUM_CHAIN, - params: [chainDetailed, address].filter(Boolean), - }) +export async function addEthereumChain( + chainDetailed: EthereumChainDetailed, + address?: string, + overrides?: SendOverrides, +) { + return request( + { + method: EthereumMethodType.WALLET_ADD_ETHEREUM_CHAIN, + params: [chainDetailed, address].filter(Boolean), + }, + overrides, + ) } -export async function switchEthereumChain(chainId: ChainId) { +export async function switchEthereumChain(chainId: ChainId, overrides?: SendOverrides) { return request({ method: EthereumMethodType.WALLET_SWITCH_ETHEREUM_CHAIN, params: [ @@ -89,25 +123,26 @@ export async function switchEthereumChain(chainId: ChainId) { chainId: `0x${chainId.toString(16)}`, }, ], + overrides, }) } -export async function signTransaction(config: TransactionConfig) { - return request({ - method: EthereumMethodType.ETH_SIGN_TRANSACTION, - params: [config], - }) +export async function signTransaction(config: TransactionConfig, overrides?: SendOverrides) { + return request( + { + method: EthereumMethodType.ETH_SIGN_TRANSACTION, + params: [config], + }, + overrides, + ) } -export async function getPastLogs(config: PastLogsOptions, chainId: ChainId) { - const { RPC } = getRPCConstants(chainId) - const provderURL = first(RPC) - if (!provderURL) throw new Error('Unknown chain id.') +export async function getPastLogs(config: PastLogsOptions, overrides?: SendOverrides) { return request( { method: EthereumMethodType.ETH_GET_LOGS, params: [config], }, - provderURL, + overrides, ) } diff --git a/packages/maskbook/src/extension/background-script/EthereumServices/providers/MetaMask.ts b/packages/maskbook/src/extension/background-script/EthereumServices/providers/MetaMask.ts index f035e819bb57..5b19b5290a07 100644 --- a/packages/maskbook/src/extension/background-script/EthereumServices/providers/MetaMask.ts +++ b/packages/maskbook/src/extension/background-script/EthereumServices/providers/MetaMask.ts @@ -1,46 +1,52 @@ import Web3 from 'web3' import type { provider as Provider } from 'web3-core' import { first } from 'lodash-es' -import { EthereumAddress } from 'wallet.ts' import createMetaMaskProvider, { MetaMaskInpageProvider } from '@dimensiondev/metamask-extension-provider' -import { ChainId, NetworkType, ProviderType } from '@masknet/web3-shared' -import { updateAccount, updateExoticWalletFromSource } from '../../../../plugins/Wallet/services' -import { currentIsMetamaskLockedSettings, currentProviderSettings } from '../../../../plugins/Wallet/settings' +import { ChainId, ProviderType } from '@masknet/web3-shared' +import { resetAccount, updateAccount } from '../../../../plugins/Wallet/services' +import { + currentChainIdSettings, + currentIsMetamaskLockedSettings, + currentProviderSettings, +} from '../../../../plugins/Wallet/settings' let provider: MetaMaskInpageProvider | null = null let web3: Web3 | null = null async function onAccountsChanged(accounts: string[]) { - await updateWalletInDB(first(accounts) ?? '') currentIsMetamaskLockedSettings.value = !(await provider!._metamask?.isUnlocked()) && accounts.length === 0 + if (currentProviderSettings.value !== ProviderType.MetaMask) return + await updateAccount({ + account: first(accounts), + providerType: ProviderType.MetaMask, + chainId: typeof provider?.chainId === 'string' ? Number.parseInt(provider.chainId, 16) : undefined, + networkType: undefined, + }) } async function onChainIdChanged(id: string) { - // learn more: https://docs.metamask.io/guide/ethereum-provider.html#chain-ids and https://chainid.network/ - const chainId_ = Number.parseInt(id, 16) - const chainId = chainId_ === 0 ? ChainId.Mainnet : chainId_ currentIsMetamaskLockedSettings.value = !(await provider!._metamask?.isUnlocked()) if (currentProviderSettings.value !== ProviderType.MetaMask) return + + // learn more: https://docs.metamask.io/guide/ethereum-provider.html#chain-ids and https://chainid.network/ + const chainId = Number.parseInt(id, 16) || ChainId.Mainnet + if (currentChainIdSettings.value === chainId) return await updateAccount({ chainId, + networkType: undefined, }) } async function onError(error: string) { if (typeof error !== 'string' || !/Lost Connection to MetaMask/i.test(error)) return if (currentProviderSettings.value !== ProviderType.MetaMask) return - await updateAccount({ - account: '', - networkType: NetworkType.Ethereum, + await resetAccount({ + providerType: ProviderType.MetaMask, }) } export function createProvider() { - if (provider) { - provider.off('accountsChanged', onAccountsChanged) - provider.off('chainChanged', onChainIdChanged) - provider.off('error', onError) - } + if (provider) return provider provider = createMetaMaskProvider() if (!provider) throw new Error('Unable to create in page provider.') provider.on('accountsChanged', onAccountsChanged as (...args: unknown[]) => void) @@ -60,36 +66,10 @@ export function createWeb3() { export async function requestAccounts() { const web3 = createWeb3() - - // update accounts const accounts = await web3.eth.requestAccounts() - await updateWalletInDB(first(accounts) ?? '', true) - - // update chain id const chainId = await web3.eth.getChainId() - onChainIdChanged(chainId.toString(16)) - return { chainId, accounts, } } - -async function updateWalletInDB(address: string, setAsDefault: boolean = false) { - const providerType = currentProviderSettings.value - - // validate address - if (!EthereumAddress.isValid(address)) { - if (providerType === ProviderType.MetaMask) await updateAccount({ account: '' }) - return - } - - // update wallet in the DB - await updateExoticWalletFromSource(ProviderType.MetaMask, new Map([[address, { address }]])) - - // update chain account - await updateAccount({ - account: setAsDefault || providerType === ProviderType.MetaMask ? address : undefined, - providerType: setAsDefault ? ProviderType.MetaMask : undefined, - }) -} diff --git a/packages/maskbook/src/extension/background-script/EthereumServices/providers/WalletConnect.ts b/packages/maskbook/src/extension/background-script/EthereumServices/providers/WalletConnect.ts index 7f780e5096ee..db10e89ddba1 100644 --- a/packages/maskbook/src/extension/background-script/EthereumServices/providers/WalletConnect.ts +++ b/packages/maskbook/src/extension/background-script/EthereumServices/providers/WalletConnect.ts @@ -1,11 +1,10 @@ import type { JsonRpcResponse } from 'web3-core-helpers' import { first } from 'lodash-es' -import { EthereumAddress } from 'wallet.ts' import WalletConnect from '@walletconnect/client' import type { IJsonRpcRequest } from '@walletconnect/types' -import { ProviderType, NetworkType, ChainId } from '@masknet/web3-shared' +import { ProviderType, ChainId } from '@masknet/web3-shared' import * as Maskbook from '../providers/Maskbook' -import { updateAccount, updateExoticWalletFromSource } from '../../../../plugins/Wallet/services' +import { resetAccount, updateAccount } from '../../../../plugins/Wallet/services' import { currentChainIdSettings, currentProviderSettings } from '../../../../plugins/Wallet/settings' let connector: WalletConnect | null = null @@ -55,7 +54,7 @@ export async function sendCustomRequest(payload: IJsonRpcRequest) { // Wrap promise as PromiEvent because WalletConnect returns transaction hash only // docs: https://docs.walletconnect.org/client-api -export function createWeb3(chainId = currentChainIdSettings.value) { +export function createWeb3({ chainId = currentChainIdSettings.value }: { chainId?: ChainId } = {}) { return Maskbook.createWeb3({ chainId, }) @@ -85,17 +84,11 @@ export async function requestAccounts() { }) } -const onConnect = async () => { - if (!connector?.accounts.length) return - await updateAccount({ - chainId: connector.chainId, - }) - await updateWalletInDB(first(connector.accounts) ?? '', connector.peerMeta?.name, true) -} +const onConnect = () => onUpdate(null) const onUpdate = async ( error: Error | null, - payload: { + payload?: { params: { chainId: number accounts: string[] @@ -104,37 +97,21 @@ const onUpdate = async ( ) => { if (error) return if (!connector?.accounts.length) return + if (currentProviderSettings.value !== ProviderType.WalletConnect) return await updateAccount({ + name: connector.peerMeta?.name, + account: first(connector.accounts), chainId: connector.chainId, + providerType: ProviderType.WalletConnect, + networkType: undefined, }) - await updateWalletInDB(first(connector.accounts) ?? '', connector.peerMeta?.name, false) } const onDisconnect = async (error: Error | null) => { if (connector?.connected) await connector.killSession() connector = null if (currentProviderSettings.value !== ProviderType.WalletConnect) return - await updateAccount({ - account: '', - networkType: NetworkType.Ethereum, - }) -} - -async function updateWalletInDB(address: string, name: string = 'WalletConnect', setAsDefault: boolean = false) { - const providerType = currentProviderSettings.value - - // validate address - if (!EthereumAddress.isValid(address)) { - if (providerType === ProviderType.WalletConnect) await updateAccount({ account: '' }) - return - } - - // update wallet in the DB - await updateExoticWalletFromSource(ProviderType.WalletConnect, new Map([[address, { name, address }]])) - - // update chain account - await updateAccount({ - account: setAsDefault || providerType === ProviderType.WalletConnect ? address : undefined, - providerType: setAsDefault ? ProviderType.WalletConnect : undefined, + await resetAccount({ + providerType: ProviderType.WalletConnect, }) } diff --git a/packages/maskbook/src/extension/background-script/EthereumServices/request.ts b/packages/maskbook/src/extension/background-script/EthereumServices/request.ts index 3f3e4ff5fd12..45bca93aa905 100644 --- a/packages/maskbook/src/extension/background-script/EthereumServices/request.ts +++ b/packages/maskbook/src/extension/background-script/EthereumServices/request.ts @@ -1,10 +1,10 @@ import type { RequestArguments } from 'web3-core' import type { JsonRpcPayload, JsonRpcResponse } from 'web3-core-helpers' -import { INTERNAL_send } from './send' +import { INTERNAL_send, SendOverrides } from './send' let id = 0 -export async function request(requestArguments: RequestArguments, rpc?: string) { +export async function request(requestArguments: RequestArguments, overrides?: SendOverrides) { return new Promise((resolve, reject) => { id++ INTERNAL_send( @@ -18,7 +18,7 @@ export async function request(requestArguments: RequestArgume if (error || response?.error) reject(error ?? response?.error) else resolve(response?.result) }, - rpc, + overrides, ) }) } diff --git a/packages/maskbook/src/extension/background-script/EthereumServices/send.ts b/packages/maskbook/src/extension/background-script/EthereumServices/send.ts index da32c7337ea7..f02fa53038ec 100644 --- a/packages/maskbook/src/extension/background-script/EthereumServices/send.ts +++ b/packages/maskbook/src/extension/background-script/EthereumServices/send.ts @@ -1,7 +1,7 @@ import { EthereumAddress } from 'wallet.ts' import type { HttpProvider, TransactionConfig } from 'web3-core' import type { JsonRpcPayload, JsonRpcResponse } from 'web3-core-helpers' -import { addGasMargin, EthereumMethodType, ProviderType } from '@masknet/web3-shared' +import { addGasMargin, ChainId, EthereumMethodType, ProviderType } from '@masknet/web3-shared' import type { IJsonRpcRequest } from '@walletconnect/types' import { safeUnreachable } from '@dimensiondev/kit' import { createWeb3 } from './web3' @@ -9,7 +9,18 @@ import * as WalletConnect from './providers/WalletConnect' import { addRecentTransaction, getWallet } from '../../../plugins/Wallet/services' import { commitNonce, getNonce, resetNonce } from './nonce' import { getGasPrice } from './network' -import { currentAccountSettings, currentProviderSettings } from '../../../plugins/Wallet/settings' +import { + currentAccountSettings, + currentChainIdSettings, + currentProviderSettings, +} from '../../../plugins/Wallet/settings' +import { debugModeSetting } from '../../../settings/settings' + +export interface SendOverrides { + chainId?: ChainId + account?: string + providerType?: ProviderType +} /** * This API is only used internally. Please use requestSend instead in order to share the same payload id globally. @@ -20,18 +31,22 @@ import { currentAccountSettings, currentProviderSettings } from '../../../plugin export async function INTERNAL_send( payload: JsonRpcPayload, callback: (error: Error | null, response?: JsonRpcResponse) => void, - rpc?: string, + { + chainId = currentChainIdSettings.value, + account = currentAccountSettings.value, + providerType = currentProviderSettings.value, + }: SendOverrides = {}, ) { - if (process.env.NODE_ENV === 'development') { + if (process.env.NODE_ENV === 'development' && debugModeSetting.value) { console.table(payload) console.debug(new Error().stack) } - const account = currentAccountSettings.value - const providerType = currentProviderSettings.value const wallet = providerType === ProviderType.Maskbook ? await getWallet() : null const web3 = createWeb3({ + chainId, privKeys: wallet?._private_key_ ? [wallet._private_key_] : [], + providerType, }) const provider = web3.currentProvider as HttpProvider | undefined @@ -141,18 +156,7 @@ export async function INTERNAL_send( await sendTransaction() break default: - if (rpc) { - fetch(rpc, { - method: 'POST', - body: JSON.stringify(payload), - }) - .catch((error: Error) => callback(error)) - .then(async (res) => { - if (res) callback(null, (await res.json()) as JsonRpcResponse) - }) - } else { - provider.send(payload, callback) - } + provider.send(payload, callback) break } } catch (error) { diff --git a/packages/maskbook/src/extension/background-script/EthereumServices/web3.ts b/packages/maskbook/src/extension/background-script/EthereumServices/web3.ts index 02ecf3d03e0d..3c0872dea55f 100644 --- a/packages/maskbook/src/extension/background-script/EthereumServices/web3.ts +++ b/packages/maskbook/src/extension/background-script/EthereumServices/web3.ts @@ -19,9 +19,11 @@ export function createWeb3({ case ProviderType.MetaMask: return MetaMask.createWeb3() case ProviderType.WalletConnect: - return WalletConnect.createWeb3() + return WalletConnect.createWeb3({ + chainId, + }) case ProviderType.CustomNetwork: - return WalletConnect.createWeb3() + throw new Error('To be implemented.') default: unreachable(providerType) } diff --git a/packages/maskbook/src/extension/options-page/DashboardComponents/CollectibleList/index.tsx b/packages/maskbook/src/extension/options-page/DashboardComponents/CollectibleList/index.tsx index 8cea5e7ae10b..d1101acb3fe3 100644 --- a/packages/maskbook/src/extension/options-page/DashboardComponents/CollectibleList/index.tsx +++ b/packages/maskbook/src/extension/options-page/DashboardComponents/CollectibleList/index.tsx @@ -1,9 +1,8 @@ -import { useValueRef } from '@masknet/shared' -import { EthereumTokenType, formatEthereumAddress, useAccount, Wallet } from '@masknet/web3-shared' -import { Box, Button, makeStyles, Skeleton, TablePagination, Typography } from '@material-ui/core' import { createContext, useState } from 'react' import { useUpdateEffect } from 'react-use' -import { useCollectibles } from '../../../../plugins/Wallet/hooks/useCollectibles' +import { useValueRef } from '@masknet/shared' +import { EthereumTokenType, formatEthereumAddress, useAccount, useCollectibles, Wallet } from '@masknet/web3-shared' +import { Box, Button, makeStyles, Skeleton, TablePagination, Typography } from '@material-ui/core' import { currentCollectibleDataProviderSettings } from '../../../../plugins/Wallet/settings' import { useI18N } from '../../../../utils' import { CollectibleCard } from './CollectibleCard' diff --git a/packages/maskbook/src/extension/options-page/DashboardComponents/WalletAssetsTable.tsx b/packages/maskbook/src/extension/options-page/DashboardComponents/WalletAssetsTable.tsx index 45989dc7a174..9b557ef67c60 100644 --- a/packages/maskbook/src/extension/options-page/DashboardComponents/WalletAssetsTable.tsx +++ b/packages/maskbook/src/extension/options-page/DashboardComponents/WalletAssetsTable.tsx @@ -19,7 +19,7 @@ import BigNumber from 'bignumber.js' import classNames from 'classnames' import ExpandLessIcon from '@material-ui/icons/ExpandLess' import ExpandMoreIcon from '@material-ui/icons/ExpandMore' -import type { Asset } from '@masknet/web3-shared' +import { Asset, useTrustedERC20Tokens } from '@masknet/web3-shared' import { CurrencyType, currySameAddress, @@ -37,7 +37,6 @@ import { useStylesExtends } from '../../../components/custom-ui-helper' import { FormattedCurrency, TokenIcon, useValueRef } from '@masknet/shared' import { useI18N, useMatchXS } from '../../../utils' import { ActionsBarFT } from './ActionsBarFT' -import { useTrustedERC20TokensFromDB } from '../../../plugins/Wallet/hooks/useERC20Tokens' import { getTokenUSDValue } from '../../../plugins/Wallet/helpers' import { currentEtherPriceSettings } from '../../../plugins/Wallet/settings' @@ -206,7 +205,7 @@ export function WalletAssetsTable(props: WalletAssetsTableProps) { ...(isMobile ? [] : ['']), ] as const - const erc20Tokens = useTrustedERC20TokensFromDB() + const erc20Tokens = useTrustedERC20Tokens() const { value: detailedTokens, error: detailedTokensError, diff --git a/packages/maskbook/src/plugins/ITO/SNSAdaptor/hooks/useClaimablePoolsByWeb3.ts b/packages/maskbook/src/plugins/ITO/SNSAdaptor/hooks/useClaimablePoolsByWeb3.ts index 8b0fba95e7d2..f7bc689fa192 100644 --- a/packages/maskbook/src/plugins/ITO/SNSAdaptor/hooks/useClaimablePoolsByWeb3.ts +++ b/packages/maskbook/src/plugins/ITO/SNSAdaptor/hooks/useClaimablePoolsByWeb3.ts @@ -46,7 +46,11 @@ export function useClaimablePoolsByWeb3() { return useAsyncRetry(async () => { const logs = flatten( await Promise.all( - queryParams.map((queryParam: PastLogsOptions) => Services.Ethereum.getPastLogs(queryParam, chainId)), + queryParams.map((queryParam: PastLogsOptions) => + Services.Ethereum.getPastLogs(queryParam, { + chainId, + }), + ), ), ) return logs.reduce((acc, log) => { diff --git a/packages/maskbook/src/plugins/Wallet/SNSAdaptor/ConnectWalletDialog/index.tsx b/packages/maskbook/src/plugins/Wallet/SNSAdaptor/ConnectWalletDialog/index.tsx index 0a78cbd29136..683fd58cb050 100644 --- a/packages/maskbook/src/plugins/Wallet/SNSAdaptor/ConnectWalletDialog/index.tsx +++ b/packages/maskbook/src/plugins/Wallet/SNSAdaptor/ConnectWalletDialog/index.tsx @@ -15,7 +15,7 @@ import { useStylesExtends } from '../../../../components/custom-ui-helper' import { InjectedDialog } from '../../../../components/shared/InjectedDialog' import { delay } from '../../../../utils' import { useRemoteControlledDialog } from '@masknet/shared' -import { WalletMessages } from '../../messages' +import { WalletMessages, WalletRPC } from '../../messages' import { ConnectionProgress } from './ConnectionProgress' import Services from '../../../../extension/service' @@ -47,83 +47,87 @@ export function ConnectWalletDialog(props: ConnectWalletDialogProps) { ) //#endregion - const connectTo = useCallback( - async (providerType: ProviderType) => { - // unknown network type - if (!networkType) throw new Error('Unknown network type.') - - // read the chain detailed from the built-in chain list - const chainDetailedCAIP = getChainDetailedCAIP(getChainIdFromNetworkType(networkType)) - if (!chainDetailedCAIP) throw new Error('Unknown network type.') - - // a short time loading makes the user fells better - await delay(1000) - - let account: string | undefined - let chainId: ChainId | undefined - - switch (providerType) { - case ProviderType.Maskbook: - throw new Error('Not necessary!') - case ProviderType.MetaMask: - ;({ account, chainId } = await Services.Ethereum.connectMetaMask()) - break - case ProviderType.WalletConnect: - // create wallet connect QR code URI - const uri = await Services.Ethereum.createConnectionURI() - if (!uri) throw new Error('Failed to create connection URI.') - - // open the QR code dialog - setWalletConnectDialog({ - open: true, - uri, - }) - - // wait for walletconnect to be connected - ;({ account, chainId } = await Services.Ethereum.connectWalletConnect()) - break - case ProviderType.CustomNetwork: - throw new Error('To be implemented.') - default: - safeUnreachable(providerType) - break - } - - // connection failed - if (!account || !networkType) throw new Error(`Failed to connect ${resolveProviderName(providerType)}.`) - - // no need to switch the chain - if (chainId === Number.parseInt(chainDetailedCAIP.chainId)) return true + const connectTo = useCallback(async () => { + if (!networkType) throw new Error('Unknown network type.') + if (!providerType) throw new Error('Unknown provider type.') - // request ethereum-compatiable network + // read the chain detailed from the built-in chain list + const expectedChainId = getChainIdFromNetworkType(networkType) + const chainDetailedCAIP = getChainDetailedCAIP(expectedChainId) + if (!chainDetailedCAIP) throw new Error('Unknown network type.') + + // a short time loading makes the user fells better + await delay(1000) + + let account: string | undefined + let chainId: ChainId | undefined + + switch (providerType) { + case ProviderType.Maskbook: + throw new Error('Not necessary!') + case ProviderType.MetaMask: + ;({ account, chainId } = await Services.Ethereum.connectMetaMask()) + break + case ProviderType.WalletConnect: + // create wallet connect QR code URI + const uri = await Services.Ethereum.createConnectionURI() + if (!uri) throw new Error('Failed to create connection URI.') + + // open the QR code dialog + setWalletConnectDialog({ + open: true, + uri, + }) + + // wait for walletconnect to be connected + ;({ account, chainId } = await Services.Ethereum.connectWalletConnect()) + break + case ProviderType.CustomNetwork: + throw new Error('To be implemented.') + default: + safeUnreachable(providerType) + break + } + + // connection failed + if (!account || !networkType) throw new Error(`Failed to connect ${resolveProviderName(providerType)}.`) + + // need to switch chain + if (chainId !== expectedChainId) { try { + const overrides = { + chainId: expectedChainId, + providerType, + } await Promise.race([ (async () => { await delay(30 /* seconds */ * 1000 /* milliseconds */) throw new Error('Timeout!') })(), networkType === NetworkType.Ethereum - ? Services.Ethereum.switchEthereumChain(ChainId.Mainnet) - : Services.Ethereum.addEthereumChain(chainDetailedCAIP, account), + ? Services.Ethereum.switchEthereumChain(ChainId.Mainnet, overrides) + : Services.Ethereum.addEthereumChain(chainDetailedCAIP, account, overrides), ]) } catch (e) { throw new Error(`Make sure your wallet is on the ${resolveNetworkName(networkType)} network.`) } - - // wait for settings to be synced - await delay(1000) - - return true as const - }, - [networkType], - ) + } + + // update account + await WalletRPC.updateAccount({ + account, + chainId: expectedChainId, + providerType, + networkType, + }) + return true as const + }, [networkType, providerType]) const connection = useAsyncRetry(async () => { if (!open) return true - if (!providerType) throw new Error('Unknown provider type.') // connect to the specific provider - await connectTo(providerType) + await connectTo() // switch to the wallet status dialog closeDialog() diff --git a/packages/maskbook/src/plugins/Wallet/SNSAdaptor/SelectProviderDialog/index.tsx b/packages/maskbook/src/plugins/Wallet/SNSAdaptor/SelectProviderDialog/index.tsx index 5439ac75157b..865bfc82880d 100644 --- a/packages/maskbook/src/plugins/Wallet/SNSAdaptor/SelectProviderDialog/index.tsx +++ b/packages/maskbook/src/plugins/Wallet/SNSAdaptor/SelectProviderDialog/index.tsx @@ -188,9 +188,7 @@ function SelectProviderDialogUI(props: SelectProviderDialogUIProps) { // create a new wallet if (isEnvironment(Environment.ManifestOptions)) history.push(`${DashboardRoute.Wallets}?create=${Date.now()}`) - else { - openCreateImportDialog() - } + else openCreateImportDialog() break case ProviderType.MetaMask: case ProviderType.WalletConnect: diff --git a/packages/maskbook/src/plugins/Wallet/SNSAdaptor/SelectWalletDialog.tsx b/packages/maskbook/src/plugins/Wallet/SNSAdaptor/SelectWalletDialog.tsx index fe01d5b55a53..ab4287d64fde 100644 --- a/packages/maskbook/src/plugins/Wallet/SNSAdaptor/SelectWalletDialog.tsx +++ b/packages/maskbook/src/plugins/Wallet/SNSAdaptor/SelectWalletDialog.tsx @@ -42,6 +42,7 @@ function SelectWalletDialogUI(props: SelectWalletDialogUIProps) { closeDialog() await WalletRPC.updateAccount({ account: address, + chainId: undefined, providerType: ProviderType.Maskbook, networkType, }) diff --git a/packages/maskbook/src/plugins/Wallet/hooks/useAssets.ts b/packages/maskbook/src/plugins/Wallet/hooks/useAssets.ts deleted file mode 100644 index 2db29d80e453..000000000000 --- a/packages/maskbook/src/plugins/Wallet/hooks/useAssets.ts +++ /dev/null @@ -1 +0,0 @@ -export { useAssets } from '@masknet/web3-shared' diff --git a/packages/maskbook/src/plugins/Wallet/hooks/useAssetsFromChain.ts b/packages/maskbook/src/plugins/Wallet/hooks/useAssetsFromChain.ts deleted file mode 100644 index c85f3a152556..000000000000 --- a/packages/maskbook/src/plugins/Wallet/hooks/useAssetsFromChain.ts +++ /dev/null @@ -1 +0,0 @@ -export { useAssetsFromChain } from '@masknet/web3-shared' diff --git a/packages/maskbook/src/plugins/Wallet/hooks/useAssetsFromProvider.ts b/packages/maskbook/src/plugins/Wallet/hooks/useAssetsFromProvider.ts deleted file mode 100644 index b3598fb2c822..000000000000 --- a/packages/maskbook/src/plugins/Wallet/hooks/useAssetsFromProvider.ts +++ /dev/null @@ -1 +0,0 @@ -export { useAssetsFromProvider } from '@masknet/web3-shared' diff --git a/packages/maskbook/src/plugins/Wallet/hooks/useAssetsMerged.ts b/packages/maskbook/src/plugins/Wallet/hooks/useAssetsMerged.ts deleted file mode 100644 index 63d297f214f3..000000000000 --- a/packages/maskbook/src/plugins/Wallet/hooks/useAssetsMerged.ts +++ /dev/null @@ -1 +0,0 @@ -export { useAssetsMerged } from '@masknet/web3-shared' diff --git a/packages/maskbook/src/plugins/Wallet/hooks/useCollectibles.ts b/packages/maskbook/src/plugins/Wallet/hooks/useCollectibles.ts deleted file mode 100644 index 8a9f0c7f981f..000000000000 --- a/packages/maskbook/src/plugins/Wallet/hooks/useCollectibles.ts +++ /dev/null @@ -1 +0,0 @@ -export { useCollectibles } from '@masknet/web3-shared' diff --git a/packages/maskbook/src/plugins/Wallet/hooks/useERC20Tokens.ts b/packages/maskbook/src/plugins/Wallet/hooks/useERC20Tokens.ts deleted file mode 100644 index 142917b75e9b..000000000000 --- a/packages/maskbook/src/plugins/Wallet/hooks/useERC20Tokens.ts +++ /dev/null @@ -1 +0,0 @@ -export { useERC20Tokens, useTrustedERC20Tokens as useTrustedERC20TokensFromDB } from '@masknet/web3-shared' diff --git a/packages/maskbook/src/plugins/Wallet/hooks/useTransactions.ts b/packages/maskbook/src/plugins/Wallet/hooks/useTransactions.ts index 04a40e249857..f0d243f2a534 100644 --- a/packages/maskbook/src/plugins/Wallet/hooks/useTransactions.ts +++ b/packages/maskbook/src/plugins/Wallet/hooks/useTransactions.ts @@ -5,17 +5,19 @@ import type { AsyncStateRetry } from 'react-use/lib/useAsyncRetry' import { WalletRPC } from '../messages' import { currentPortfolioDataProviderSettings } from '../settings' import { PortfolioProvider, Transaction } from '../types' -import { useNetworkType } from '@masknet/web3-shared' +import { getNetworkTypeFromChainId, useChainId } from '@masknet/web3-shared' export function useTransactions( address: string, page?: number, ): AsyncStateRetry<{ transactions: Transaction[]; hasNextPage: boolean }> { + const chainId = useChainId() const provider = useValueRef(currentPortfolioDataProviderSettings) - const network = useNetworkType() return useAsyncRetry(async () => { - if (!address) + const network = getNetworkTypeFromChainId(chainId) + + if (!address || !network) return { transactions: [], hasNextPage: false, @@ -29,5 +31,5 @@ export function useTransactions( default: unreachable(provider) } - }, [address, network, provider, page]) + }, [address, chainId, provider, page]) } diff --git a/packages/maskbook/src/plugins/Wallet/services/account.ts b/packages/maskbook/src/plugins/Wallet/services/account.ts index 8a93349646dc..e5b9bb4c42d1 100644 --- a/packages/maskbook/src/plugins/Wallet/services/account.ts +++ b/packages/maskbook/src/plugins/Wallet/services/account.ts @@ -5,32 +5,60 @@ import { getNetworkTypeFromChainId, getChainIdFromNetworkType, } from '@masknet/web3-shared' +import { EthereumAddress } from 'wallet.ts' +import type { WalletRecord } from '../database/types' import { currentAccountSettings, currentChainIdSettings, currentNetworkSettings, currentProviderSettings, } from '../settings' +import { updateExoticWalletFromSource } from './wallet' export async function updateAccount( options: { + name?: string account?: string chainId?: ChainId networkType?: NetworkType providerType?: ProviderType } = {}, ) { - if (!options?.chainId && options.networkType) options.chainId = getChainIdFromNetworkType(options.networkType) - if (options?.chainId && !options?.networkType) options.networkType = getNetworkTypeFromChainId(options.chainId) - if (options?.account !== undefined) currentAccountSettings.value = options.account - if (options?.chainId) currentChainIdSettings.value = options.chainId - if (options?.providerType) currentProviderSettings.value = options.providerType - if (options?.networkType) currentNetworkSettings.value = options.networkType + if (options.chainId && !options.networkType) options.networkType = getNetworkTypeFromChainId(options.chainId) + if (!options.chainId && options.networkType) options.chainId = getChainIdFromNetworkType(options.networkType) + + const { name, account, chainId, providerType, networkType } = options + + // update wallet in the DB + if (account && providerType && EthereumAddress.isValid(account) && providerType !== ProviderType.Maskbook) { + const updates: Partial = { address: account } + if (name) updates.name = name + await updateExoticWalletFromSource(providerType, new Map([[account, updates]])) + } + + // update global settings + if (account) currentAccountSettings.value = account + if (chainId) currentChainIdSettings.value = chainId + if (providerType) currentProviderSettings.value = providerType + if (networkType) currentNetworkSettings.value = networkType } -export async function resetAccount() { - currentAccountSettings.value = '' - currentChainIdSettings.value = ChainId.Mainnet - currentNetworkSettings.value = NetworkType.Ethereum - currentProviderSettings.value = ProviderType.Maskbook +export async function resetAccount( + options: { + account?: string + chainId?: ChainId + networkType?: NetworkType + providerType?: ProviderType + } = {}, +) { + const { + account = '', + chainId = ChainId.Mainnet, + networkType = NetworkType.Ethereum, + providerType = ProviderType.Maskbook, + } = options + currentAccountSettings.value = account + currentChainIdSettings.value = chainId + currentNetworkSettings.value = networkType + currentProviderSettings.value = providerType } diff --git a/packages/maskbook/src/plugins/Wallet/services/chain.ts b/packages/maskbook/src/plugins/Wallet/services/chain.ts index 046c80274ea1..6b0f541aeba7 100644 --- a/packages/maskbook/src/plugins/Wallet/services/chain.ts +++ b/packages/maskbook/src/plugins/Wallet/services/chain.ts @@ -1,5 +1,5 @@ +import { ProviderType } from '@masknet/web3-shared' import { pollingTask } from '@masknet/shared' -import { ChainId, ProviderType } from '@masknet/web3-shared' import { getBalance, getBlockNumber, resetAllNonce } from '../../../extension/background-script/EthereumService' import { startEffects } from '../../../utils' import { UPDATE_CHAIN_STATE_DELAY } from '../constants' @@ -11,7 +11,6 @@ import { currentChainIdSettings, currentProviderSettings, } from '../settings' -import { updateAccount } from './account' const beats: true[] = [] @@ -19,19 +18,13 @@ export async function kickToUpdateChainState() { beats.push(true) } -export async function updateChainState(chainId?: ChainId) { +export async function updateChainState() { // reset the polling task cause it will be called from service call resetPoolTask() // forget those passed beats beats.length = 0 - // update network type - if (chainId) - await updateAccount({ - chainId, - }) - // update chain state try { const wallet = await getWallet() @@ -69,8 +62,8 @@ effect(() => { // revalidate chain state if the chainId of current provider was changed effect(() => - currentChainIdSettings.addListener((chainId) => { - updateChainState(chainId) + currentChainIdSettings.addListener(() => { + updateChainState() if (currentProviderSettings.value === ProviderType.Maskbook) resetAllNonce() }), ) diff --git a/packages/maskbook/src/web3/UI/EthereumChainBoundary.tsx b/packages/maskbook/src/web3/UI/EthereumChainBoundary.tsx index 51845a8e8449..edd16980b356 100644 --- a/packages/maskbook/src/web3/UI/EthereumChainBoundary.tsx +++ b/packages/maskbook/src/web3/UI/EthereumChainBoundary.tsx @@ -8,6 +8,7 @@ import { isChainIdValid, NetworkType, ProviderType, + resolveNetworkName, useAccount, useChainId, } from '@masknet/web3-shared' @@ -58,8 +59,23 @@ export function EthereumChainBoundary(props: EthereumChainBoundaryProps) { // request ethereum-compatiable network const networkType = getNetworkTypeFromChainId(expectedChainId) if (!networkType) return - if (networkType === NetworkType.Ethereum) await Services.Ethereum.switchEthereumChain(expectedChainId) - else await Services.Ethereum.addEthereumChain(chainDetailedCAIP, account) + try { + const overrides = { + chainId: expectedChainId, + providerType, + } + await Promise.race([ + (async () => { + await delay(30 /* seconds */ * 1000 /* milliseconds */) + throw new Error('Timeout!') + })(), + networkType === NetworkType.Ethereum + ? Services.Ethereum.switchEthereumChain(ChainId.Mainnet, overrides) + : Services.Ethereum.addEthereumChain(chainDetailedCAIP, account, overrides), + ]) + } catch (e) { + throw new Error(`Make sure your wallet is on the ${resolveNetworkName(networkType)} network.`) + } }, [account, isAllowed, providerType, expectedChainId]) // matched diff --git a/packages/web3-constants/data/rpc.json b/packages/web3-constants/data/rpc.json index 074ff1fe1afe..ee94c56b31cc 100644 --- a/packages/web3-constants/data/rpc.json +++ b/packages/web3-constants/data/rpc.json @@ -7,8 +7,8 @@ "Gorli": ["https://goerli.infura.io/v3/d65858b010d249419cf8687eca12b094"], "BSC": ["https://bsc-dataseed.binance.org/"], "BSCT": ["https://data-seed-prebsc-1-s1.binance.org:8545/"], - "Matic": ["https://matic-mainnet--jsonrpc.datahub.figment.io/apikey/ade4322bac48ecb3fad64b13105b5e65/"], - "Mumbai": ["https://matic-mumbai--jsonrpc.datahub.figment.io/apikey/ade4322bac48ecb3fad64b13105b5e65/"] + "Matic": ["https://polygon-mainnet.infura.io/v3/d65858b010d249419cf8687eca12b094"], + "Mumbai": ["https://polygon-mumbai.infura.io/v3/d65858b010d249419cf8687eca12b094"] }, "RPC_WEIGHTS": { "Mainnet": [0, 0, 0, 0], diff --git a/packages/web3-shared/src/context/index.tsx b/packages/web3-shared/src/context/index.tsx index 30917ab71d01..9633044f61d3 100644 --- a/packages/web3-shared/src/context/index.tsx +++ b/packages/web3-shared/src/context/index.tsx @@ -1,4 +1,4 @@ -import { useContext, createContext } from 'react' +import { useMemo, useContext, createContext } from 'react' import { createContainer } from 'unstated-next' import { useSubscription } from 'use-subscription' import type { Web3ProviderType } from './type' @@ -28,7 +28,7 @@ export function useWeb3State() { const networkType = useSubscription(_.networkType) const wallets = useSubscription(_.wallets) const chainId = useSubscription(_.chainId) - const chainDetailed = getChainDetailed(chainId) + const chainDetailed = useMemo(() => getChainDetailed(chainId), [chainId]) const erc20Tokens = useSubscription(_.erc20Tokens) const erc20TokensCount = useSubscription(_.erc20TokensCount) const portfolioProvider = useSubscription(_.portfolioProvider) diff --git a/packages/web3-shared/src/hooks/useAssetsFromProvider.ts b/packages/web3-shared/src/hooks/useAssetsFromProvider.ts index 33f50153eecb..a0009446c097 100644 --- a/packages/web3-shared/src/hooks/useAssetsFromProvider.ts +++ b/packages/web3-shared/src/hooks/useAssetsFromProvider.ts @@ -3,6 +3,7 @@ import { useChainDetailed } from './useChainDetailed' import { useWeb3Context, useWeb3State } from '../context' import { useAsyncRetry } from 'react-use' import { useNetworkType } from './useNetworkType' +import { getNetworkTypeFromChainId } from '../utils' export function useAssetsFromProvider() { const { getAssetList } = useWeb3Context() @@ -14,6 +15,7 @@ export function useAssetsFromProvider() { return useAsyncRetry(async () => { if (!account) return [] if (chainDetailed?.network !== 'mainnet') return [] + if (getNetworkTypeFromChainId(chainDetailed.chainId) !== network) return [] return getAssetList(account.toLowerCase(), network, portfolioProvider) }, [account, network, portfolioProvider, chainDetailed]) } diff --git a/packages/web3-shared/src/hooks/useTokensBalance.ts b/packages/web3-shared/src/hooks/useTokensBalance.ts index c2439263f0fb..c31dfa89c54f 100644 --- a/packages/web3-shared/src/hooks/useTokensBalance.ts +++ b/packages/web3-shared/src/hooks/useTokensBalance.ts @@ -1,6 +1,7 @@ import { useAsyncRetry } from 'react-use' import { useBalanceCheckerContract } from '../contracts/useBalanceChecker' import { useAccount } from './useAccount' +import { useChainId } from './useChainId' /** * Fetch balance of multiple tokens from chain @@ -9,6 +10,7 @@ import { useAccount } from './useAccount' */ export function useTokensBalance(listOfAddress: string[]) { const account = useAccount() + const chainId = useChainId() const balanceCheckerContract = useBalanceCheckerContract() return useAsyncRetry(async () => { @@ -17,5 +19,5 @@ export function useTokensBalance(listOfAddress: string[]) { // cannot check the sender's balance in the same contract from: undefined, }) - }, [account, listOfAddress.join(), balanceCheckerContract]) + }, [chainId, account, listOfAddress.join(), balanceCheckerContract]) }