@swapkit/wallet-hardware 5.0.1 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/dist/THIRD_PARTY_LICENSES.md +2344 -0
  2. package/dist/chunk-09e51wma.js +4 -0
  3. package/dist/chunk-09e51wma.js.map +167 -0
  4. package/dist/chunk-0arpsm5f.js +4 -0
  5. package/dist/chunk-0arpsm5f.js.map +10 -0
  6. package/dist/chunk-1amf58aq.js +4 -0
  7. package/dist/{chunk-xkh15edk.js.map → chunk-1amf58aq.js.map} +2 -2
  8. package/dist/chunk-3t17wpjg.js +17 -0
  9. package/dist/chunk-3t17wpjg.js.map +29 -0
  10. package/dist/chunk-5c7b7z2d.js +4 -0
  11. package/dist/{chunk-20wx60bn.js.map → chunk-5c7b7z2d.js.map} +2 -2
  12. package/dist/chunk-5p3ttw6y.js +4 -0
  13. package/dist/{chunk-1jpcb30j.js.map → chunk-5p3ttw6y.js.map} +1 -1
  14. package/dist/chunk-dptbnkzm.js +64 -0
  15. package/dist/chunk-dptbnkzm.js.map +260 -0
  16. package/dist/chunk-ds2m83jp.js +5 -0
  17. package/dist/chunk-ds2m83jp.js.map +11 -0
  18. package/dist/chunk-eenx3cv6.js +4 -0
  19. package/dist/chunk-eenx3cv6.js.map +33 -0
  20. package/dist/chunk-gr3eswf1.js +4 -0
  21. package/dist/chunk-gr3eswf1.js.map +73 -0
  22. package/dist/{chunk-7fsaymh4.js → chunk-hg71hp4m.js} +3 -3
  23. package/dist/{chunk-7fsaymh4.js.map → chunk-hg71hp4m.js.map} +1 -1
  24. package/dist/chunk-jewm2ag3.js +5 -0
  25. package/dist/{chunk-4tbtxdxa.js.map → chunk-jewm2ag3.js.map} +2 -2
  26. package/dist/chunk-jzrytcrf.js +4 -0
  27. package/dist/chunk-jzrytcrf.js.map +12 -0
  28. package/dist/chunk-k3thje6a.js +4 -0
  29. package/dist/chunk-k3thje6a.js.map +9 -0
  30. package/dist/chunk-nhva847d.js +5 -0
  31. package/dist/chunk-nhva847d.js.map +9 -0
  32. package/dist/chunk-tm47brht.js +4 -0
  33. package/dist/chunk-tm47brht.js.map +140 -0
  34. package/dist/chunk-xmvg4cds.js +4 -0
  35. package/dist/chunk-xmvg4cds.js.map +127 -0
  36. package/dist/chunk-xx7cwpzq.js +4 -0
  37. package/dist/chunk-xx7cwpzq.js.map +9 -0
  38. package/dist/chunk-yrxe491r.js +20 -0
  39. package/dist/chunk-yrxe491r.js.map +17 -0
  40. package/dist/index.cjs +2 -2
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/keepkey/index.cjs +2 -2
  43. package/dist/keepkey/index.cjs.map +2 -2
  44. package/dist/keepkey/index.js +2 -2
  45. package/dist/keepkey/index.js.map +2 -2
  46. package/dist/ledger/index.cjs +90 -2
  47. package/dist/ledger/index.cjs.map +830 -24
  48. package/dist/ledger/index.js +2 -2
  49. package/dist/ledger/index.js.map +21 -19
  50. package/dist/trezor/index.cjs +2 -2
  51. package/dist/trezor/index.cjs.map +5 -5
  52. package/dist/trezor/index.js +2 -2
  53. package/dist/trezor/index.js.map +3 -3
  54. package/dist/types/ledger/clients/bitcoin.d.ts +36 -0
  55. package/dist/types/ledger/clients/bitcoin.d.ts.map +1 -0
  56. package/dist/types/ledger/clients/cosmos.d.ts +38 -10
  57. package/dist/types/ledger/clients/cosmos.d.ts.map +1 -1
  58. package/dist/types/ledger/clients/evm.d.ts +37 -46
  59. package/dist/types/ledger/clients/evm.d.ts.map +1 -1
  60. package/dist/types/ledger/clients/near.d.ts +5 -2
  61. package/dist/types/ledger/clients/near.d.ts.map +1 -1
  62. package/dist/types/ledger/clients/sui.d.ts +8 -12
  63. package/dist/types/ledger/clients/sui.d.ts.map +1 -1
  64. package/dist/types/ledger/clients/thorchain/index.d.ts +34 -8
  65. package/dist/types/ledger/clients/thorchain/index.d.ts.map +1 -1
  66. package/dist/types/ledger/clients/thorchain/protocol.d.ts +58 -0
  67. package/dist/types/ledger/clients/thorchain/protocol.d.ts.map +1 -0
  68. package/dist/types/ledger/clients/tron.d.ts +11 -8
  69. package/dist/types/ledger/clients/tron.d.ts.map +1 -1
  70. package/dist/types/ledger/clients/utxo-legacy-adapter.d.ts +3 -2
  71. package/dist/types/ledger/clients/utxo-legacy-adapter.d.ts.map +1 -1
  72. package/dist/types/ledger/clients/utxo-legacy-outputs.d.ts +18 -0
  73. package/dist/types/ledger/clients/utxo-legacy-outputs.d.ts.map +1 -0
  74. package/dist/types/ledger/clients/utxo.d.ts +10 -13
  75. package/dist/types/ledger/clients/utxo.d.ts.map +1 -1
  76. package/dist/types/ledger/clients/xrp.d.ts +4 -1
  77. package/dist/types/ledger/clients/xrp.d.ts.map +1 -1
  78. package/dist/types/ledger/clients/zcash.d.ts +53 -0
  79. package/dist/types/ledger/clients/zcash.d.ts.map +1 -0
  80. package/dist/types/ledger/helpers/dmk.d.ts +24 -0
  81. package/dist/types/ledger/helpers/dmk.d.ts.map +1 -0
  82. package/dist/types/ledger/helpers/executeDeviceAction.d.ts +34 -0
  83. package/dist/types/ledger/helpers/executeDeviceAction.d.ts.map +1 -0
  84. package/dist/types/ledger/helpers/getLedgerAddress.d.ts +1 -1
  85. package/dist/types/ledger/helpers/getLedgerAddress.d.ts.map +1 -1
  86. package/dist/types/ledger/helpers/getLedgerClient.d.ts +10 -3
  87. package/dist/types/ledger/helpers/getLedgerClient.d.ts.map +1 -1
  88. package/dist/types/ledger/helpers/index.d.ts +2 -1
  89. package/dist/types/ledger/helpers/index.d.ts.map +1 -1
  90. package/dist/types/ledger/helpers/ledgerJsDmkBridge.d.ts +26 -0
  91. package/dist/types/ledger/helpers/ledgerJsDmkBridge.d.ts.map +1 -0
  92. package/dist/types/ledger/helpers/ledgerJsDmkTransport.d.ts +13 -0
  93. package/dist/types/ledger/helpers/ledgerJsDmkTransport.d.ts.map +1 -0
  94. package/dist/types/ledger/helpers/lowS.d.ts +6 -0
  95. package/dist/types/ledger/helpers/lowS.d.ts.map +1 -0
  96. package/dist/types/ledger/helpers/rawTx.d.ts +2 -0
  97. package/dist/types/ledger/helpers/rawTx.d.ts.map +1 -0
  98. package/dist/types/ledger/index.d.ts +19 -7
  99. package/dist/types/ledger/index.d.ts.map +1 -1
  100. package/dist/types/ledger/types.d.ts +6 -10
  101. package/dist/types/ledger/types.d.ts.map +1 -1
  102. package/dist/types/trezor/index.d.ts +2 -2
  103. package/dist/types/trezor/index.d.ts.map +1 -1
  104. package/package.json +36 -36
  105. package/src/ledger/clients/bitcoin.ts +575 -0
  106. package/src/ledger/clients/cosmos.ts +180 -51
  107. package/src/ledger/clients/evm.ts +238 -195
  108. package/src/ledger/clients/near.ts +43 -13
  109. package/src/ledger/clients/sui.ts +55 -68
  110. package/src/ledger/clients/thorchain/index.ts +210 -86
  111. package/src/ledger/clients/thorchain/protocol.ts +166 -0
  112. package/src/ledger/clients/tron.ts +49 -37
  113. package/src/ledger/clients/utxo-legacy-adapter.ts +35 -32
  114. package/src/ledger/clients/utxo-legacy-outputs.ts +122 -0
  115. package/src/ledger/clients/utxo.ts +108 -206
  116. package/src/ledger/clients/xrp.ts +39 -23
  117. package/src/ledger/clients/zcash.ts +440 -0
  118. package/src/ledger/helpers/dmk.ts +231 -0
  119. package/src/ledger/helpers/executeDeviceAction.ts +201 -0
  120. package/src/ledger/helpers/getLedgerClient.ts +101 -72
  121. package/src/ledger/helpers/index.ts +6 -1
  122. package/src/ledger/helpers/ledgerJsDmkBridge.ts +109 -0
  123. package/src/ledger/helpers/ledgerJsDmkTransport.ts +88 -0
  124. package/src/ledger/helpers/lowS.ts +30 -0
  125. package/src/ledger/helpers/rawTx.ts +12 -0
  126. package/src/ledger/index.ts +564 -369
  127. package/src/ledger/types.ts +15 -10
  128. package/src/trezor/index.ts +183 -68
  129. package/dist/chunk-1jpcb30j.js +0 -5
  130. package/dist/chunk-20wx60bn.js +0 -4
  131. package/dist/chunk-4tbtxdxa.js +0 -5
  132. package/dist/chunk-7v7cgm69.js +0 -4
  133. package/dist/chunk-7v7cgm69.js.map +0 -10
  134. package/dist/chunk-83g13ck6.js +0 -5
  135. package/dist/chunk-83g13ck6.js.map +0 -10
  136. package/dist/chunk-h2r568rz.js +0 -4
  137. package/dist/chunk-h2r568rz.js.map +0 -10
  138. package/dist/chunk-xkh15edk.js +0 -4
  139. package/dist/types/ledger/clients/thorchain/common.d.ts +0 -28
  140. package/dist/types/ledger/clients/thorchain/common.d.ts.map +0 -1
  141. package/dist/types/ledger/clients/thorchain/helpers.d.ts +0 -7
  142. package/dist/types/ledger/clients/thorchain/helpers.d.ts.map +0 -1
  143. package/dist/types/ledger/clients/thorchain/lib.d.ts +0 -22
  144. package/dist/types/ledger/clients/thorchain/lib.d.ts.map +0 -1
  145. package/dist/types/ledger/clients/thorchain/utils.d.ts +0 -2
  146. package/dist/types/ledger/clients/thorchain/utils.d.ts.map +0 -1
  147. package/dist/types/ledger/clients/utxo-psbt.d.ts +0 -18
  148. package/dist/types/ledger/clients/utxo-psbt.d.ts.map +0 -1
  149. package/dist/types/ledger/cosmosTypes.d.ts +0 -40
  150. package/dist/types/ledger/cosmosTypes.d.ts.map +0 -1
  151. package/dist/types/ledger/helpers/getLedgerTransport.d.ts +0 -3
  152. package/dist/types/ledger/helpers/getLedgerTransport.d.ts.map +0 -1
  153. package/dist/types/ledger/helpers/ledgerNetworkInfo.d.ts +0 -14
  154. package/dist/types/ledger/helpers/ledgerNetworkInfo.d.ts.map +0 -1
  155. package/dist/types/ledger/interfaces/CosmosLedgerInterface.d.ts +0 -14
  156. package/dist/types/ledger/interfaces/CosmosLedgerInterface.d.ts.map +0 -1
  157. package/src/ledger/clients/thorchain/common.ts +0 -93
  158. package/src/ledger/clients/thorchain/helpers.ts +0 -120
  159. package/src/ledger/clients/thorchain/lib.ts +0 -258
  160. package/src/ledger/clients/thorchain/utils.ts +0 -69
  161. package/src/ledger/clients/utxo-psbt.ts +0 -204
  162. package/src/ledger/cosmosTypes.ts +0 -98
  163. package/src/ledger/helpers/getLedgerTransport.ts +0 -102
  164. package/src/ledger/helpers/ledgerNetworkInfo.ts +0 -127
  165. package/src/ledger/interfaces/CosmosLedgerInterface.ts +0 -67
@@ -1,5 +1,5 @@
1
- import type EthereumApp from "@ledgerhq/hw-app-eth";
2
- import type Transport from "@ledgerhq/hw-transport";
1
+ import type { BlindSigningReporter } from "@ledgerhq/context-module";
2
+ import type { TypedDataDomain as LedgerTypedDataDomain, SignerEth } from "@ledgerhq/device-signer-kit-ethereum";
3
3
  import {
4
4
  ChainId,
5
5
  type DerivationPathArray,
@@ -10,66 +10,107 @@ import {
10
10
  import {
11
11
  AbstractSigner,
12
12
  type Provider,
13
+ type TransactionLike,
13
14
  type TransactionRequest,
14
15
  type TypedDataDomain,
15
16
  type TypedDataField,
16
17
  } from "ethers";
17
18
 
18
- import { getLedgerTransport } from "../helpers/getLedgerTransport";
19
- import {
20
- fetchLedgerNetworkCertificate,
21
- fetchLedgerNetworkDescriptor,
22
- provideLedgerCertificate,
23
- provideLedgerNetworkInformation,
24
- } from "../helpers/ledgerNetworkInfo";
25
-
26
- const LEDGER_INCORRECT_DATA = 0x6a80;
27
-
28
- const ARC_CHAIN_ID = Number(ChainId.Arc);
29
-
30
- type LedgerTransactionResolution = Awaited<
31
- ReturnType<typeof import("@ledgerhq/hw-app-eth")["ledgerService"]["resolveTransaction"]>
32
- >;
33
-
34
- function isLedgerIncorrectDataError(error: unknown) {
35
- return (
36
- error instanceof Error &&
37
- "statusCode" in error &&
38
- (error as { statusCode: number }).statusCode === LEDGER_INCORRECT_DATA
39
- );
40
- }
19
+ import { createLedgerSessionSigner, type LedgerDMKSession } from "../helpers/dmk";
20
+ import { executeLedgerDeviceAction, type LedgerDeviceActionStateHandler } from "../helpers/executeDeviceAction";
41
21
 
42
- function hasClearSigningPayload(resolution: LedgerTransactionResolution | null) {
43
- return Object.values(resolution ?? {}).some((entries) => Array.isArray(entries) && entries.length);
22
+ interface EVMLedgerParams {
23
+ dmkSession?: LedgerDMKSession;
24
+ provider: Provider;
25
+ derivationPath?: DerivationPathArray | string;
26
+ chainId?: ChainId;
27
+ onDeviceActionState?: LedgerDeviceActionStateHandler;
28
+ originToken?: string;
44
29
  }
45
30
 
46
- function parseLedgerSignatureV(v: number | string) {
47
- if (typeof v === "number") return v;
31
+ function selectTypedDataTypes({
32
+ primaryType,
33
+ types,
34
+ }: {
35
+ primaryType: string;
36
+ types: Record<string, TypedDataField[]>;
37
+ }) {
38
+ const selectedTypes: Record<string, TypedDataField[]> = {};
39
+
40
+ function addType(typeName: string) {
41
+ if (Object.hasOwn(selectedTypes, typeName) || !Object.hasOwn(types, typeName)) return;
42
+ const fields = types[typeName];
43
+ if (!fields) return;
44
+
45
+ selectedTypes[typeName] = fields;
46
+ for (const field of fields) addType(field.type.replace(/\[[0-9]*\]/g, ""));
47
+ }
48
+
49
+ addType(primaryType);
48
50
 
49
- const hex = v.startsWith("0x") ? v.slice(2) : v;
50
- return Number.parseInt(hex || "0", 16);
51
+ if (!Object.hasOwn(selectedTypes, primaryType)) {
52
+ throw new SwapKitError("wallet_ledger_invalid_params", {
53
+ primaryType,
54
+ reason: "The EIP-712 primary type is not defined",
55
+ });
56
+ }
57
+
58
+ return selectedTypes;
51
59
  }
52
60
 
61
+ const disabledBlindSigningReporter: BlindSigningReporter = {
62
+ // The context module ignores the result; the cast avoids depending on purify-ts for an unused Either.
63
+ report: () => Promise.resolve(undefined as unknown as Awaited<ReturnType<BlindSigningReporter["report"]>>),
64
+ };
65
+
53
66
  class EVMLedgerInterface extends AbstractSigner {
54
67
  chainId: ChainId = ChainId.Ethereum;
55
68
  derivationPath = "";
56
- ledgerApp: InstanceType<typeof EthereumApp> | null = null;
57
- ledgerTimeout = 50000;
58
- private transport?: Transport;
59
- private readonly injectedTransport?: Transport;
60
- private arcNetworkRegistered = false;
69
+ private readonly dmkSession?: LedgerDMKSession;
70
+ private readonly getLedgerSigner: () => Promise<SignerEth>;
71
+ private readonly onDeviceActionState?: LedgerDeviceActionStateHandler;
72
+ private readonly originToken?: string;
61
73
 
62
74
  constructor({
63
75
  provider,
76
+ dmkSession,
64
77
  derivationPath = NetworkDerivationPath.OP,
65
78
  chainId = ChainId.Optimism,
66
- transport,
67
- }: { provider: Provider; derivationPath?: DerivationPathArray | string; chainId?: ChainId; transport?: Transport }) {
79
+ onDeviceActionState,
80
+ originToken,
81
+ }: EVMLedgerParams) {
68
82
  super(provider);
69
83
 
70
84
  this.chainId = chainId || ChainId.Ethereum;
71
- this.derivationPath = typeof derivationPath === "string" ? derivationPath : derivationPathToString(derivationPath);
72
- this.injectedTransport = transport;
85
+ this.derivationPath = (
86
+ typeof derivationPath === "string" ? derivationPath : derivationPathToString(derivationPath)
87
+ ).replace(/^m\//, "");
88
+ this.dmkSession = dmkSession;
89
+ this.onDeviceActionState = onDeviceActionState;
90
+ this.originToken = originToken;
91
+ this.getLedgerSigner = createLedgerSessionSigner({
92
+ build: async (session) => {
93
+ const [{ ContextModuleBuilder, ContextModuleChainID }, { SignerEthBuilder }] = await Promise.all([
94
+ import("@ledgerhq/context-module"),
95
+ import("@ledgerhq/device-signer-kit-ethereum"),
96
+ ]);
97
+ // Same context module the signer kit builds by default, minus the blind-signing reporter that
98
+ // would post chain, target address and device model to Ledger on every signature.
99
+ const contextModule = new ContextModuleBuilder({
100
+ loggerFactory: (tag) => session.dmk.getLoggerFactory()(["ContextModule", tag]),
101
+ originToken,
102
+ })
103
+ .setChain(ContextModuleChainID.Ethereum)
104
+ .setBlindSigningReporter(disabledBlindSigningReporter)
105
+ .build();
106
+ // The signer kit also calls the optional sign reporter after every signature. 2.6.0 leaves it unset
107
+ // and offers no builder setter, so keep it inert in case a later 2.x release wires it up.
108
+ contextModule.signReport = () => Promise.resolve();
109
+
110
+ return new SignerEthBuilder({ ...session, originToken }).withContextModule(contextModule).build();
111
+ },
112
+ dmkSession,
113
+ });
73
114
 
74
115
  Object.defineProperty(this, "provider", { enumerable: true, value: provider || null, writable: false });
75
116
  }
@@ -78,58 +119,47 @@ class EVMLedgerInterface extends AbstractSigner {
78
119
  new EVMLedgerInterface({
79
120
  chainId: this.chainId,
80
121
  derivationPath: this.derivationPath,
122
+ dmkSession: this.dmkSession,
123
+ onDeviceActionState: this.onDeviceActionState,
124
+ originToken: this.originToken,
81
125
  provider,
82
- transport: this.transport ?? this.injectedTransport,
83
126
  });
84
127
 
85
- checkOrCreateTransportAndLedger = async () => {
86
- await this.createTransportAndLedger();
87
- };
88
-
89
- createTransportAndLedger = async () => {
90
- if (this.ledgerApp) return;
91
-
92
- this.transport ||= this.injectedTransport ?? (await getLedgerTransport());
93
- const EthereumApp = (await import("@ledgerhq/hw-app-eth")).default;
94
-
95
- this.ledgerApp = new EthereumApp(this.transport);
96
- };
97
-
98
128
  getAddress = async () => {
99
- const response = await this.getAddressAndPubKey();
100
- if (!response) throw new SwapKitError("wallet_ledger_failed_to_get_address");
101
- return response.address;
129
+ const { address } = await this.getAddressAndPubKey();
130
+ if (!address) throw new SwapKitError("wallet_ledger_failed_to_get_address");
131
+ return address;
102
132
  };
103
133
 
104
134
  getAddressAndPubKey = async () => {
105
- await this.createTransportAndLedger();
106
- return this.ledgerApp?.getAddress(this.derivationPath);
107
- };
135
+ const ledgerSigner = await this.getLedgerSigner();
108
136
 
109
- showAddressAndPubKey = async () => {
110
- await this.createTransportAndLedger();
111
- return this.ledgerApp?.getAddress(this.derivationPath, true);
137
+ return executeLedgerDeviceAction({
138
+ action: () => ledgerSigner.getAddress(this.derivationPath, { chainId: Number(this.chainId) }),
139
+ onDeviceActionState: this.onDeviceActionState,
140
+ });
112
141
  };
113
142
 
114
- signMessage = async (messageHex: string) => {
115
- const { Signature } = await import("ethers");
116
- await this.createTransportAndLedger();
117
-
118
- const sig = await this.ledgerApp?.signPersonalMessage(this.derivationPath, messageHex);
119
-
120
- if (!sig) throw new SwapKitError("wallet_ledger_signing_error");
143
+ showAddressAndPubKey = async () => {
144
+ const ledgerSigner = await this.getLedgerSigner();
121
145
 
122
- sig.r = `0x${sig.r}`;
123
- sig.s = `0x${sig.s}`;
124
- return Signature.from(sig).serialized;
146
+ return executeLedgerDeviceAction({
147
+ action: () =>
148
+ ledgerSigner.getAddress(this.derivationPath, { chainId: Number(this.chainId), checkOnDevice: true }),
149
+ onDeviceActionState: this.onDeviceActionState,
150
+ });
125
151
  };
126
152
 
127
- sendTransaction = async (tx: TransactionRequest): Promise<any> => {
128
- if (!this.provider) throw new SwapKitError("wallet_ledger_no_provider");
129
-
130
- const signedTxHex = await this.signTransaction(tx);
153
+ signMessage = async (message: string | Uint8Array) => {
154
+ const { Signature, toUtf8Bytes } = await import("ethers");
155
+ const messageBytes = typeof message === "string" ? toUtf8Bytes(message) : message;
156
+ const ledgerSigner = await this.getLedgerSigner();
157
+ const signature = await executeLedgerDeviceAction({
158
+ action: () => ledgerSigner.signMessage(this.derivationPath, messageBytes),
159
+ onDeviceActionState: this.onDeviceActionState,
160
+ });
131
161
 
132
- return await this.provider.broadcastTransaction(signedTxHex);
162
+ return Signature.from(signature).serialized;
133
163
  };
134
164
 
135
165
  signTypedData = async (
@@ -139,152 +169,165 @@ class EVMLedgerInterface extends AbstractSigner {
139
169
  explicitPrimaryType?: string,
140
170
  ) => {
141
171
  const { buildEIP712DomainType } = await import("@swapkit/toolboxes/evm");
142
- const { Signature, TypedDataEncoder } = await import("ethers");
143
- await this.createTransportAndLedger();
144
-
172
+ const { hexlify, Signature, TypedDataEncoder } = await import("ethers");
145
173
  const { EIP712Domain: _, ...filteredTypes } = types;
146
174
  const primaryType = explicitPrimaryType ?? TypedDataEncoder.from(filteredTypes).primaryType;
175
+ const resolutionTypes = selectTypedDataTypes({ primaryType, types: filteredTypes });
176
+ const populated = await TypedDataEncoder.resolveNames(domain, resolutionTypes, value, async (name) => {
177
+ const resolvedAddress = await this.resolveName(name);
178
+ if (!resolvedAddress) throw new SwapKitError("wallet_ledger_invalid_params", { name });
179
+ return resolvedAddress;
180
+ });
181
+ const chainId = populated.domain.chainId == null ? undefined : Number(populated.domain.chainId);
147
182
 
148
- let sig: { v: number; s: string; r: string } | undefined;
149
-
150
- try {
151
- sig = await this.ledgerApp?.signEIP712Message(this.derivationPath, {
152
- domain: domain as Record<string, unknown>,
153
- message: value,
154
- primaryType,
155
- types: { EIP712Domain: buildEIP712DomainType(domain), ...filteredTypes },
156
- });
157
- } catch (error) {
158
- const isLedgerDeviceError = error instanceof Error && "statusCode" in error;
159
- if (!isLedgerDeviceError || (isLedgerDeviceError && (error as { statusCode: number }).statusCode === 0x6985)) {
160
- throw error;
161
- }
162
-
163
- const domainSeparator = TypedDataEncoder.hashDomain(domain).slice(2);
164
- const messageHash = TypedDataEncoder.from(filteredTypes).hash(value).slice(2);
165
-
166
- sig = await this.ledgerApp?.signEIP712HashedMessage(this.derivationPath, domainSeparator, messageHash);
183
+ if (chainId !== undefined && !Number.isSafeInteger(chainId)) {
184
+ throw new SwapKitError("wallet_ledger_invalid_params", { chainId: populated.domain.chainId });
167
185
  }
168
186
 
169
- if (!sig) throw new SwapKitError("wallet_ledger_signing_error");
187
+ const ledgerDomain = {
188
+ ...(populated.domain.chainId != null && { chainId }),
189
+ ...(populated.domain.name != null && { name: populated.domain.name }),
190
+ ...(populated.domain.salt != null && { salt: hexlify(populated.domain.salt) }),
191
+ ...(populated.domain.verifyingContract != null && { verifyingContract: populated.domain.verifyingContract }),
192
+ ...(populated.domain.version != null && { version: populated.domain.version }),
193
+ } satisfies LedgerTypedDataDomain;
194
+
195
+ const typedData = {
196
+ domain: ledgerDomain,
197
+ message: populated.value,
198
+ primaryType,
199
+ // Only the primary type and its dependencies: the signer kit's legacy fallback hashes with ethers,
200
+ // which rejects unused types.
201
+ types: { EIP712Domain: buildEIP712DomainType(populated.domain), ...resolutionTypes },
202
+ };
203
+
204
+ const ledgerSigner = await this.getLedgerSigner();
205
+ const signature = await executeLedgerDeviceAction({
206
+ action: () => ledgerSigner.signTypedData(this.derivationPath, typedData),
207
+ onDeviceActionState: this.onDeviceActionState,
208
+ });
170
209
 
171
- sig.r = `0x${sig.r}`;
172
- sig.s = `0x${sig.s}`;
173
- return Signature.from(sig).serialized;
210
+ return Signature.from(signature).serialized;
174
211
  };
175
212
 
176
213
  signTransaction = async (tx: TransactionRequest) => {
177
- const { Transaction } = await import("ethers");
178
- await this.createTransportAndLedger();
179
-
180
- const nonce = tx.nonce ?? undefined;
181
- const transactionCount =
182
- nonce === undefined ? await this.provider?.getTransactionCount(tx.from || (await this.getAddress())) : undefined;
183
-
184
- const baseTx = {
185
- chainId: tx.chainId || this.chainId,
186
- data: tx.data,
187
- gasLimit: tx.gasLimit,
188
- ...(tx.gasPrice && { gasPrice: tx.gasPrice }),
189
- ...(!tx.gasPrice &&
190
- tx.maxFeePerGas && { maxFeePerGas: tx.maxFeePerGas, maxPriorityFeePerGas: tx.maxPriorityFeePerGas }),
191
- nonce: nonce !== undefined ? Number(nonce.toString()) : transactionCount,
192
- to: tx.to?.toString(),
193
- type: tx.type && !Number.isNaN(tx.type) ? tx.type : tx.maxFeePerGas ? 2 : 0,
194
- value: tx.value,
195
- };
196
-
197
- // ledger expects the tx to be serialized without the 0x prefix
198
- const unsignedTx = Transaction.from(baseTx).unsignedSerialized.slice(2);
199
-
200
- const { ledgerService } = await import("@ledgerhq/hw-app-eth");
214
+ const { copyRequest, getAddress, getBytes, resolveAddress, resolveProperties, Transaction } = await import(
215
+ "ethers"
216
+ );
217
+ const request = copyRequest(tx);
218
+
219
+ if (request.type === 4 || request.authorizationList?.length) {
220
+ throw new SwapKitError("wallet_ledger_invalid_params", {
221
+ message: "Ledger Ethereum DSK does not support EIP-7702 transactions",
222
+ });
223
+ }
201
224
 
202
- const chainId = Number(baseTx.chainId);
203
- const isArc = chainId === ARC_CHAIN_ID;
225
+ const expectedChainId = BigInt(this.chainId);
204
226
 
205
- const resolution = await ledgerService
206
- .resolveTransaction(unsignedTx, {}, { erc20: true, externalPlugins: true })
207
- .catch((error) => {
208
- if (!isArc) throw error;
227
+ if (request.chainId != null && request.chainId !== expectedChainId) {
228
+ throw new SwapKitError("wallet_ledger_invalid_params", {
229
+ chainId: request.chainId,
230
+ expectedChainId,
231
+ message: "Transaction chainId does not match the Ledger client chain",
232
+ });
233
+ }
209
234
 
210
- console.warn("Ledger: could not fetch clear-signing metadata for Arc, signing blind");
235
+ const { from, to } = await resolveProperties({
236
+ from: request.from ? resolveAddress(request.from, this) : undefined,
237
+ to: request.to ? resolveAddress(request.to, this) : undefined,
238
+ });
239
+ const signerAddress = from || request.nonce == null ? await this.getAddress() : undefined;
211
240
 
212
- return null;
241
+ if (from && signerAddress && getAddress(from) !== getAddress(signerAddress)) {
242
+ throw new SwapKitError("wallet_ledger_invalid_params", {
243
+ message: "Transaction from address does not match the Ledger account",
213
244
  });
245
+ }
214
246
 
215
- if (isArc && !this.arcNetworkRegistered) await this.registerNetworkOnDevice(chainId);
247
+ if (to) request.to = to;
248
+ delete request.from;
249
+ delete request.authorizationList;
250
+ request.chainId = expectedChainId;
251
+ // ethers infers EIP-2930 for an untyped gasPrice-only request; sign it as legacy, as before the signer kit.
252
+ const isUntypedLegacyRequest =
253
+ request.type == null &&
254
+ request.gasPrice != null &&
255
+ request.accessList == null &&
256
+ request.maxFeePerGas == null &&
257
+ request.maxPriorityFeePerGas == null;
258
+ if (isUntypedLegacyRequest) request.type = 0;
259
+ request.nonce ??= await this.provider?.getTransactionCount(signerAddress ?? (await this.getAddress()));
260
+
261
+ const baseTx = Transaction.from(request as TransactionLike<string>);
262
+ const unsignedTransaction = getBytes(baseTx.unsignedSerialized);
263
+ const ledgerSigner = await this.getLedgerSigner();
264
+ const signature = await executeLedgerDeviceAction({
265
+ action: () => ledgerSigner.signTransaction(this.derivationPath, unsignedTransaction),
266
+ onDeviceActionState: this.onDeviceActionState,
267
+ });
216
268
 
217
- const signature = await this.signWithLedgerApp(unsignedTx, resolution, chainId);
269
+ baseTx.signature = signature;
218
270
 
219
- if (!signature) throw new SwapKitError("wallet_ledger_signing_error");
271
+ return baseTx.serialized;
272
+ };
273
+ }
220
274
 
221
- const { r, s, v } = signature;
275
+ interface LedgerParams {
276
+ dmkSession?: LedgerDMKSession;
277
+ provider: Provider;
278
+ derivationPath?: DerivationPathArray;
279
+ onDeviceActionState?: LedgerDeviceActionStateHandler;
280
+ originToken?: string;
281
+ }
222
282
 
223
- return Transaction.from({ ...baseTx, signature: { r: `0x${r}`, s: `0x${s}`, v: parseLedgerSignatureV(v) } })
224
- .serialized;
225
- };
283
+ export function ArbitrumLedger(params: LedgerParams) {
284
+ return new EVMLedgerInterface({ ...params, chainId: ChainId.Arbitrum });
285
+ }
226
286
 
227
- private signWithLedgerApp = async (
228
- unsignedTx: string,
229
- resolution: LedgerTransactionResolution | null,
230
- chainId: number,
231
- ) => {
232
- try {
233
- return await this.ledgerApp?.signTransaction(this.derivationPath, unsignedTx, resolution);
234
- } catch (error) {
235
- const canSignBlind =
236
- chainId === ARC_CHAIN_ID && isLedgerIncorrectDataError(error) && hasClearSigningPayload(resolution);
287
+ export function ArcLedger(params: LedgerParams) {
288
+ return new EVMLedgerInterface({ ...params, chainId: ChainId.Arc });
289
+ }
237
290
 
238
- if (!canSignBlind) throw error;
291
+ export function AuroraLedger(params: LedgerParams) {
292
+ return new EVMLedgerInterface({ ...params, chainId: ChainId.Aurora });
293
+ }
239
294
 
240
- console.warn("Ledger: the app rejected the Arc metadata, signing blind");
295
+ export function AvalancheLedger(params: LedgerParams) {
296
+ return new EVMLedgerInterface({ ...params, chainId: ChainId.Avalanche });
297
+ }
241
298
 
242
- return await this.ledgerApp?.signTransaction(this.derivationPath, unsignedTx, null);
243
- }
244
- };
299
+ export function BaseLedger(params: LedgerParams) {
300
+ return new EVMLedgerInterface({ ...params, chainId: ChainId.Base });
301
+ }
245
302
 
246
- private registerNetworkOnDevice = async (chainId: number) => {
247
- const deviceModelId = this.transport?.deviceModel?.id;
303
+ export function EthereumLedger(params: LedgerParams) {
304
+ return new EVMLedgerInterface({ ...params, chainId: ChainId.Ethereum });
305
+ }
248
306
 
249
- if (!(this.transport && deviceModelId)) return;
307
+ export function GnosisLedger(params: LedgerParams) {
308
+ return new EVMLedgerInterface({ ...params, chainId: ChainId.Gnosis });
309
+ }
250
310
 
251
- try {
252
- const [descriptor, certificate] = await Promise.all([
253
- fetchLedgerNetworkDescriptor(chainId, deviceModelId),
254
- fetchLedgerNetworkCertificate(deviceModelId),
255
- ]);
311
+ export function OptimismLedger(params: LedgerParams) {
312
+ return new EVMLedgerInterface({ ...params, chainId: ChainId.Optimism });
313
+ }
256
314
 
257
- if (!descriptor) return;
315
+ export function PolygonLedger(params: LedgerParams) {
316
+ return new EVMLedgerInterface({ ...params, chainId: ChainId.Polygon });
317
+ }
258
318
 
259
- if (certificate) await provideLedgerCertificate(this.transport, certificate);
319
+ export function BinanceSmartChainLedger(params: LedgerParams) {
320
+ return new EVMLedgerInterface({ ...params, chainId: ChainId.BinanceSmartChain });
321
+ }
260
322
 
261
- await provideLedgerNetworkInformation(this.transport, descriptor);
323
+ export function MonadLedger(params: LedgerParams) {
324
+ return new EVMLedgerInterface({ ...params, chainId: ChainId.Monad });
325
+ }
262
326
 
263
- this.arcNetworkRegistered = true;
264
- } catch {
265
- // the app will reject the metadata and signWithLedgerApp falls back to blind signing
266
- }
267
- };
327
+ export function XLayerLedger(params: LedgerParams) {
328
+ return new EVMLedgerInterface({ ...params, chainId: ChainId.XLayer });
268
329
  }
269
330
 
270
- type LedgerParams = { provider: Provider; derivationPath?: DerivationPathArray; transport?: Transport };
271
-
272
- export const ArbitrumLedger = (params: LedgerParams) =>
273
- new EVMLedgerInterface({ ...params, chainId: ChainId.Arbitrum });
274
- export const ArcLedger = (params: LedgerParams) => new EVMLedgerInterface({ ...params, chainId: ChainId.Arc });
275
- export const AuroraLedger = (params: LedgerParams) => new EVMLedgerInterface({ ...params, chainId: ChainId.Aurora });
276
- export const AvalancheLedger = (params: LedgerParams) =>
277
- new EVMLedgerInterface({ ...params, chainId: ChainId.Avalanche });
278
- export const BaseLedger = (params: LedgerParams) => new EVMLedgerInterface({ ...params, chainId: ChainId.Base });
279
- export const EthereumLedger = (params: LedgerParams) =>
280
- new EVMLedgerInterface({ ...params, chainId: ChainId.Ethereum });
281
- export const GnosisLedger = (params: LedgerParams) => new EVMLedgerInterface({ ...params, chainId: ChainId.Gnosis });
282
- export const OptimismLedger = (params: LedgerParams) =>
283
- new EVMLedgerInterface({ ...params, chainId: ChainId.Optimism });
284
- export const PolygonLedger = (params: LedgerParams) => new EVMLedgerInterface({ ...params, chainId: ChainId.Polygon });
285
- export const BinanceSmartChainLedger = (params: LedgerParams) =>
286
- new EVMLedgerInterface({ ...params, chainId: ChainId.BinanceSmartChain });
287
- export const MonadLedger = (params: LedgerParams) => new EVMLedgerInterface({ ...params, chainId: ChainId.Monad });
288
- export const XLayerLedger = (params: LedgerParams) => new EVMLedgerInterface({ ...params, chainId: ChainId.XLayer });
289
- export const BerachainLedger = (params: LedgerParams) =>
290
- new EVMLedgerInterface({ ...params, chainId: ChainId.Berachain });
331
+ export function BerachainLedger(params: LedgerParams) {
332
+ return new EVMLedgerInterface({ ...params, chainId: ChainId.Berachain });
333
+ }
@@ -1,18 +1,44 @@
1
+ import type { UserInteractionRequired } from "@ledgerhq/device-management-kit";
2
+ import type Near from "@ledgerhq/hw-app-near";
1
3
  import type Transport from "@ledgerhq/hw-transport";
2
4
  import type { SignedTransaction, Transaction } from "@near-js/transactions";
3
- import type { DerivationPathArray } from "@swapkit/helpers";
5
+ import { Chain, type DerivationPathArray, NetworkDerivationPath, SwapKitError } from "@swapkit/helpers";
4
6
  import type { NearSigner } from "@swapkit/toolboxes/near";
5
- import { getLedgerTransport } from "../helpers/getLedgerTransport";
6
7
 
7
- export async function getNearLedgerClient(derivationPath?: DerivationPathArray, injectedTransport?: Transport) {
8
- const Near = (await import("@ledgerhq/hw-app-near")).default;
9
- const { Chain, NetworkDerivationPath, SwapKitError } = await import("@swapkit/helpers");
10
- const transport = injectedTransport ?? (await getLedgerTransport());
11
- const nearApp = new Near(transport);
8
+ import {
9
+ LEDGER_USER_INTERACTION_REQUIRED,
10
+ type LedgerJsClientParams,
11
+ normalizeLedgerJsClientParams,
12
+ runLedgerJsOperation,
13
+ } from "../helpers/ledgerJsDmkBridge";
12
14
 
15
+ type NearLedgerParams = LedgerJsClientParams<DerivationPathArray>;
16
+
17
+ export async function getNearLedgerClient(
18
+ paramsOrPath?: NearLedgerParams | DerivationPathArray,
19
+ transport?: Transport,
20
+ ) {
21
+ const { derivationPath, ...connection } = normalizeLedgerJsClientParams({ paramsOrPath, transport });
13
22
  const path = (derivationPath || NetworkDerivationPath[Chain.Near]).join("'/").concat("'");
14
23
 
15
- const { address, publicKey: pubKeyHex } = await nearApp.getAddress(path);
24
+ async function runNearOperation<Output>({
25
+ operation,
26
+ requiredUserInteraction,
27
+ }: {
28
+ operation: (app: Near) => Promise<Output>;
29
+ requiredUserInteraction?: UserInteractionRequired;
30
+ }) {
31
+ const NearApp = (await import("@ledgerhq/hw-app-near")).default;
32
+ return runLedgerJsOperation({
33
+ appName: "NEAR",
34
+ connection,
35
+ createApp: (ledgerTransport) => new NearApp(ledgerTransport),
36
+ operation,
37
+ requiredUserInteraction,
38
+ });
39
+ }
40
+
41
+ const { address, publicKey } = await runNearOperation({ operation: (app) => app.getAddress(path) });
16
42
 
17
43
  const signer = {
18
44
  getAddress() {
@@ -20,10 +46,11 @@ export async function getNearLedgerClient(derivationPath?: DerivationPathArray,
20
46
  },
21
47
  async getPublicKey() {
22
48
  const { PublicKey } = await import("@near-js/crypto");
23
- return PublicKey.fromString(`ed25519:${pubKeyHex}`);
49
+ const encodedPublicKey = publicKey.startsWith("ed25519:") ? publicKey : `ed25519:${publicKey}`;
50
+ return PublicKey.fromString(encodedPublicKey);
24
51
  },
25
52
 
26
- signDelegateAction(_delegateAction: any) {
53
+ signDelegateAction(_delegateAction: unknown) {
27
54
  return Promise.reject(
28
55
  new SwapKitError("wallet_ledger_method_not_supported", { method: "signDelegateAction", wallet: "Ledger" }),
29
56
  );
@@ -44,17 +71,20 @@ export async function getNearLedgerClient(derivationPath?: DerivationPathArray,
44
71
  async signTransaction(transaction: Transaction) {
45
72
  const { Signature, SignedTransaction } = await import("@near-js/transactions");
46
73
  try {
47
- const signatureArray = await nearApp.signTransaction(transaction.encode(), path);
74
+ const signatureArray = await runNearOperation({
75
+ operation: (app) => app.signTransaction(transaction.encode(), path),
76
+ requiredUserInteraction: LEDGER_USER_INTERACTION_REQUIRED.SignTransaction,
77
+ });
48
78
  if (!signatureArray) {
49
- throw new Error("Signature undefined");
79
+ throw new SwapKitError("wallet_ledger_signing_error", { reason: "Ledger returned no NEAR signature" });
50
80
  }
51
81
 
52
82
  const signature = new Signature({ data: signatureArray, keyType: 0 });
53
-
54
83
  const signedTransaction = new SignedTransaction({ signature, transaction });
55
84
 
56
85
  return [signatureArray, signedTransaction] as [Uint8Array<ArrayBufferLike>, SignedTransaction];
57
86
  } catch (error) {
87
+ if (error instanceof SwapKitError) throw error;
58
88
  throw new SwapKitError("wallet_ledger_signing_error", { error });
59
89
  }
60
90
  },