Skip to content

Commit 299f9bc

Browse files
authored
[fix]: Hardware Wallet Providers Send Transaction (#894)
* Add eth_sendTransaction method * Update KeepKey provider * Update Keystone provider * Update Ledger provider * Update Trezor provider
1 parent 3feb522 commit 299f9bc

File tree

11 files changed

+296
-153
lines changed

11 files changed

+296
-153
lines changed

packages/common/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@web3-onboard/common",
3-
"version": "2.0.3",
3+
"version": "2.0.4",
44
"scripts": {
55
"build": "rollup -c",
66
"dev": "rollup -c -w",

packages/common/src/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ export type RequestPatch = {
3737
params: EthSignTransactionRequest['params']
3838
}) => Promise<string>)
3939
| null
40+
eth_sendTransaction?:
41+
| ((args: {
42+
baseRequest: EIP1193Provider['request']
43+
params: EthSignTransactionRequest['params']
44+
}) => Promise<string>)
45+
| null
4046
eth_sign?:
4147
| ((args: {
4248
baseRequest: EIP1193Provider['request']
@@ -228,7 +234,7 @@ export type ChainId = string
228234
export type RpcUrl = string
229235

230236
export type WalletInterface = {
231-
provider: EIP1193Provider,
237+
provider: EIP1193Provider
232238
instance?: unknown
233239
}
234240

packages/keepkey/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@web3-onboard/keepkey",
3-
"version": "2.0.0",
3+
"version": "2.0.1",
44
"description": "KeepKey module for web3-onboard",
55
"module": "dist/index.js",
66
"browser": "dist/index.js",

packages/keepkey/src/index.ts

Lines changed: 173 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import type {
33
Account,
44
Asset,
55
Chain,
6-
WalletInit
6+
WalletInit,
7+
EIP1193Provider
78
} from '@web3-onboard/common'
89

910
import type { StaticJsonRpcProvider } from '@ethersproject/providers'
@@ -239,161 +240,193 @@ function keepkey(): WalletInit {
239240
return accounts
240241
}
241242

242-
const provider = createEIP1193Provider(
243-
{},
244-
{
245-
eth_requestAccounts: async () => {
246-
if (keepKeyWallet && typeof keepKeyWallet.cancel === 'function') {
247-
// cancel any current actions on device
248-
keepKeyWallet.cancel()
249-
}
243+
const request: EIP1193Provider['request'] = async ({
244+
method,
245+
params
246+
}) => {
247+
const response = await fetch(currentChain.rpcUrl, {
248+
method: 'POST',
249+
body: JSON.stringify({
250+
id: '42',
251+
method,
252+
params
253+
})
254+
}).then(res => res.json())
250255

251-
try {
252-
keepKeyWallet =
253-
(await keepKeyAdapter.pairDevice()) as KeepKeyHDWallet
254-
} catch (error) {
255-
const { name } = error as { name: string }
256-
// This error indicates that the keepkey is paired with another app
257-
if (name === HDWalletErrorType.ConflictingApp) {
258-
throw new ProviderRpcError({
259-
code: 4001,
260-
message: errorMessages[ERROR_BUSY]
261-
})
262-
263-
// This error indicates that for some reason we can't claim the usb device
264-
} else if (name === HDWalletErrorType.WebUSBCouldNotPair) {
265-
throw new ProviderRpcError({
266-
code: 4001,
267-
message: errorMessages[ERROR_PAIRING]
268-
})
269-
}
270-
}
256+
if (response.result) {
257+
return response.result
258+
} else {
259+
throw response.error
260+
}
261+
}
271262

272-
// Triggers the account select modal if no accounts have been selected
273-
const accounts = await getAccounts()
263+
const keepKeyProvider = { request }
274264

275-
if (!accounts || !Array.isArray(accounts)) {
276-
throw new Error('No accounts were returned from Keepkey device')
277-
}
278-
if (!accounts.length) {
265+
const provider = createEIP1193Provider(keepKeyProvider, {
266+
eth_requestAccounts: async () => {
267+
if (keepKeyWallet && typeof keepKeyWallet.cancel === 'function') {
268+
// cancel any current actions on device
269+
keepKeyWallet.cancel()
270+
}
271+
272+
try {
273+
keepKeyWallet =
274+
(await keepKeyAdapter.pairDevice()) as KeepKeyHDWallet
275+
} catch (error) {
276+
const { name } = error as { name: string }
277+
// This error indicates that the keepkey is paired with another app
278+
if (name === HDWalletErrorType.ConflictingApp) {
279279
throw new ProviderRpcError({
280280
code: 4001,
281-
message: 'User rejected the request.'
281+
message: errorMessages[ERROR_BUSY]
282282
})
283-
}
284-
if (!accounts[0].hasOwnProperty('address')) {
285-
throw new Error(
286-
'The account returned does not have a required address field'
287-
)
288-
}
289283

290-
return [accounts[0].address]
291-
},
292-
eth_selectAccounts: async () => {
293-
const accounts = await getAccounts()
294-
return accounts.map(({ address }) => address)
295-
},
296-
eth_accounts: async () => {
297-
if (!accounts || !Array.isArray(accounts)) {
298-
throw new Error('No accounts were returned from Keepkey device')
284+
// This error indicates that for some reason we can't claim the usb device
285+
} else if (name === HDWalletErrorType.WebUSBCouldNotPair) {
286+
throw new ProviderRpcError({
287+
code: 4001,
288+
message: errorMessages[ERROR_PAIRING]
289+
})
299290
}
300-
return accounts[0].hasOwnProperty('address')
301-
? [accounts[0].address]
302-
: []
303-
},
304-
eth_chainId: async () => {
305-
return currentChain && currentChain.id != undefined
306-
? currentChain.id
307-
: '0x0'
308-
},
309-
eth_signTransaction: async ({ params: [transactionObject] }) => {
310-
if (!accounts || !Array.isArray(accounts) || !accounts.length)
311-
throw new Error(
312-
'No account selected. Must call eth_requestAccounts first.'
313-
)
314-
315-
const account =
316-
!transactionObject || !transactionObject.hasOwnProperty('from')
317-
? accounts[0]
318-
: accounts.find(
319-
account => account.address === transactionObject.from
320-
)
321-
322-
const { derivationPath } = account || accounts[0]
323-
const addressNList = bip32ToAddressNList(derivationPath)
324-
325-
const {
326-
nonce,
327-
gasPrice,
328-
gas,
329-
gasLimit,
330-
to,
331-
value,
332-
data,
333-
maxFeePerGas,
334-
maxPriorityFeePerGas
335-
} = transactionObject
336-
337-
const { serialized } = await keepKeyWallet.ethSignTx({
338-
addressNList,
339-
nonce: nonce || '0x0',
340-
gasPrice,
341-
gasLimit: gasLimit || gas || '0x5208',
342-
to,
343-
value: value || '0x0',
344-
data: data || '',
345-
maxFeePerGas,
346-
maxPriorityFeePerGas,
347-
chainId: parseInt(currentChain.id)
291+
}
292+
293+
// Triggers the account select modal if no accounts have been selected
294+
const accounts = await getAccounts()
295+
296+
if (!accounts || !Array.isArray(accounts)) {
297+
throw new Error('No accounts were returned from Keepkey device')
298+
}
299+
if (!accounts.length) {
300+
throw new ProviderRpcError({
301+
code: 4001,
302+
message: 'User rejected the request.'
348303
})
304+
}
305+
if (!accounts[0].hasOwnProperty('address')) {
306+
throw new Error(
307+
'The account returned does not have a required address field'
308+
)
309+
}
349310

350-
return serialized
351-
},
352-
eth_sign: async ({ params: [address, message] }) => {
353-
if (
354-
!accounts ||
355-
!Array.isArray(accounts) ||
356-
!(accounts.length && accounts.length > 0)
311+
return [accounts[0].address]
312+
},
313+
eth_selectAccounts: async () => {
314+
const accounts = await getAccounts()
315+
return accounts.map(({ address }) => address)
316+
},
317+
eth_accounts: async () => {
318+
if (!accounts || !Array.isArray(accounts)) {
319+
throw new Error('No accounts were returned from Keepkey device')
320+
}
321+
return accounts[0].hasOwnProperty('address')
322+
? [accounts[0].address]
323+
: []
324+
},
325+
eth_chainId: async () => {
326+
return currentChain && currentChain.id != undefined
327+
? currentChain.id
328+
: '0x0'
329+
},
330+
eth_signTransaction: async ({ params: [transactionObject] }) => {
331+
if (!accounts || !Array.isArray(accounts) || !accounts.length)
332+
throw new Error(
333+
'No account selected. Must call eth_requestAccounts first.'
357334
)
358-
throw new Error(
359-
'No account selected. Must call eth_requestAccounts first.'
360-
)
361335

362-
const account =
363-
accounts.find(account => account.address === address) ||
364-
accounts[0]
336+
const account =
337+
!transactionObject || !transactionObject.hasOwnProperty('from')
338+
? accounts[0]
339+
: accounts.find(
340+
account => account.address === transactionObject.from
341+
)
342+
343+
const { derivationPath } = account || accounts[0]
344+
const addressNList = bip32ToAddressNList(derivationPath)
345+
346+
const {
347+
nonce,
348+
gasPrice,
349+
gas,
350+
gasLimit,
351+
to,
352+
value,
353+
data,
354+
maxFeePerGas,
355+
maxPriorityFeePerGas
356+
} = transactionObject
357+
358+
const { serialized } = await keepKeyWallet.ethSignTx({
359+
addressNList,
360+
nonce: nonce || '0x0',
361+
gasPrice,
362+
gasLimit: gasLimit || gas || '0x5208',
363+
to,
364+
value: value || '0x0',
365+
data: data || '',
366+
maxFeePerGas,
367+
maxPriorityFeePerGas,
368+
chainId: parseInt(currentChain.id)
369+
})
365370

366-
const { derivationPath } = account
367-
const accountIdx = getAccountIdx(derivationPath)
368-
const { addressNList } = getPaths(accountIdx)
369-
370-
const { signature } = await keepKeyWallet.ethSignMessage({
371-
addressNList,
372-
message:
373-
message.slice(0, 2) === '0x'
374-
? // @ts-ignore - commonjs weirdness
375-
(ethUtil.default || ethUtil)
376-
.toBuffer(message)
377-
.toString('utf8')
378-
: message
379-
})
371+
return serialized
372+
},
373+
eth_sendTransaction: async ({ baseRequest, params }) => {
374+
const signedTx = await provider.request({
375+
method: 'eth_signTransaction',
376+
params
377+
})
380378

381-
return signature
382-
},
383-
eth_signTypedData: null,
384-
wallet_switchEthereumChain: async ({ params: [{ chainId }] }) => {
385-
currentChain =
386-
chains.find(({ id }) => id === chainId) || currentChain
379+
const transactionHash = await baseRequest({
380+
method: 'eth_sendRawTransaction',
381+
params: [signedTx]
382+
})
387383

388-
if (!currentChain)
389-
throw new Error('chain must be set before switching')
384+
return transactionHash as string
385+
},
386+
eth_sign: async ({ params: [address, message] }) => {
387+
if (
388+
!accounts ||
389+
!Array.isArray(accounts) ||
390+
!(accounts.length && accounts.length > 0)
391+
)
392+
throw new Error(
393+
'No account selected. Must call eth_requestAccounts first.'
394+
)
390395

391-
eventEmitter.emit('chainChanged', currentChain.id)
392-
return null
393-
},
394-
wallet_addEthereumChain: null
395-
}
396-
)
396+
const account =
397+
accounts.find(account => account.address === address) ||
398+
accounts[0]
399+
400+
const { derivationPath } = account
401+
const accountIdx = getAccountIdx(derivationPath)
402+
const { addressNList } = getPaths(accountIdx)
403+
404+
const { signature } = await keepKeyWallet.ethSignMessage({
405+
addressNList,
406+
message:
407+
message.slice(0, 2) === '0x'
408+
? // @ts-ignore - commonjs weirdness
409+
(ethUtil.default || ethUtil)
410+
.toBuffer(message)
411+
.toString('utf8')
412+
: message
413+
})
414+
415+
return signature
416+
},
417+
eth_signTypedData: null,
418+
wallet_switchEthereumChain: async ({ params: [{ chainId }] }) => {
419+
currentChain =
420+
chains.find(({ id }) => id === chainId) || currentChain
421+
422+
if (!currentChain)
423+
throw new Error('chain must be set before switching')
424+
425+
eventEmitter.emit('chainChanged', currentChain.id)
426+
return null
427+
},
428+
wallet_addEthereumChain: null
429+
})
397430

398431
provider.on = eventEmitter.on.bind(eventEmitter)
399432

packages/keystone/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@web3-onboard/keystone",
3-
"version": "2.0.0",
3+
"version": "2.0.1",
44
"description": "Keystone module for web3-onboard",
55
"module": "dist/index.js",
66
"typings": "dist/index.d.ts",

0 commit comments

Comments
 (0)