Skip to content

Commit 98cb684

Browse files
Fix outdated hardware wallet signing example (#4084)
* Fix hardware wallet signing example in tx documentation * Complete example update * remove signature from unsigned tx * fix ledger example * clean up example * lint --------- Co-authored-by: acolytec3 <[email protected]>
1 parent ee42ec5 commit 98cb684

File tree

2 files changed

+119
-27
lines changed

2 files changed

+119
-27
lines changed

packages/tx/README.md

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -441,42 +441,64 @@ To sign a tx with a hardware or external wallet use `tx.getMessageToSign()` to r
441441

442442
A legacy transaction will return a Buffer list of the values, and a Typed Transaction ([EIP-2718](https://eips.ethereum.org/EIPS/eip-2718)) will return the serialized output.
443443

444-
Here is an example of signing txs with `@ledgerhq/hw-app-eth` as of `v6.5.0`:
445-
444+
Here is an example of signing txs with `@ledgerhq/hw-app-eth` with `v6.45.4` and `@ledgerhq/hw-transport-node-hid` with `v6.29.5`:
446445
```ts
447-
import { Chain, Common } from '@ethereumjs/common'
448-
import { LegacyTransaction, FeeMarketEIP1559Transaction } from '@ethereumjs/tx'
446+
// examples/ledgerSigner.mts
447+
448+
import { Chain, Common, Sepolia } from '@ethereumjs/common'
449+
import { createLegacyTx, createFeeMarket1559Tx, type LegacyTx, type FeeMarket1559Tx, type LegacyTxData, type FeeMarketEIP1559TxData } from '@ethereumjs/tx'
449450
import { bytesToHex } from '@ethereumjs/util'
450451
import { RLP } from '@ethereumjs/rlp'
451452
import Eth from '@ledgerhq/hw-app-eth'
453+
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'
452454

453-
const eth = new Eth(transport)
454-
const common = new Common({ chain: Chain.Sepolia })
455+
const transport = await TransportNodeHid.default.open()
456+
const eth = new Eth.default(transport)
457+
const common = new Common({ chain: Sepolia })
455458

456-
let txData: any = { value: 1 }
457-
let tx: LegacyTransaction | FeeMarketEIP1559Transaction
458-
let unsignedTx: Uint8Array[] | Uint8Array
459-
let signedTx: typeof tx
459+
// Signing with the first key of the derivation path
460460
const bip32Path = "44'/60'/0'/0/0"
461461

462+
const legacyTxData: LegacyTxData = {
463+
nonce: '0x0',
464+
gasPrice: '0x09184e72a000',
465+
gasLimit: '0x2710',
466+
to: '0x0000000000000000000000000000000000000000',
467+
value: '0x00',
468+
data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057',
469+
}
470+
471+
const eip1559TxData: FeeMarketEIP1559TxData = {
472+
data: '0x1a8451e600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
473+
gasLimit: '0x02625a00',
474+
maxPriorityFeePerGas: '0x01',
475+
maxFeePerGas: '0xff',
476+
nonce: '0x00',
477+
to: '0xcccccccccccccccccccccccccccccccccccccccc',
478+
value: '0x0186a0',
479+
accessList: [],
480+
type: '0x02',
481+
}
482+
462483
const run = async () => {
463-
// Signing a legacy tx
464-
tx = LegacyTransaction.fromTxData(txData, { common })
465-
tx = tx.getMessageToSign()
466-
// ledger signTransaction API expects it to be serialized
467-
let { v, r, s } = await eth.signTransaction(bip32Path, RLP.encode(tx))
468-
tx.addSignature(v, r, s, true)
469-
let from = tx.getSenderAddress().toString()
470-
console.log(`signedTx: ${bytesToHex(tx.serialize())}\nfrom: ${from}`)
471-
472-
// Signing a 1559 tx
473-
txData = { value: 1 }
474-
tx = FeeMarketEIP1559Transaction.fromTxData(txData, { common })
475-
tx = tx.getMessageToSign()
476-
;({ v, r, s } = await eth.signTransaction(bip32Path, unsignedTx)) // this syntax is: object destructuring - assignment without declaration
477-
tx.addSignature(v, r, s)
478-
from = tx.getSenderAddress().toString()
479-
console.log(`signedTx: ${bytesToHex(tx.serialize())}\nfrom: ${from}`)
484+
// Signing a legacy tx
485+
const tx1 = createLegacyTx(legacyTxData, { common })
486+
const unsignedTx1 = tx1.getMessageToSign()
487+
// Ledger signTransaction API expects it to be serialized
488+
// Ledger returns unprefixed hex strings without 0x for v, r, s values
489+
const { v, r, s } = await eth.signTransaction(bip32Path, bytesToHex(RLP.encode(unsignedTx1)).slice(2), null)
490+
const signedTx1 = tx1.addSignature(BigInt(`0x${v}`), BigInt(`0x${r}`), BigInt(`0x${s}`))
491+
const from = signedTx1.getSenderAddress().toString()
492+
console.log(`signedTx: ${bytesToHex(tx1.serialize())}\nfrom: ${from}`)
493+
494+
// Signing a 1559 tx
495+
const tx2 = createFeeMarket1559Tx(eip1559TxData, { common })
496+
// Ledger returns unprefixed hex strings without 0x for v, r, s values
497+
const unsignedTx2 = tx2.getMessageToSign()
498+
const { v2, r2, s2 } = await eth.signTransaction(bip32Path, bytesToHex(unsignedTx2).slice(2), null)
499+
const signedTx2 = tx2.addSignature(BigInt(`0x${v2}`), BigInt(`0x${r2}`), BigInt(`0x${s2}`))
500+
const from2 = signedTx2.getSenderAddress().toString()
501+
console.log(`signedTx: ${bytesToHex(tx2.serialize())}\nfrom: ${from2}`)
480502
}
481503

482504
run()

packages/tx/examples/ledgerSigner.mts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { Common, Sepolia } from '@ethereumjs/common'
2+
import { RLP } from '@ethereumjs/rlp'
3+
import {
4+
type FeeMarketEIP1559TxData,
5+
type LegacyTxData,
6+
createFeeMarket1559Tx,
7+
createLegacyTx,
8+
} from '@ethereumjs/tx'
9+
import { bytesToHex } from '@ethereumjs/util'
10+
import Eth from '@ledgerhq/hw-app-eth'
11+
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'
12+
13+
const transport = await TransportNodeHid.default.open()
14+
const eth = new Eth.default(transport)
15+
const common = new Common({ chain: Sepolia })
16+
17+
// Signing with the first key of the derivation path
18+
const bip32Path = "44'/60'/0'/0/0"
19+
20+
const legacyTxData: LegacyTxData = {
21+
nonce: '0x0',
22+
gasPrice: '0x09184e72a000',
23+
gasLimit: '0x2710',
24+
to: '0x0000000000000000000000000000000000000000',
25+
value: '0x00',
26+
data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057',
27+
}
28+
29+
const eip1559TxData: FeeMarketEIP1559TxData = {
30+
data: '0x1a8451e600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
31+
gasLimit: '0x02625a00',
32+
maxPriorityFeePerGas: '0x01',
33+
maxFeePerGas: '0xff',
34+
nonce: '0x00',
35+
to: '0xcccccccccccccccccccccccccccccccccccccccc',
36+
value: '0x0186a0',
37+
accessList: [],
38+
type: '0x02',
39+
}
40+
41+
const run = async () => {
42+
// Signing a legacy tx
43+
const tx1 = createLegacyTx(legacyTxData, { common })
44+
const unsignedTx1 = tx1.getMessageToSign()
45+
// Ledger signTransaction API expects it to be serialized
46+
// Ledger returns unprefixed hex strings without 0x for v, r, s values
47+
const { v, r, s } = await eth.signTransaction(
48+
bip32Path,
49+
bytesToHex(RLP.encode(unsignedTx1)).slice(2),
50+
null,
51+
)
52+
const signedTx1 = tx1.addSignature(BigInt(`0x${v}`), BigInt(`0x${r}`), BigInt(`0x${s}`))
53+
const from = signedTx1.getSenderAddress().toString()
54+
console.log(`signedTx: ${bytesToHex(tx1.serialize())}\nfrom: ${from}`)
55+
56+
// Signing a 1559 tx
57+
const tx2 = createFeeMarket1559Tx(eip1559TxData, { common })
58+
// Ledger returns unprefixed hex strings without 0x for v, r, s values
59+
const unsignedTx2 = tx2.getMessageToSign()
60+
const { v2, r2, s2 } = await eth.signTransaction(
61+
bip32Path,
62+
bytesToHex(unsignedTx2).slice(2),
63+
null,
64+
)
65+
const signedTx2 = tx2.addSignature(BigInt(`0x${v2}`), BigInt(`0x${r2}`), BigInt(`0x${s2}`))
66+
const from2 = signedTx2.getSenderAddress().toString()
67+
console.log(`signedTx: ${bytesToHex(tx2.serialize())}\nfrom: ${from2}`)
68+
}
69+
70+
run()

0 commit comments

Comments
 (0)