import type { Chain, WalletInit, EIP1193Provider, Platform } from '@web3-onboard/common' import type { CustomNetwork, Account, ScanAccountsOptions } from '@web3-onboard/hw-common' import { StaticJsonRpcProvider } from '@ethersproject/providers' interface CustomWindow extends Window { ethereum: EIP1193Provider } declare const window: CustomWindow const DEFAULT_BASE_PATH = "m/44'/60'/0'/0/0" const basePaths = [ { label: `D'CENT`, value: DEFAULT_BASE_PATH } ] const assets = [ { label: 'ETH' } ] const generateAccounts = async ( keyring: any, provider: StaticJsonRpcProvider ): Promise => { const accounts = [] const addressList = await keyring.addAccounts() const derivationPath = DEFAULT_BASE_PATH const account = { derivationPath, address: addressList[0], balance: { asset: '', value: await provider.getBalance(addressList[0]) } } accounts.push(account) return accounts } function dcent({ customNetwork, filter, containerElement }: { customNetwork?: CustomNetwork filter?: Platform[] containerElement?: string } = {}): WalletInit { const getIcon = async () => (await import('./icon.js')).default return ({ device }) => { const filtered = Array.isArray(filter) && (filter.includes(device.type) || filter.includes(device.os.name)) if (filtered) return null const isMobile = device.type === 'mobile' let accounts: Account[] | undefined return { label: "D'CENT", getIcon, getInterface: async ({ EventEmitter, chains }) => { const eventEmitter = new EventEmitter() if (isMobile) { const provider = window.ethereum as EIP1193Provider if (isMobile && !provider) { location.replace( 'https://link.dcentwallet.com/DAppBrowser/?url=' + document.location ) } provider.on = eventEmitter.on.bind(eventEmitter) return { provider } } const { StaticJsonRpcProvider } = await import( '@ethersproject/providers' ) const { default: EthDcentKeyring } = await import('eth-dcent-keyring') const dcentKeyring = new EthDcentKeyring({}) const { TransactionFactory: Transaction } = await import( '@ethereumjs/tx' ) const { getCommon, accountSelect } = await import( '@web3-onboard/hw-common' ) const { createEIP1193Provider, ProviderRpcErrorCode, ProviderRpcError } = await import('@web3-onboard/common') let currentChain: Chain = chains[0] const scanAccounts = async ({ chainId }: ScanAccountsOptions): Promise => { currentChain = chains.find(({ id }: Chain) => id === chainId) || currentChain const provider = new StaticJsonRpcProvider(currentChain.rpcUrl) return generateAccounts(dcentKeyring, provider) } const getAccounts = async () => { accounts = await accountSelect({ basePaths, assets, chains, scanAccounts, supportsCustomPath: false, containerElement }) if (accounts.length) { eventEmitter.emit('accountsChanged', [accounts[0].address]) } return accounts } const request = async ({ method, params }: { method: string params: any }) => { const response = await fetch(currentChain.rpcUrl, { method: 'POST', body: JSON.stringify({ id: '42', method, params }) }).then(res => res.json()) if (response.result) { return response.result } else { throw response.error } } const dcentProvider = { request } const provider = createEIP1193Provider(dcentProvider, { eth_requestAccounts: async () => { // Triggers the account select modal if no accounts have been selected const accounts = await getAccounts() if (accounts.length === 0) { throw new ProviderRpcError({ code: ProviderRpcErrorCode.ACCOUNT_ACCESS_REJECTED, message: 'User rejected the request.' }) } return accounts[0] ? [accounts[0].address] : [] }, eth_selectAccounts: async () => { const accounts = await getAccounts() return accounts.map(({ address }) => address) }, eth_accounts: async () => { return accounts && accounts[0].address ? [accounts[0].address] : [] }, eth_chainId: async () => { return currentChain.id }, eth_signTransaction: async ({ params: [transactionObject] }) => { if (!accounts) throw new Error( 'No account selected. Must call eth_requestAccounts first.' ) if (!transactionObject) throw new ProviderRpcError({ message: 'Invalid method parameters', code: ProviderRpcErrorCode.INVALID_PARAMS, data: transactionObject }) const account = accounts.find( account => account.address === transactionObject.from ) || accounts[0] const { address: from } = account // Set the `from` field to the currently selected account transactionObject = { ...transactionObject, from } const chainId = currentChain.hasOwnProperty('id') ? Number.parseInt(currentChain.id) : 1 const common = await getCommon({ customNetwork, chainId }) transactionObject.gasLimit = transactionObject.gas || transactionObject.gasLimit const transaction = Transaction.fromTxData( { ...transactionObject }, { common, freeze: false } ) try { const result = await dcentKeyring.signTransaction( from, transaction ) return `0x${result.serialize().toString('hex')}` } catch (err) { throw err } }, eth_sendTransaction: async ({ baseRequest, params }) => { const signedTx = (await provider.request({ method: 'eth_signTransaction', params })) as string const transactionHash = (await baseRequest({ method: 'eth_sendRawTransaction', params: [signedTx] })) as string return transactionHash }, eth_sign: async ({ params: [address, message] }) => { if (!(accounts && accounts.length && accounts.length > 0)) throw new Error( 'No account selected. Must call eth_requestAccounts first.' ) const account = accounts.find(account => account.address === address) || accounts[0] return dcentKeyring.signMessage(account.address, message) }, personal_sign: async ({ params: [message, address] }) => { if (!(accounts && accounts.length && accounts.length > 0)) throw new Error( 'No account selected. Must call eth_requestAccounts first.' ) const account = accounts.find(account => account.address === address) || accounts[0] return dcentKeyring.signPersonalMessage(account.address, message) }, eth_signTypedData: async ({ params: [address, typedData] }) => { if (!(accounts && accounts.length && accounts.length > 0)) throw new Error( 'No account selected. Must call eth_requestAccounts first.' ) const account = accounts.find(account => account.address === address) || accounts[0] const opt = { version: 'V4' } return dcentKeyring.signTypedData(account.address, typedData, opt) }, wallet_switchEthereumChain: async ({ params: [{ chainId }] }) => { currentChain = chains.find(({ id }) => id === chainId) || currentChain if (!currentChain) throw new Error('chain must be set before switching') eventEmitter.emit('chainChanged', currentChain.id) return null }, wallet_addEthereumChain: null }) provider.on = eventEmitter.on.bind(eventEmitter) return { provider } } } } } export default dcent