saepenatus
Version:
Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardised spec compliant web3 providers for all supported wallets, framework agnostic modern javascript UI with code splitting, CSS customization, mul
320 lines (268 loc) • 9.12 kB
text/typescript
import type {
Chain,
WalletInit,
EIP1193Provider,
Platform
} from '@web3-onboard/common'
import type { providers } from 'ethers'
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<Account[]> => {
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<Account[]> => {
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