@swapkit/wallet-hardware 5.0.0 → 6.0.0

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 (167) 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-vwv6z2mk.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-9gy0xve4.js.map → chunk-5c7b7z2d.js.map} +2 -2
  12. package/dist/chunk-5p3ttw6y.js +4 -0
  13. package/dist/{chunk-n05bv2n5.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-jewm2ag3.js.map +10 -0
  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 -23
  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 -4
  52. package/dist/trezor/index.js +2 -2
  53. package/dist/trezor/index.js.map +3 -3
  54. package/dist/types/helpers/psbt.d.ts +9 -0
  55. package/dist/types/helpers/psbt.d.ts.map +1 -0
  56. package/dist/types/ledger/clients/bitcoin.d.ts +36 -0
  57. package/dist/types/ledger/clients/bitcoin.d.ts.map +1 -0
  58. package/dist/types/ledger/clients/cosmos.d.ts +38 -10
  59. package/dist/types/ledger/clients/cosmos.d.ts.map +1 -1
  60. package/dist/types/ledger/clients/evm.d.ts +37 -46
  61. package/dist/types/ledger/clients/evm.d.ts.map +1 -1
  62. package/dist/types/ledger/clients/near.d.ts +5 -2
  63. package/dist/types/ledger/clients/near.d.ts.map +1 -1
  64. package/dist/types/ledger/clients/sui.d.ts +8 -12
  65. package/dist/types/ledger/clients/sui.d.ts.map +1 -1
  66. package/dist/types/ledger/clients/thorchain/index.d.ts +34 -8
  67. package/dist/types/ledger/clients/thorchain/index.d.ts.map +1 -1
  68. package/dist/types/ledger/clients/thorchain/protocol.d.ts +58 -0
  69. package/dist/types/ledger/clients/thorchain/protocol.d.ts.map +1 -0
  70. package/dist/types/ledger/clients/tron.d.ts +11 -8
  71. package/dist/types/ledger/clients/tron.d.ts.map +1 -1
  72. package/dist/types/ledger/clients/utxo-legacy-adapter.d.ts +3 -2
  73. package/dist/types/ledger/clients/utxo-legacy-adapter.d.ts.map +1 -1
  74. package/dist/types/ledger/clients/utxo-legacy-outputs.d.ts +18 -0
  75. package/dist/types/ledger/clients/utxo-legacy-outputs.d.ts.map +1 -0
  76. package/dist/types/ledger/clients/utxo.d.ts +10 -13
  77. package/dist/types/ledger/clients/utxo.d.ts.map +1 -1
  78. package/dist/types/ledger/clients/xrp.d.ts +4 -1
  79. package/dist/types/ledger/clients/xrp.d.ts.map +1 -1
  80. package/dist/types/ledger/clients/zcash.d.ts +53 -0
  81. package/dist/types/ledger/clients/zcash.d.ts.map +1 -0
  82. package/dist/types/ledger/helpers/dmk.d.ts +24 -0
  83. package/dist/types/ledger/helpers/dmk.d.ts.map +1 -0
  84. package/dist/types/ledger/helpers/executeDeviceAction.d.ts +34 -0
  85. package/dist/types/ledger/helpers/executeDeviceAction.d.ts.map +1 -0
  86. package/dist/types/ledger/helpers/getLedgerAddress.d.ts +1 -1
  87. package/dist/types/ledger/helpers/getLedgerAddress.d.ts.map +1 -1
  88. package/dist/types/ledger/helpers/getLedgerClient.d.ts +10 -3
  89. package/dist/types/ledger/helpers/getLedgerClient.d.ts.map +1 -1
  90. package/dist/types/ledger/helpers/index.d.ts +2 -1
  91. package/dist/types/ledger/helpers/index.d.ts.map +1 -1
  92. package/dist/types/ledger/helpers/ledgerJsDmkBridge.d.ts +26 -0
  93. package/dist/types/ledger/helpers/ledgerJsDmkBridge.d.ts.map +1 -0
  94. package/dist/types/ledger/helpers/ledgerJsDmkTransport.d.ts +13 -0
  95. package/dist/types/ledger/helpers/ledgerJsDmkTransport.d.ts.map +1 -0
  96. package/dist/types/ledger/helpers/lowS.d.ts +6 -0
  97. package/dist/types/ledger/helpers/lowS.d.ts.map +1 -0
  98. package/dist/types/ledger/helpers/rawTx.d.ts +2 -0
  99. package/dist/types/ledger/helpers/rawTx.d.ts.map +1 -0
  100. package/dist/types/ledger/index.d.ts +19 -7
  101. package/dist/types/ledger/index.d.ts.map +1 -1
  102. package/dist/types/ledger/types.d.ts +6 -10
  103. package/dist/types/ledger/types.d.ts.map +1 -1
  104. package/dist/types/trezor/index.d.ts +2 -2
  105. package/dist/types/trezor/index.d.ts.map +1 -1
  106. package/package.json +36 -36
  107. package/src/helpers/psbt.ts +26 -0
  108. package/src/ledger/clients/bitcoin.ts +575 -0
  109. package/src/ledger/clients/cosmos.ts +180 -51
  110. package/src/ledger/clients/evm.ts +238 -195
  111. package/src/ledger/clients/near.ts +43 -13
  112. package/src/ledger/clients/sui.ts +55 -68
  113. package/src/ledger/clients/thorchain/index.ts +210 -86
  114. package/src/ledger/clients/thorchain/protocol.ts +166 -0
  115. package/src/ledger/clients/tron.ts +49 -37
  116. package/src/ledger/clients/utxo-legacy-adapter.ts +35 -32
  117. package/src/ledger/clients/utxo-legacy-outputs.ts +122 -0
  118. package/src/ledger/clients/utxo.ts +108 -206
  119. package/src/ledger/clients/xrp.ts +39 -23
  120. package/src/ledger/clients/zcash.ts +440 -0
  121. package/src/ledger/helpers/dmk.ts +231 -0
  122. package/src/ledger/helpers/executeDeviceAction.ts +201 -0
  123. package/src/ledger/helpers/getLedgerClient.ts +101 -72
  124. package/src/ledger/helpers/index.ts +6 -1
  125. package/src/ledger/helpers/ledgerJsDmkBridge.ts +109 -0
  126. package/src/ledger/helpers/ledgerJsDmkTransport.ts +88 -0
  127. package/src/ledger/helpers/lowS.ts +30 -0
  128. package/src/ledger/helpers/rawTx.ts +12 -0
  129. package/src/ledger/index.ts +564 -369
  130. package/src/ledger/types.ts +15 -10
  131. package/src/trezor/index.ts +117 -25
  132. package/dist/chunk-3yr76n9s.js +0 -5
  133. package/dist/chunk-3yr76n9s.js.map +0 -10
  134. package/dist/chunk-9gy0xve4.js +0 -4
  135. package/dist/chunk-bwf31zxr.js +0 -4
  136. package/dist/chunk-bwf31zxr.js.map +0 -10
  137. package/dist/chunk-egtwbe6q.js +0 -4
  138. package/dist/chunk-egtwbe6q.js.map +0 -10
  139. package/dist/chunk-n05bv2n5.js +0 -5
  140. package/dist/chunk-vwv6z2mk.js +0 -4
  141. package/dist/types/ledger/clients/thorchain/common.d.ts +0 -28
  142. package/dist/types/ledger/clients/thorchain/common.d.ts.map +0 -1
  143. package/dist/types/ledger/clients/thorchain/helpers.d.ts +0 -7
  144. package/dist/types/ledger/clients/thorchain/helpers.d.ts.map +0 -1
  145. package/dist/types/ledger/clients/thorchain/lib.d.ts +0 -22
  146. package/dist/types/ledger/clients/thorchain/lib.d.ts.map +0 -1
  147. package/dist/types/ledger/clients/thorchain/utils.d.ts +0 -2
  148. package/dist/types/ledger/clients/thorchain/utils.d.ts.map +0 -1
  149. package/dist/types/ledger/clients/utxo-psbt.d.ts +0 -18
  150. package/dist/types/ledger/clients/utxo-psbt.d.ts.map +0 -1
  151. package/dist/types/ledger/cosmosTypes.d.ts +0 -40
  152. package/dist/types/ledger/cosmosTypes.d.ts.map +0 -1
  153. package/dist/types/ledger/helpers/getLedgerTransport.d.ts +0 -3
  154. package/dist/types/ledger/helpers/getLedgerTransport.d.ts.map +0 -1
  155. package/dist/types/ledger/helpers/ledgerNetworkInfo.d.ts +0 -14
  156. package/dist/types/ledger/helpers/ledgerNetworkInfo.d.ts.map +0 -1
  157. package/dist/types/ledger/interfaces/CosmosLedgerInterface.d.ts +0 -14
  158. package/dist/types/ledger/interfaces/CosmosLedgerInterface.d.ts.map +0 -1
  159. package/src/ledger/clients/thorchain/common.ts +0 -93
  160. package/src/ledger/clients/thorchain/helpers.ts +0 -120
  161. package/src/ledger/clients/thorchain/lib.ts +0 -258
  162. package/src/ledger/clients/thorchain/utils.ts +0 -69
  163. package/src/ledger/clients/utxo-psbt.ts +0 -172
  164. package/src/ledger/cosmosTypes.ts +0 -98
  165. package/src/ledger/helpers/getLedgerTransport.ts +0 -102
  166. package/src/ledger/helpers/ledgerNetworkInfo.ts +0 -127
  167. package/src/ledger/interfaces/CosmosLedgerInterface.ts +0 -67
@@ -1,3 +1,4 @@
1
+ import type { UserInteractionRequired } from "@ledgerhq/device-management-kit";
1
2
  import type Sui from "@ledgerhq/hw-app-sui";
2
3
  import type Transport from "@ledgerhq/hw-transport";
3
4
  import {
@@ -8,55 +9,57 @@ import {
8
9
  SwapKitError,
9
10
  } from "@swapkit/helpers";
10
11
 
11
- import { getLedgerTransport } from "../helpers/getLedgerTransport";
12
+ import {
13
+ LEDGER_USER_INTERACTION_REQUIRED,
14
+ type LedgerJsClientParams,
15
+ normalizeLedgerJsClientParams,
16
+ runLedgerJsOperation,
17
+ } from "../helpers/ledgerJsDmkBridge";
18
+
19
+ type SuiLedgerParams = LedgerJsClientParams<DerivationPathArray | string>;
12
20
 
13
21
  export class SuiLedgerInterface {
14
- derivationPath: string;
15
- ledgerApp: InstanceType<typeof Sui> | null = null;
22
+ readonly derivationPath: string;
16
23
  address: string | null = null;
17
24
  publicKey: Uint8Array | null = null;
18
- private readonly injectedTransport?: Transport;
25
+ private readonly connection: Omit<SuiLedgerParams, "derivationPath">;
19
26
 
20
- constructor(derivationPath?: DerivationPathArray | string, transport?: Transport) {
27
+ constructor(paramsOrPath?: SuiLedgerParams | DerivationPathArray | string, transport?: Transport) {
28
+ const { derivationPath, ...connection } = normalizeLedgerJsClientParams({ paramsOrPath, transport });
21
29
  this.derivationPath =
22
30
  typeof derivationPath === "string"
23
31
  ? derivationPath
24
32
  : derivationPathToString(derivationPath || NetworkDerivationPath[Chain.Sui]);
25
- this.injectedTransport = transport;
33
+ this.connection = connection;
26
34
  }
27
35
 
28
- /**
29
- * Transform derivation path for SUI Ledger format.
30
- * SUI Ledger expects: 44'/784'/0'/0'/0' (all hardened, no 'm/' prefix)
31
- * Standard format is: m/44'/784'/0'/0/0 (only first 3 hardened, has 'm/' prefix)
32
- */
33
- private getLedgerPath(): string {
34
- return this.derivationPath
35
- .replace(/^m\//, "") // Remove 'm/' prefix
36
- .replace(/\/(\d+)\/(\d+)$/, "/$1'/$2'"); // Make last two components hardened
36
+ // The Sui app wants every level hardened and no `m/`: m/44'/784'/0'/0/0 becomes 44'/784'/0'/0'/0'.
37
+ private getLedgerPath() {
38
+ return this.derivationPath.replace(/^m\//, "").replace(/\/(\d+)\/(\d+)$/, "/$1'/$2'");
37
39
  }
38
40
 
39
- private async createTransportAndLedger() {
40
- if (this.ledgerApp) return;
41
-
42
- const transport = this.injectedTransport ?? (await getLedgerTransport());
41
+ private async runSuiOperation<Output>({
42
+ operation,
43
+ requiredUserInteraction,
44
+ }: {
45
+ operation: (app: InstanceType<typeof Sui>) => Promise<Output>;
46
+ requiredUserInteraction?: UserInteractionRequired;
47
+ }) {
43
48
  const SuiApp = (await import("@ledgerhq/hw-app-sui")).default;
44
- this.ledgerApp = new SuiApp(transport);
49
+ return runLedgerJsOperation({
50
+ appName: "Sui",
51
+ connection: this.connection,
52
+ createApp: (ledgerTransport) => new SuiApp(ledgerTransport),
53
+ operation,
54
+ requiredUserInteraction,
55
+ });
45
56
  }
46
57
 
47
- async connect(): Promise<string> {
48
- await this.createTransportAndLedger();
49
-
50
- if (!this.ledgerApp) {
51
- throw new SwapKitError("wallet_ledger_transport_error");
52
- }
53
-
58
+ async connect() {
54
59
  const ledgerPath = this.getLedgerPath();
55
- const result = await this.ledgerApp.getPublicKey(ledgerPath);
60
+ const result = await this.runSuiOperation({ operation: (app) => app.getPublicKey(ledgerPath) });
56
61
 
57
- if (!result?.publicKey) {
58
- throw new SwapKitError("wallet_ledger_failed_to_get_address");
59
- }
62
+ if (!result?.publicKey) throw new SwapKitError("wallet_ledger_failed_to_get_address");
60
63
 
61
64
  this.publicKey = result.publicKey;
62
65
  this.address = `0x${Buffer.from(result.address).toString("hex")}`;
@@ -64,71 +67,55 @@ export class SuiLedgerInterface {
64
67
  return this.address;
65
68
  }
66
69
 
67
- toSuiAddress(): string {
68
- if (!this.address) {
69
- throw new SwapKitError("wallet_ledger_failed_to_get_address");
70
- }
70
+ toSuiAddress() {
71
+ if (!this.address) throw new SwapKitError("wallet_ledger_failed_to_get_address");
71
72
  return this.address;
72
73
  }
73
74
 
74
- async getAddress(): Promise<string> {
75
+ async getAddress() {
75
76
  if (this.address) return this.address;
76
77
  return await this.connect();
77
78
  }
78
79
 
79
- async signTransaction(
80
- input: Uint8Array | { transaction: Uint8Array },
81
- ): Promise<{ bytes: string; signature: string }> {
80
+ async signTransaction(input: Uint8Array | { transaction: Uint8Array }) {
82
81
  const txBytes = input instanceof Uint8Array ? input : input.transaction;
83
- await this.createTransportAndLedger();
84
-
85
- if (!this.ledgerApp) {
86
- throw new SwapKitError("wallet_ledger_transport_error");
87
- }
88
-
89
- if (!this.publicKey) {
90
- throw new SwapKitError("wallet_ledger_failed_to_get_address");
91
- }
82
+ if (!this.publicKey) throw new SwapKitError("wallet_ledger_failed_to_get_address");
92
83
 
93
84
  try {
94
85
  const ledgerPath = this.getLedgerPath();
95
-
96
- // SUI intent message format for TransactionData:
97
- // [intent_scope=0 (TransactionData), intent_version=0, app_id=0] + transaction_bytes
98
- // The Ledger SUI app expects the INTENT MESSAGE, not raw transaction bytes
86
+ // The app signs the intent message, not the raw bytes. Its three-byte prefix (scope TransactionData,
87
+ // version V0, app Sui) is all zeros, which the fresh buffer already holds.
99
88
  const intentMessage = new Uint8Array(3 + txBytes.length);
100
- intentMessage[0] = 0; // IntentScope: TransactionData
101
- intentMessage[1] = 0; // IntentVersion: V0
102
- intentMessage[2] = 0; // AppId: Sui
103
89
  intentMessage.set(txBytes, 3);
104
90
 
105
- const result = await this.ledgerApp.signTransaction(ledgerPath, intentMessage);
91
+ const result = await this.runSuiOperation({
92
+ operation: (app) => app.signTransaction(ledgerPath, intentMessage),
93
+ requiredUserInteraction: LEDGER_USER_INTERACTION_REQUIRED.SignTransaction,
94
+ });
106
95
 
107
- if (!result?.signature) {
108
- throw new SwapKitError("wallet_ledger_signing_error");
109
- }
96
+ if (!result?.signature) throw new SwapKitError("wallet_ledger_signing_error");
110
97
 
111
- // SUI signature format: [scheme_flag (1 byte)] + [signature (64 bytes)] + [public_key (32 bytes)]
112
- // Scheme flag 0x00 = Ed25519
113
98
  const pubKey = this.publicKey.length === 33 ? this.publicKey.slice(1) : this.publicKey;
114
99
  if (pubKey.length !== 32) {
115
100
  throw new SwapKitError("wallet_ledger_signing_error", { error: "Invalid public key length" });
116
101
  }
117
102
 
103
+ // Sui serialised signature: Ed25519 flag (0x00), 64-byte signature, 32-byte public key.
118
104
  const serializedSignature = new Uint8Array(1 + 64 + 32);
119
- serializedSignature[0] = 0x00; // Ed25519 scheme flag
120
105
  serializedSignature.set(result.signature, 1);
121
106
  serializedSignature.set(pubKey, 65);
122
107
 
123
- const signatureBase64 = Buffer.from(serializedSignature).toString("base64");
124
- const bytesBase64 = Buffer.from(txBytes).toString("base64");
125
-
126
- return { bytes: bytesBase64, signature: signatureBase64 };
108
+ return {
109
+ bytes: Buffer.from(txBytes).toString("base64"),
110
+ signature: Buffer.from(serializedSignature).toString("base64"),
111
+ };
127
112
  } catch (error) {
113
+ if (error instanceof SwapKitError) throw error;
128
114
  throw new SwapKitError("wallet_ledger_signing_error", { error });
129
115
  }
130
116
  }
131
117
  }
132
118
 
133
- export const SuiLedger = (derivationPath?: DerivationPathArray, transport?: Transport) =>
134
- new SuiLedgerInterface(derivationPath, transport);
119
+ export function SuiLedger(paramsOrPath?: SuiLedgerParams | DerivationPathArray, transport?: Transport) {
120
+ return new SuiLedgerInterface(paramsOrPath, transport);
121
+ }
@@ -1,11 +1,36 @@
1
1
  import type { AccountData, AminoSignResponse, StdSignDoc } from "@cosmjs/amino";
2
+ import type { Command, DmkError, UserInteractionRequired } from "@ledgerhq/device-management-kit";
2
3
  import type Transport from "@ledgerhq/hw-transport";
3
- import { base64 } from "@scure/base";
4
+ import { base64, hex } from "@scure/base";
4
5
  import { type DerivationPathArray, NetworkDerivationPath, SwapKitError } from "@swapkit/helpers";
5
6
 
6
- import { CosmosLedgerInterface } from "../../interfaces/CosmosLedgerInterface";
7
- import type { GetAddressAndPubKeyResponse } from "../../types";
8
- import { getSignature } from "./utils";
7
+ import { getLedgerDMKSession, type LedgerDMKSession } from "../../helpers/dmk";
8
+ import {
9
+ executeLedgerDeviceAction,
10
+ LEDGER_USER_INTERACTION_REQUIRED,
11
+ type LedgerDeviceActionStateHandler,
12
+ toLedgerDeviceError,
13
+ ZONDAX_USER_REFUSED_STATUS_WORDS,
14
+ } from "../../helpers/executeDeviceAction";
15
+ import { toLowSSignature } from "../../helpers/lowS";
16
+ import {
17
+ getThorAddressCommand,
18
+ getThorLegacyVersion,
19
+ getThorSignCommands,
20
+ invalidThorAppVersionMessage,
21
+ parseThorAddressResponse,
22
+ sendThorLegacyCommand,
23
+ type ThorCommand,
24
+ } from "./protocol";
25
+
26
+ const THOR_APP_NAME = "THORChain";
27
+
28
+ interface THORChainLedgerParams {
29
+ derivationPath?: DerivationPathArray;
30
+ dmkSession?: LedgerDMKSession;
31
+ onDeviceActionState?: LedgerDeviceActionStateHandler;
32
+ transport?: Transport;
33
+ }
9
34
 
10
35
  type ThorchainAssetObject = { chain?: string; symbol?: string; synth?: boolean; ticker?: string };
11
36
 
@@ -18,9 +43,7 @@ function getAminoAssetDenom(asset: string | ThorchainAssetObject) {
18
43
  const symbol = (asset.symbol || asset.ticker || "").toUpperCase();
19
44
 
20
45
  if (!(chain && symbol)) return symbol || chain || "";
21
- if (asset.synth) return `${chain}/${symbol}`;
22
-
23
- return `${chain}.${symbol}`;
46
+ return `${chain}${asset.synth ? "/" : "."}${symbol}`;
24
47
  }
25
48
 
26
49
  export function normalizeThorchainLedgerSignDoc(signDoc: StdSignDoc): StdSignDoc {
@@ -43,123 +66,224 @@ export function normalizeThorchainLedgerSignDoc(signDoc: StdSignDoc): StdSignDoc
43
66
  };
44
67
  }
45
68
 
46
- export class THORChainLedger extends CosmosLedgerInterface {
47
- private pubKey: string | null = null;
69
+ // cosmjs is loaded lazily: Bun rejects its CJS `require("@scure/base")` next to the ESM-only `@swapkit/*` graph.
70
+ async function getFixedSignature({ signature }: { signature: Uint8Array }) {
71
+ const { Secp256k1Signature } = await import("@cosmjs/crypto");
72
+ try {
73
+ return toLowSSignature(Secp256k1Signature.fromDer(signature).toFixedLength());
74
+ } catch (error) {
75
+ throw new SwapKitError("wallet_ledger_invalid_signature", error);
76
+ }
77
+ }
48
78
 
49
- derivationPath: DerivationPathArray;
79
+ async function executeThorLegacyCommands({ commands, transport }: { commands: ThorCommand[]; transport: Transport }) {
80
+ const version = await getThorLegacyVersion({ transport });
81
+ if (!version.startsWith("2.")) {
82
+ const { InvalidResponseFormatError } = await import("@ledgerhq/device-management-kit");
83
+ throw new InvalidResponseFormatError(invalidThorAppVersionMessage({ version }));
84
+ }
85
+
86
+ let response = new Uint8Array();
87
+ for (const command of commands) {
88
+ response = await sendThorLegacyCommand({ command, transport });
89
+ }
90
+ return response;
91
+ }
92
+
93
+ export class THORChainLedger {
94
+ readonly derivationPath: DerivationPathArray;
95
+ private readonly dmkSession?: LedgerDMKSession;
96
+ private readonly onDeviceActionState?: LedgerDeviceActionStateHandler;
97
+ private pubKey: Uint8Array | undefined;
98
+ private readonly transport?: Transport;
99
+
100
+ constructor({
101
+ derivationPath = NetworkDerivationPath.THOR,
102
+ dmkSession,
103
+ onDeviceActionState,
104
+ transport,
105
+ }: THORChainLedgerParams = {}) {
106
+ if (dmkSession && transport) {
107
+ throw new SwapKitError("wallet_ledger_invalid_params", {
108
+ message: "Provide either a Ledger DMK session or a LedgerJS transport, not both",
109
+ });
110
+ }
50
111
 
51
- constructor(derivationPath: DerivationPathArray = NetworkDerivationPath.THOR, transport?: Transport) {
52
- super(transport);
53
- this.chain = "thor";
54
112
  this.derivationPath = derivationPath;
113
+ this.dmkSession = dmkSession;
114
+ this.onDeviceActionState = onDeviceActionState;
115
+ this.transport = transport;
55
116
  }
56
117
 
57
118
  get pubkey() {
58
- return this.pubKey;
119
+ return this.pubKey ? base64.encode(this.pubKey) : null;
59
120
  }
60
121
 
61
- connect = async () => {
62
- await this.checkOrCreateTransportAndLedger();
63
- const { compressed_pk, bech32_address }: GetAddressAndPubKeyResponse = await this.getAddressAndPubKey();
122
+ private executeCommands = async ({
123
+ commands,
124
+ requiredUserInteraction,
125
+ }: {
126
+ commands: ThorCommand[];
127
+ requiredUserInteraction: UserInteractionRequired;
128
+ }) => {
129
+ if (this.transport) {
130
+ try {
131
+ return await executeThorLegacyCommands({ commands, transport: this.transport });
132
+ } catch (error) {
133
+ // hw-transport rejects with status errors; map them as the DMK action below does.
134
+ throw toLedgerDeviceError(error, { userRefusedStatusWords: ZONDAX_USER_REFUSED_STATUS_WORDS });
135
+ }
136
+ }
64
137
 
65
- this.pubKey = base64.encode(compressed_pk);
138
+ const dmkSession = this.dmkSession ?? (await getLedgerDMKSession());
139
+
140
+ const {
141
+ Apdu,
142
+ CallTaskInAppDeviceAction,
143
+ CommandResultFactory,
144
+ DmkResultFactory,
145
+ InvalidResponseFormatError,
146
+ InvalidStatusWordError,
147
+ isSuccessCommandResult,
148
+ UnknownDeviceExchangeError,
149
+ } = await import("@ledgerhq/device-management-kit");
150
+ const toDmkCommand = (command: ThorCommand): Command<Uint8Array> => {
151
+ const apdu = command.getApdu();
66
152
 
67
- return bech32_address;
68
- };
153
+ return {
154
+ getApdu: () => new Apdu(apdu.cla, apdu.ins, apdu.p1, apdu.p2, apdu.data),
155
+ name: command.name,
156
+ parseResponse: (response) => {
157
+ try {
158
+ return CommandResultFactory<Uint8Array>({ data: command.parseResponse(response) });
159
+ } catch (error) {
160
+ // Keep the status word so rejections and wrong-app errors map to typed SwapKit errors.
161
+ const errorCode = hex.encode(response.statusCode);
162
+ return CommandResultFactory<Uint8Array>({
163
+ error: Object.assign(new InvalidStatusWordError(error instanceof Error ? error.message : String(error)), {
164
+ errorCode,
165
+ }),
166
+ });
167
+ }
168
+ },
169
+ };
170
+ };
69
171
 
70
- getAddressAndPubKey = async () => {
71
- await this.checkOrCreateTransportAndLedger(true);
172
+ const deviceAction = new CallTaskInAppDeviceAction<{ response: Uint8Array }, DmkError, UserInteractionRequired>({
173
+ input: {
174
+ appName: THOR_APP_NAME,
175
+ requiredUserInteraction,
176
+ skipOpenApp: false,
177
+ task: async (internalApi) => {
178
+ try {
179
+ const sessionState = internalApi.getDeviceSessionState();
180
+ const version = "currentApp" in sessionState ? sessionState.currentApp.version : undefined;
181
+ if (!version?.startsWith("2.")) {
182
+ return DmkResultFactory<{ response: Uint8Array }, DmkError>({
183
+ error: new InvalidResponseFormatError(invalidThorAppVersionMessage({ version })),
184
+ });
185
+ }
186
+
187
+ let response: Uint8Array<ArrayBufferLike> = new Uint8Array();
188
+ for (const command of commands) {
189
+ const result = await internalApi.sendCommand(toDmkCommand(command));
190
+ if (!isSuccessCommandResult(result)) {
191
+ return DmkResultFactory<{ response: Uint8Array }, DmkError>({ error: result.error });
192
+ }
193
+ response = result.data;
194
+ }
195
+
196
+ return DmkResultFactory<{ response: Uint8Array }, DmkError>({ data: { response } });
197
+ } catch (error) {
198
+ return DmkResultFactory<{ response: Uint8Array }, DmkError>({
199
+ error: new UnknownDeviceExchangeError(error),
200
+ });
201
+ }
202
+ },
203
+ },
204
+ });
72
205
 
73
- const response: GetAddressAndPubKeyResponse = await this.ledgerApp.getAddressAndPubKey(
74
- this.derivationPath,
75
- this.chain,
76
- );
206
+ const result = await executeLedgerDeviceAction({
207
+ action: () => dmkSession.dmk.executeDeviceAction({ deviceAction, sessionId: dmkSession.sessionId }),
208
+ onDeviceActionState: this.onDeviceActionState,
209
+ userRefusedStatusWords: ZONDAX_USER_REFUSED_STATUS_WORDS,
210
+ });
77
211
 
78
- this.validateResponse(response.return_code, response.error_message);
212
+ return result.response;
213
+ };
79
214
 
80
- return response;
215
+ private getAddressData = async ({ checkOnDevice }: { checkOnDevice: boolean }) => {
216
+ const response = await this.executeCommands({
217
+ commands: [getThorAddressCommand({ checkOnDevice, hrp: "thor", path: this.derivationPath })],
218
+ requiredUserInteraction: checkOnDevice
219
+ ? LEDGER_USER_INTERACTION_REQUIRED.VerifyAddress
220
+ : LEDGER_USER_INTERACTION_REQUIRED.None,
221
+ });
222
+
223
+ return parseThorAddressResponse({ response });
81
224
  };
82
225
 
83
- showAddressAndPubKey = async () => {
84
- await this.checkOrCreateTransportAndLedger(true);
226
+ private signBytes = async ({ message }: { message: Uint8Array }) => {
227
+ const response = await this.executeCommands({
228
+ commands: getThorSignCommands({ message, path: this.derivationPath }),
229
+ requiredUserInteraction: LEDGER_USER_INTERACTION_REQUIRED.SignTransaction,
230
+ });
85
231
 
86
- const response: GetAddressAndPubKeyResponse = await this.ledgerApp.showAddressAndPubKey(
87
- this.derivationPath,
88
- this.chain,
89
- );
232
+ return getFixedSignature({ signature: response });
233
+ };
234
+
235
+ connect = async () => {
236
+ const { address, publicKey } = await this.getAddressData({ checkOnDevice: false });
237
+ this.pubKey = publicKey;
238
+ return address;
239
+ };
90
240
 
91
- this.validateResponse(response.return_code, response.error_message);
241
+ getAddressAndPubKey = async () => {
242
+ const { address, publicKey } = await this.getAddressData({ checkOnDevice: false });
243
+ this.pubKey = publicKey;
92
244
 
93
- return response;
245
+ return { bech32_address: address, compressed_pk: publicKey, error_message: "No errors", return_code: 0x9000 };
94
246
  };
95
247
 
96
- signTransaction = async (rawTx: string, sequence = "0") => {
97
- await this.checkOrCreateTransportAndLedger(true);
248
+ showAddressAndPubKey = async () => {
249
+ const { address, publicKey } = await this.getAddressData({ checkOnDevice: true });
250
+ this.pubKey = publicKey;
98
251
 
99
- const { return_code, error_message, signature } = await this.ledgerApp.sign(this.derivationPath, rawTx);
252
+ return { bech32_address: address, compressed_pk: publicKey, error_message: "No errors", return_code: 0x9000 };
253
+ };
100
254
 
101
- if (!this.pubKey) throw new SwapKitError("wallet_ledger_pubkey_not_found");
255
+ signTransaction = async ({ rawTx, sequence = "0" }: { rawTx: string; sequence?: string }) => {
256
+ if (!this.pubKey) await this.connect();
257
+ const pubKey = this.pubKey;
258
+ if (!pubKey) throw new SwapKitError("wallet_ledger_pubkey_not_found");
102
259
 
103
- this.validateResponse(return_code, error_message);
260
+ const signature = await this.signBytes({ message: new TextEncoder().encode(rawTx) });
104
261
 
105
262
  return [
106
263
  {
107
- pub_key: { type: "tendermint/PubKeySecp256k1", value: this.pubKey },
264
+ pub_key: { type: "tendermint/PubKeySecp256k1", value: base64.encode(pubKey) },
108
265
  sequence,
109
- signature: getSignature(signature),
266
+ signature: base64.encode(signature),
110
267
  },
111
268
  ];
112
269
  };
113
270
 
114
271
  signAmino = async (signerAddress: string, signDoc: StdSignDoc): Promise<AminoSignResponse> => {
115
- await this.checkOrCreateTransportAndLedger(true);
272
+ const account = (await this.getAccounts()).find(({ address }) => address === signerAddress);
273
+ if (!account) throw new SwapKitError("wallet_ledger_address_not_found", { address: signerAddress });
116
274
 
117
- const account = (await this.getAccounts()).find((item) => item.address === signerAddress);
118
- if (!account) {
119
- throw new SwapKitError("wallet_ledger_address_not_found", { address: signerAddress });
120
- }
275
+ const { encodeSecp256k1Signature, serializeSignDoc } = await import("@cosmjs/amino");
121
276
 
122
- const importedAmino = await import("@cosmjs/amino");
123
- const encodeSecp256k1Signature =
124
- importedAmino.encodeSecp256k1Signature ?? importedAmino.default?.encodeSecp256k1Signature;
125
- const serializeSignDoc = importedAmino.serializeSignDoc ?? importedAmino.default?.serializeSignDoc;
126
- const normalizedSignDoc = normalizeThorchainLedgerSignDoc(signDoc);
127
-
128
- const { return_code, error_message, signature } = await this.ledgerApp.sign(
129
- this.derivationPath,
130
- serializeSignDoc(normalizedSignDoc),
131
- );
132
-
133
- this.validateResponse(return_code, error_message);
134
-
135
- return {
136
- signature: encodeSecp256k1Signature(account.pubkey, base64.decode(getSignature(signature))),
137
- // CosmJS rebuilds bodyBytes from the returned `signed` doc. Keep the
138
- // original direct-shaped doc there so MsgDeposit protobuf assets do not
139
- // get round-tripped through the incomplete Amino converter.
140
- signed: signDoc,
141
- };
277
+ const signature = await this.signBytes({ message: serializeSignDoc(normalizeThorchainLedgerSignDoc(signDoc)) });
278
+
279
+ return { signature: encodeSecp256k1Signature(account.pubkey, signature), signed: signDoc };
142
280
  };
143
281
 
144
282
  getAccounts = async (): Promise<readonly AccountData[]> => {
145
- await this.checkOrCreateTransportAndLedger(true);
146
-
147
- const { bech32_address, compressed_pk }: GetAddressAndPubKeyResponse = await this.getAddressAndPubKey();
148
-
149
- this.pubKey = base64.encode(compressed_pk);
150
-
151
- return [{ address: bech32_address, algo: "secp256k1", pubkey: compressed_pk }];
283
+ const { address, publicKey } = await this.getAddressData({ checkOnDevice: false });
284
+ this.pubKey = publicKey;
285
+ return [{ address, algo: "secp256k1", pubkey: publicKey }];
152
286
  };
153
287
 
154
- sign = async (message: string) => {
155
- await this.checkOrCreateTransportAndLedger(true);
156
-
157
- const { return_code, error_message, signature } = await this.ledgerApp.sign(this.derivationPath, message);
158
-
159
- if (!this.pubKey) throw new SwapKitError("wallet_ledger_pubkey_not_found");
160
-
161
- this.validateResponse(return_code, error_message);
162
-
163
- return getSignature(signature);
164
- };
288
+ sign = async (message: string) => base64.encode(await this.signBytes({ message: new TextEncoder().encode(message) }));
165
289
  }