@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,21 +1,45 @@
1
+ import type { UserInteractionRequired } from "@ledgerhq/device-management-kit";
1
2
  import type BitcoinApp from "@ledgerhq/hw-app-btc";
2
3
  import type { CreateTransactionArg } from "@ledgerhq/hw-app-btc/lib-es/createTransaction";
3
4
  import type Transport from "@ledgerhq/hw-transport";
4
5
  import { hex } from "@scure/base";
5
6
  import { type DerivationPathArray, derivationPathToString, getWalletFormatFor, SwapKitError } from "@swapkit/helpers";
6
7
  import type { UTXOType } from "@swapkit/toolboxes/utxo";
7
- import type { PCZT, Transaction } from "@swapkit/utxo-signer";
8
+ import type { Transaction } from "@swapkit/utxo-signer";
8
9
 
9
- import { getLedgerTransport } from "../helpers/getLedgerTransport";
10
+ import {
11
+ LEDGER_USER_INTERACTION_REQUIRED,
12
+ type LedgerJsClientParams,
13
+ normalizeLedgerJsClientParams,
14
+ runLedgerJsOperation,
15
+ } from "../helpers/ledgerJsDmkBridge";
16
+ import { splitOutputsForLegacyApp, withOutputAlignedFinalize } from "./utxo-legacy-outputs";
10
17
 
11
18
  const nonSegwitLedgerChains = ["bitcoin-cash", "dash", "dogecoin", "zcash"];
12
19
 
20
+ type LedgerUTXOChain = "bitcoin-cash" | "bitcoin" | "litecoin" | "dogecoin" | "dash" | "zcash";
21
+ type UTXOLedgerParams = LedgerJsClientParams<DerivationPathArray | string>;
22
+
23
+ // The Bitcoin Cash, Dash, Dogecoin and Litecoin apps are built on lib-app-bitcoin, which checks every output in a
24
+ // 100-byte buffer (see utxo-legacy-outputs). Bitcoin signs through app-bitcoin-new, and clients/zcash.ts refuses to sign
25
+ // over the legacy Zcash client.
26
+ const libAppBitcoinChains: LedgerUTXOChain[] = ["bitcoin-cash", "dash", "dogecoin", "litecoin"];
27
+
28
+ const ledgerAppNames: Record<LedgerUTXOChain, string> = {
29
+ bitcoin: "Bitcoin",
30
+ "bitcoin-cash": "Bitcoin Cash",
31
+ dash: "Dash",
32
+ dogecoin: "Dogecoin",
33
+ litecoin: "Litecoin",
34
+ zcash: "Zcash",
35
+ };
36
+
13
37
  type Params = {
14
- tx: Transaction;
38
+ outputScriptHex: string;
15
39
  inputUtxos: UTXOType[];
16
40
  btcApp: BitcoinApp;
17
41
  derivationPath: string;
18
- chain: "bitcoin-cash" | "bitcoin" | "litecoin" | "dogecoin" | "dash" | "zcash";
42
+ chain: LedgerUTXOChain;
19
43
  };
20
44
 
21
45
  type MultiPathParams = Omit<Params, "derivationPath"> & {
@@ -24,7 +48,7 @@ type MultiPathParams = Omit<Params, "derivationPath"> & {
24
48
  };
25
49
 
26
50
  const signUTXOTransaction = (
27
- { tx, inputUtxos, btcApp, derivationPath, chain }: Params,
51
+ { outputScriptHex, inputUtxos, btcApp, derivationPath, chain }: Params,
28
52
  options?: Partial<CreateTransactionArg>,
29
53
  ) => {
30
54
  const inputs = inputUtxos.map((item) => {
@@ -37,11 +61,6 @@ const signUTXOTransaction = (
37
61
  return [splitTx, item.index, undefined as string | null | undefined, undefined as number | null | undefined] as any;
38
62
  });
39
63
 
40
- const newTxHex = hex.encode(tx.unsignedTx);
41
-
42
- const splitNewTx = btcApp.splitTransaction(newTxHex, true);
43
- const outputScriptHex = btcApp.serializeTransactionOutputs(splitNewTx).toString("hex");
44
-
45
64
  // hw-app-btc derives the signing policy from these flags:
46
65
  // additionals ["bech32"] + segwit → wpkh (native segwit, m/84')
47
66
  // no additionals + segwit → sh(wpkh) (P2SH-P2WPKH, m/49')
@@ -66,7 +85,7 @@ const signUTXOTransaction = (
66
85
  * Each input can have its own derivation path for HD wallet multi-address support.
67
86
  */
68
87
  const signUTXOTransactionWithMultiplePaths = (
69
- { tx, inputUtxos, btcApp, derivationPaths, chain }: MultiPathParams,
88
+ { outputScriptHex, inputUtxos, btcApp, derivationPaths, chain }: MultiPathParams,
70
89
  options?: Partial<CreateTransactionArg>,
71
90
  ) => {
72
91
  if (derivationPaths.length !== inputUtxos.length) {
@@ -85,11 +104,6 @@ const signUTXOTransactionWithMultiplePaths = (
85
104
  return [splitTx, item.index, undefined as string | null | undefined, undefined as number | null | undefined] as any;
86
105
  });
87
106
 
88
- const newTxHex = hex.encode(tx.unsignedTx);
89
-
90
- const splitNewTx = btcApp.splitTransaction(newTxHex, true);
91
- const outputScriptHex = btcApp.serializeTransactionOutputs(splitNewTx).toString("hex");
92
-
93
107
  // Same policy/path matching rules as signUTXOTransaction; all paths share
94
108
  // one account so the first path determines the format.
95
109
  const format = getWalletFormatFor(derivationPaths[0] ?? "");
@@ -110,52 +124,67 @@ const BaseLedgerUTXO = ({
110
124
  chain,
111
125
  additionalSignParams,
112
126
  }: {
113
- chain: "bitcoin-cash" | "bitcoin" | "litecoin" | "dogecoin" | "dash" | "zcash";
127
+ chain: LedgerUTXOChain;
114
128
  additionalSignParams?: Partial<CreateTransactionArg>;
115
129
  }) => {
116
- return (derivationPathArray?: DerivationPathArray | string, injectedTransport?: Transport) => {
117
- // Per-call state — each BitcoinLedger/LitecoinLedger/... invocation has its own
118
- // transport + btcApp so different consumers (e.g. concurrent MCP sessions) cannot
119
- // cross-contaminate each other's Ledger device handle.
120
- let btcApp: InstanceType<typeof BitcoinApp> | undefined;
121
- let transport: any = null;
122
-
123
- async function createTransportWebUSB() {
124
- transport ||= injectedTransport ?? (await getLedgerTransport());
125
- const BitcoinApp = (await import("@ledgerhq/hw-app-btc")).default;
130
+ const usesLibAppBitcoin = libAppBitcoinChains.includes(chain);
126
131
 
127
- btcApp ||= new BitcoinApp({ currency: chain, transport });
128
- return btcApp;
129
- }
132
+ return (paramsOrPath?: UTXOLedgerParams | DerivationPathArray | string, transport?: Transport) => {
133
+ const { derivationPath: derivationPathParam, ...connection } = normalizeLedgerJsClientParams({
134
+ paramsOrPath,
135
+ transport,
136
+ });
137
+ const derivationPath =
138
+ typeof derivationPathParam === "string"
139
+ ? derivationPathParam
140
+ : derivationPathToString(derivationPathParam as DerivationPathArray);
141
+ const format = getWalletFormatFor(derivationPath);
130
142
 
131
- async function getBtcApp() {
132
- return btcApp || (await createTransportWebUSB());
143
+ async function runBtcOperation<Output>({
144
+ operation,
145
+ requiredUserInteraction,
146
+ }: {
147
+ operation: (app: InstanceType<typeof BitcoinApp>) => Promise<Output> | Output;
148
+ requiredUserInteraction?: UserInteractionRequired;
149
+ }) {
150
+ const BitcoinApp = (await import("@ledgerhq/hw-app-btc")).default;
151
+ return runLedgerJsOperation({
152
+ appName: ledgerAppNames[chain],
153
+ connection,
154
+ // A wrapper per operation, so no collected output chunk outlives the operation that sent it.
155
+ createApp: (ledgerTransport) =>
156
+ new BitcoinApp({
157
+ currency: chain,
158
+ transport: usesLibAppBitcoin ? withOutputAlignedFinalize(ledgerTransport, chain) : ledgerTransport,
159
+ }),
160
+ operation,
161
+ requiredUserInteraction,
162
+ });
133
163
  }
134
164
 
135
- async function disconnect() {
136
- if (!injectedTransport) await transport?.close?.();
137
- btcApp = undefined;
138
- transport = null;
165
+ // The outputs hw-app-btc streams to the app, checked before any device call so that an output the app cannot
166
+ // buffer fails with a typed error rather than a 0x6a80 from the device.
167
+ async function getOutputScriptHex(tx: Transaction): Promise<string> {
168
+ const [{ splitTransaction }, { serializeTransactionOutputs }] = await Promise.all([
169
+ import("@ledgerhq/hw-app-btc/splitTransaction"),
170
+ import("@ledgerhq/hw-app-btc/serializeTransaction"),
171
+ ]);
172
+ const outputs = serializeTransactionOutputs(splitTransaction(hex.encode(tx.unsignedTx), true));
173
+ if (usesLibAppBitcoin) splitOutputsForLegacyApp(outputs, chain);
174
+
175
+ return outputs.toString("hex");
139
176
  }
140
177
 
141
- const derivationPath =
142
- typeof derivationPathArray === "string"
143
- ? derivationPathArray
144
- : derivationPathToString(derivationPathArray as DerivationPathArray);
145
-
146
- const format = getWalletFormatFor(derivationPath);
147
-
148
178
  return {
149
179
  connect: async () => {
150
- await getBtcApp();
180
+ await runBtcOperation({ operation: async () => true });
151
181
  },
152
- disconnect,
182
+ disconnect: async () => {},
153
183
  getAddress: async () => {
154
184
  const { toCashAddress } = await import("@swapkit/toolboxes/utxo");
155
-
156
- const app = await getBtcApp();
157
-
158
- const { bitcoinAddress: address } = await app.getWalletPublicKey(derivationPath, { format });
185
+ const { bitcoinAddress: address } = await runBtcOperation({
186
+ operation: (app) => app.getWalletPublicKey(derivationPath, { format }),
187
+ });
159
188
 
160
189
  if (!address) {
161
190
  throw new SwapKitError("wallet_ledger_get_address_error", {
@@ -168,91 +197,19 @@ const BaseLedgerUTXO = ({
168
197
  : address;
169
198
  },
170
199
  getExtendedPublicKey: async (path = "84'/0'/0'", xpubVersion = 76067358) => {
171
- const app = await getBtcApp();
172
-
173
- return app.getWalletXpub({ path, xpubVersion });
200
+ return await runBtcOperation({ operation: (app) => app.getWalletXpub({ path, xpubVersion }) });
174
201
  },
175
202
 
176
- signPCZT: async (pczt: PCZT): Promise<PCZT> => {
177
- if (chain !== "zcash") {
178
- throw new SwapKitError("wallet_ledger_chain_not_supported", {
179
- message: "PCZT signing is only supported for Zcash",
180
- });
181
- }
182
-
183
- const app = await getBtcApp();
184
-
185
- const { ZcashTransaction, Script } = await import("@swapkit/utxo-signer");
186
-
187
- const global = pczt.getGlobal();
188
-
189
- const unsignedTx = new ZcashTransaction({
190
- consensusBranchId: global.consensusBranchId,
191
- expiryHeight: global.expiryHeight,
192
- lockTime: global.lockTime,
193
- version: global.txVersion,
194
- versionGroupId: global.versionGroupId,
195
- });
196
-
197
- const inputUtxos: UTXOType[] = [];
198
-
199
- for (let i = 0; i < pczt.inputsLength; i++) {
200
- const input = pczt.getInput(i);
201
-
202
- unsignedTx.addInput({
203
- index: input.index,
204
- script: new Uint8Array(),
205
- sequence: input.sequence ?? 0xffffffff,
206
- txid: input.txid,
207
- value: input.value,
208
- });
209
-
210
- inputUtxos.push({
211
- hash: hex.encode(new Uint8Array([...input.txid].reverse())),
212
- index: input.index,
213
- txHex: buildMinimalPrevTxHex(input, global),
214
- value: Number(input.value),
215
- witnessUtxo: { script: input.scriptPubkey, value: Number(input.value) },
216
- } as UTXOType);
217
- }
218
-
219
- for (let i = 0; i < pczt.outputsLength; i++) {
220
- const output = pczt.getOutput(i);
221
- unsignedTx.addOutput({ amount: output.value, script: output.scriptPubkey });
222
- }
223
-
224
- const signedTxHex = await signUTXOTransaction(
225
- { btcApp: app, chain, derivationPath, inputUtxos, tx: unsignedTx as unknown as Transaction },
226
- {
227
- ...additionalSignParams,
228
- expiryHeight: (() => {
229
- const buf = Buffer.alloc(4);
230
- buf.writeUInt32LE(global.expiryHeight);
231
- return buf;
232
- })(),
233
- lockTime: global.lockTime,
234
- },
235
- );
236
-
237
- const signedTx = ZcashTransaction.fromHex(signedTxHex, { allowUnknownOutputs: true });
238
- const signedPczt = pczt.clone();
239
-
240
- for (let i = 0; i < signedTx.inputsLength; i++) {
241
- const signedInput = signedTx.getInput(i);
242
- if (signedInput.script && signedInput.script.length > 0) {
243
- const scriptParts = Script.decode(signedInput.script);
244
- if (scriptParts.length >= 2) {
245
- signedPczt.addSignature(i, scriptParts[1] as Uint8Array, scriptParts[0] as Uint8Array);
246
- }
247
- }
248
- }
249
-
250
- return signedPczt;
251
- },
252
203
  signTransaction: async (tx: Transaction, inputUtxos: UTXOType[]) => {
253
- const app = await getBtcApp();
254
-
255
- return signUTXOTransaction({ btcApp: app, chain, derivationPath, inputUtxos, tx }, additionalSignParams);
204
+ const outputScriptHex = await getOutputScriptHex(tx);
205
+ return await runBtcOperation({
206
+ operation: (app) =>
207
+ signUTXOTransaction(
208
+ { btcApp: app, chain, derivationPath, inputUtxos, outputScriptHex },
209
+ additionalSignParams,
210
+ ),
211
+ requiredUserInteraction: LEDGER_USER_INTERACTION_REQUIRED.SignTransaction,
212
+ });
256
213
  },
257
214
 
258
215
  /**
@@ -260,90 +217,35 @@ const BaseLedgerUTXO = ({
260
217
  * Each input can be signed with its own derivation path.
261
218
  */
262
219
  signTransactionWithMultiplePaths: async (tx: Transaction, inputUtxos: UTXOType[], derivationPaths: string[]) => {
263
- const app = await getBtcApp();
264
-
265
- return signUTXOTransactionWithMultiplePaths(
266
- { btcApp: app, chain, derivationPaths, inputUtxos, tx },
267
- additionalSignParams,
268
- );
220
+ const outputScriptHex = await getOutputScriptHex(tx);
221
+ return await runBtcOperation({
222
+ operation: (app) =>
223
+ signUTXOTransactionWithMultiplePaths(
224
+ { btcApp: app, chain, derivationPaths, inputUtxos, outputScriptHex },
225
+ additionalSignParams,
226
+ ),
227
+ requiredUserInteraction: LEDGER_USER_INTERACTION_REQUIRED.SignTransaction,
228
+ });
269
229
  },
270
230
  };
271
231
  };
272
232
  };
273
233
 
274
- function buildMinimalPrevTxHex(
275
- input: { txid: Uint8Array; index: number; scriptPubkey: Uint8Array; value: bigint },
276
- global: { txVersion: number; versionGroupId: number; expiryHeight: number; lockTime: number },
277
- ): string {
278
- const parts: number[] = [];
279
-
280
- const version = (global.txVersion | 0x80000000) >>> 0;
281
- parts.push(version & 0xff, (version >> 8) & 0xff, (version >> 16) & 0xff, (version >> 24) & 0xff);
282
-
283
- const vgid = global.versionGroupId;
284
- parts.push(vgid & 0xff, (vgid >> 8) & 0xff, (vgid >> 16) & 0xff, (vgid >> 24) & 0xff);
285
-
286
- parts.push(0);
287
-
288
- const outputCount = input.index + 1;
289
- if (outputCount < 0xfd) {
290
- parts.push(outputCount);
291
- } else {
292
- parts.push(0xfd, outputCount & 0xff, (outputCount >> 8) & 0xff);
293
- }
294
-
295
- for (let i = 0; i < input.index; i++) {
296
- parts.push(0, 0, 0, 0, 0, 0, 0, 0);
297
- parts.push(0);
298
- }
299
-
300
- const value = input.value;
301
- parts.push(
302
- Number(value & 0xffn),
303
- Number((value >> 8n) & 0xffn),
304
- Number((value >> 16n) & 0xffn),
305
- Number((value >> 24n) & 0xffn),
306
- Number((value >> 32n) & 0xffn),
307
- Number((value >> 40n) & 0xffn),
308
- Number((value >> 48n) & 0xffn),
309
- Number((value >> 56n) & 0xffn),
310
- );
311
-
312
- const script = input.scriptPubkey;
313
- if (script.length < 0xfd) {
314
- parts.push(script.length);
315
- } else {
316
- parts.push(0xfd, script.length & 0xff, (script.length >> 8) & 0xff);
317
- }
318
- for (const byte of script) {
319
- parts.push(byte);
320
- }
321
-
322
- parts.push(
323
- global.lockTime & 0xff,
324
- (global.lockTime >> 8) & 0xff,
325
- (global.lockTime >> 16) & 0xff,
326
- (global.lockTime >> 24) & 0xff,
327
- );
328
- parts.push(
329
- global.expiryHeight & 0xff,
330
- (global.expiryHeight >> 8) & 0xff,
331
- (global.expiryHeight >> 16) & 0xff,
332
- (global.expiryHeight >> 24) & 0xff,
333
- );
334
- parts.push(0, 0, 0, 0, 0, 0, 0, 0); // value balance
335
- parts.push(0); // empty sapling spends
336
- parts.push(0); // empty sapling outputs
337
- parts.push(0); // empty joinsplits
338
-
339
- return hex.encode(new Uint8Array(parts));
340
- }
341
-
342
234
  export const BitcoinLedger = BaseLedgerUTXO({ chain: "bitcoin" });
343
235
  export const LitecoinLedger = BaseLedgerUTXO({ chain: "litecoin" });
344
236
 
237
+ // "abc" makes hw-app-btc sign with BIP143, which SIGHASH_ALL | SIGHASH_FORKID (0x41) requires, and "cashaddr" opens
238
+ // the transaction with HASH_INPUT_START P2 0x03 so the app shows the outputs as CashAddr, as the dApp does. Trusted
239
+ // inputs (useTrustedInputForSegwit covers every BIP143 transaction) let the app take each amount from a previous
240
+ // transaction it hashed itself; without them it warns about unverified inputs once per transaction. Ledger Live signs
241
+ // CashAddr recipients the same way, with trusted inputs from app 1.4.0.
345
242
  export const BitcoinCashLedger = BaseLedgerUTXO({
346
- additionalSignParams: { additionals: ["abc"], segwit: false, sigHashType: 0x41 },
243
+ additionalSignParams: {
244
+ additionals: ["abc", "cashaddr"],
245
+ segwit: false,
246
+ sigHashType: 0x41,
247
+ useTrustedInputForSegwit: true,
248
+ },
347
249
  chain: "bitcoin-cash",
348
250
  });
349
251
 
@@ -1,45 +1,61 @@
1
- import Xrp from "@ledgerhq/hw-app-xrp";
1
+ import type { UserInteractionRequired } from "@ledgerhq/device-management-kit";
2
+ import type Xrp from "@ledgerhq/hw-app-xrp";
2
3
  import type Transport from "@ledgerhq/hw-transport";
3
4
  import { Chain, type DerivationPathArray, derivationPathToString, NetworkDerivationPath } from "@swapkit/helpers";
4
5
  import type { RippleTransaction } from "@swapkit/toolboxes/ripple";
5
- import { encode } from "ripple-binary-codec";
6
6
  import type { Payment } from "xrpl";
7
- import { getLedgerTransport } from "../helpers/getLedgerTransport";
7
+
8
+ import {
9
+ LEDGER_USER_INTERACTION_REQUIRED,
10
+ type LedgerJsClientParams,
11
+ normalizeLedgerJsClientParams,
12
+ runLedgerJsOperation,
13
+ } from "../helpers/ledgerJsDmkBridge";
8
14
 
9
15
  const TF_FULLY_CANONICAL_SIG = 2147483648;
10
16
 
11
- function cleanTransactionObject(obj: Record<string, any>) {
12
- const cleaned: Record<string, any> = {};
13
- for (const key in obj) {
14
- if (obj[key] !== null && obj[key] !== undefined) {
15
- cleaned[key] = obj[key];
16
- }
17
- }
18
- return cleaned;
19
- }
17
+ type XRPLedgerParams = LedgerJsClientParams<DerivationPathArray>;
20
18
 
21
- function establishConnection(transport: Transport) {
22
- return new Xrp(transport);
19
+ function cleanTransactionObject(transaction: object): Record<string, unknown> {
20
+ return Object.fromEntries(Object.entries(transaction).filter(([, value]) => value !== null && value !== undefined));
23
21
  }
24
22
 
25
- export const XRPLedger = async (derivationPath?: DerivationPathArray, injectedTransport?: Transport) => {
23
+ export async function XRPLedger(paramsOrPath?: XRPLedgerParams | DerivationPathArray, transport?: Transport) {
24
+ const { derivationPath, ...connection } = normalizeLedgerJsClientParams({ paramsOrPath, transport });
26
25
  const path = derivationPathToString(derivationPath || NetworkDerivationPath[Chain.Ripple]);
27
- const transport = injectedTransport ?? (await getLedgerTransport());
28
- const xrpInstance = establishConnection(transport);
29
26
 
30
- const { address, publicKey } = await xrpInstance.getAddress(path);
27
+ async function runXrpOperation<Output>({
28
+ operation,
29
+ requiredUserInteraction,
30
+ }: {
31
+ operation: (app: Xrp) => Promise<Output>;
32
+ requiredUserInteraction?: UserInteractionRequired;
33
+ }) {
34
+ const XrpApp = (await import("@ledgerhq/hw-app-xrp")).default;
35
+ return runLedgerJsOperation({
36
+ appName: "XRP",
37
+ connection,
38
+ createApp: (ledgerTransport) => new XrpApp(ledgerTransport),
39
+ operation,
40
+ requiredUserInteraction,
41
+ });
42
+ }
43
+
44
+ const { address, publicKey } = await runXrpOperation({ operation: (app) => app.getAddress(path) });
31
45
 
32
46
  async function signTransaction(transaction: Payment | RippleTransaction) {
33
- const { hashes } = await import("xrpl");
34
- const cleanedTxWithPubKey = cleanTransactionObject(transaction);
47
+ const [{ encode }, { hashes }] = await Promise.all([import("ripple-binary-codec"), import("xrpl")]);
35
48
  const transactionJSON = {
36
- ...cleanedTxWithPubKey,
49
+ ...cleanTransactionObject(transaction),
37
50
  Flags: transaction.Flags || TF_FULLY_CANONICAL_SIG,
38
51
  SigningPubKey: publicKey.toUpperCase(),
39
52
  };
40
53
 
41
54
  const transactionToSignOnLedger = encode(transactionJSON);
42
- const txnSignature = await xrpInstance.signTransaction(path, transactionToSignOnLedger);
55
+ const txnSignature = await runXrpOperation({
56
+ operation: (app) => app.signTransaction(path, transactionToSignOnLedger),
57
+ requiredUserInteraction: LEDGER_USER_INTERACTION_REQUIRED.SignTransaction,
58
+ });
43
59
  const tx_blob = encode({ ...transactionJSON, TxnSignature: txnSignature });
44
60
  const hash = hashes.hashSignedTx(tx_blob);
45
61
 
@@ -47,4 +63,4 @@ export const XRPLedger = async (derivationPath?: DerivationPathArray, injectedTr
47
63
  }
48
64
 
49
65
  return { getAddress: () => address, signTransaction };
50
- };
66
+ }