@swapkit/wallet-hardware 4.9.9 → 4.9.11

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.
@@ -1,4 +1,4 @@
1
- import { HDKey } from "@scure/bip32";
1
+ import { HDKey, type Versions } from "@scure/bip32";
2
2
  import {
3
3
  Chain,
4
4
  type DerivationPathArray,
@@ -6,6 +6,7 @@ import {
6
6
  FeeOption,
7
7
  filterSupportedChains,
8
8
  type GenericTransferParams,
9
+ NetworkDerivationPath,
9
10
  SKConfig,
10
11
  SwapKitError,
11
12
  type UTXOChain,
@@ -14,7 +15,6 @@ import {
14
15
  import {
15
16
  assertDerivationIndex,
16
17
  createHDWalletHelpers,
17
- deriveAddressesFromXpub,
18
18
  getNetworkForChain,
19
19
  getUTXOAccountIndexFromPath,
20
20
  getUTXOAccountPath,
@@ -23,12 +23,13 @@ import {
23
23
  type UTXOType,
24
24
  } from "@swapkit/toolboxes/utxo";
25
25
  import type { BTCNetwork, PCZT, Transaction, ZcashTransaction } from "@swapkit/utxo-signer";
26
- import { NETWORKS, ZcashConsensusBranchId, ZcashVersionGroupId } from "@swapkit/utxo-signer";
27
- import { createWallet, getWalletSupportedChains } from "@swapkit/wallet-core";
26
+ import { BCHSigHash, NETWORKS, ZcashConsensusBranchId, ZcashVersionGroupId } from "@swapkit/utxo-signer";
27
+ import { createWallet, getWalletSupportedChains, type HardwareExtendedPublicKeyInfo } from "@swapkit/wallet-core";
28
28
 
29
29
  type TrezorBip32Derivation = [Uint8Array, { fingerprint: number; path: number[] }];
30
30
  type TrezorCoreMode = "auto" | "iframe" | "popup" | "suite-desktop" | "suite-web";
31
31
  type TrezorTransport = "BridgeTransport" | "WebUsbTransport" | "NodeUsbTransport";
32
+ type ConnectTrezorOptions = { address?: string };
32
33
  type TrezorExtendedPublicKeyInfo = {
33
34
  accountIndex: number;
34
35
  chainCode?: string;
@@ -44,8 +45,16 @@ const TREZOR_CORE_MODES = new Set<TrezorCoreMode>(["auto", "iframe", "popup", "s
44
45
  const TREZOR_TRANSPORTS = new Set<TrezorTransport>(["BridgeTransport", "WebUsbTransport", "NodeUsbTransport"]);
45
46
  const DEFAULT_TREZOR_MANIFEST = { appName: "SwapKit", appUrl: "https://swapkit.dev", email: "support@swapkit.dev" };
46
47
  const DEFAULT_TREZOR_TRANSPORTS = ["WebUsbTransport" as const];
48
+ const TREZOR_KEEP_SESSION_PARAMS = { keepSession: true } as const;
47
49
  const trezorXpubCache = new Map<string, TrezorExtendedPublicKeyInfo>();
48
50
  let trezorSessionDispose: Promise<void> | undefined;
51
+ const EXTENDED_KEY_VERSION_CANDIDATES = [
52
+ NETWORKS.bitcoin.bip32,
53
+ NETWORKS.bitcoinCash.bip32,
54
+ NETWORKS.dash.bip32,
55
+ NETWORKS.dogecoin.bip32,
56
+ NETWORKS.litecoin.bip32,
57
+ ];
49
58
 
50
59
  async function disconnectTrezorSession() {
51
60
  trezorXpubCache.clear();
@@ -94,6 +103,98 @@ function getDefaultTrezorAppUrl() {
94
103
  : DEFAULT_TREZOR_MANIFEST.appUrl;
95
104
  }
96
105
 
106
+ async function initTrezorConnect() {
107
+ const TrezorConnect = (await import("@trezor/connect-web")).default;
108
+
109
+ const trezorConfig = SKConfig.get("integrations").trezor as Record<string, unknown> | undefined;
110
+ const {
111
+ connectSrc,
112
+ coreMode,
113
+ debug,
114
+ interactionTimeout,
115
+ lazyLoad,
116
+ pendingTransportEvent,
117
+ popup,
118
+ transportReconnect,
119
+ transports,
120
+ ...manifestConfig
121
+ } = trezorConfig ?? {};
122
+ const manifest = {
123
+ ...manifestConfig,
124
+ appName: getTrezorManifestValue(trezorConfig?.appName, DEFAULT_TREZOR_MANIFEST.appName),
125
+ appUrl: getTrezorManifestValue(trezorConfig?.appUrl, getDefaultTrezorAppUrl()),
126
+ email: getTrezorManifestValue(trezorConfig?.email, DEFAULT_TREZOR_MANIFEST.email),
127
+ };
128
+ const isLocalhost =
129
+ typeof globalThis.location !== "undefined" && ["localhost", "127.0.0.1"].includes(globalThis.location.hostname);
130
+ const resolvedCoreMode = normalizeTrezorCoreMode(coreMode) ?? "popup";
131
+ const resolvedTransports = normalizeTrezorTransports(transports) ?? DEFAULT_TREZOR_TRANSPORTS;
132
+
133
+ if (trezorSessionDispose) {
134
+ await trezorSessionDispose;
135
+ }
136
+
137
+ if (isLocalhost) {
138
+ await TrezorConnect.dispose();
139
+ }
140
+
141
+ await TrezorConnect.init({
142
+ connectSrc: connectSrc as string | undefined,
143
+ coreMode: resolvedCoreMode,
144
+ debug: debug as boolean | undefined,
145
+ interactionTimeout: interactionTimeout as number | undefined,
146
+ lazyLoad: (lazyLoad as boolean | undefined) ?? false,
147
+ manifest,
148
+ pendingTransportEvent: pendingTransportEvent as boolean | undefined,
149
+ popup: (popup as boolean | undefined) ?? true,
150
+ transportReconnect: transportReconnect as boolean | undefined,
151
+ transports: resolvedTransports,
152
+ });
153
+
154
+ return { coreMode: resolvedCoreMode, isLocalhost, popup: (popup as boolean | undefined) ?? true, TrezorConnect };
155
+ }
156
+
157
+ export function normalizeTrezorExtendedPublicKey(xpub: string, chain: UTXOChain) {
158
+ const targetVersions = getNetworkForChain(chain).bip32;
159
+ const candidates = [
160
+ targetVersions,
161
+ ...EXTENDED_KEY_VERSION_CANDIDATES.filter(
162
+ (versions) => versions.public !== targetVersions.public || versions.private !== targetVersions.private,
163
+ ),
164
+ ];
165
+ let lastError: unknown;
166
+
167
+ for (const versions of candidates) {
168
+ try {
169
+ const key = HDKey.fromExtendedKey(xpub, versions as Versions);
170
+ if (!(key.publicKey && key.chainCode)) throw new Error("Extended key is missing public key data");
171
+
172
+ return new HDKey({
173
+ chainCode: key.chainCode,
174
+ depth: key.depth,
175
+ index: key.index,
176
+ parentFingerprint: key.parentFingerprint,
177
+ publicKey: key.publicKey,
178
+ versions: targetVersions,
179
+ }).publicExtendedKey;
180
+ } catch (error) {
181
+ lastError = error;
182
+ }
183
+ }
184
+
185
+ throw lastError instanceof Error ? lastError : new Error("Unable to parse Trezor extended public key");
186
+ }
187
+
188
+ function tryNormalizeTrezorExtendedPublicKey(xpub: string | undefined, chain: UTXOChain) {
189
+ if (!xpub) return undefined;
190
+
191
+ try {
192
+ return normalizeTrezorExtendedPublicKey(xpub, chain);
193
+ } catch {
194
+ return xpub;
195
+ }
196
+ }
197
+
97
198
  function decodeOpReturnData(script: Uint8Array): string | null {
98
199
  if (script.length < 2 || script[0] !== 0x6a) return null;
99
200
  const dataLen = script[1];
@@ -151,12 +252,12 @@ function getPrevoutAmount(input: {
151
252
  return undefined;
152
253
  }
153
254
 
154
- function normalizeTrezorSignature(signatureHex: string) {
255
+ export function normalizeTrezorSignature(signatureHex: string, chain: Chain) {
155
256
  const signature = Buffer.from(signatureHex, "hex");
156
257
  const derLength = signature[1] !== undefined ? signature[1] + 2 : undefined;
157
258
 
158
259
  if (derLength !== undefined && signature.length === derLength) {
159
- return new Uint8Array([...signature, 0x01]);
260
+ return new Uint8Array([...signature, chain === Chain.BitcoinCash ? BCHSigHash.ALL : 0x01]);
160
261
  }
161
262
 
162
263
  return new Uint8Array(signature);
@@ -356,10 +457,20 @@ function buildUtxoOutputsForTrezor(
356
457
  return outputs;
357
458
  }
358
459
 
460
+ function shouldUseTrezorPsbtSigner(chain: Chain) {
461
+ return chain === Chain.Bitcoin || chain === Chain.Litecoin;
462
+ }
463
+
464
+ function shouldUseTrezorSerializedSigner(chain: Chain) {
465
+ return chain === Chain.BitcoinCash || chain === Chain.Dash || chain === Chain.Dogecoin;
466
+ }
467
+
359
468
  async function getTrezorWallet<T extends Chain>({
469
+ address: providedAddress,
360
470
  chain,
361
471
  derivationPath,
362
472
  }: {
473
+ address?: string;
363
474
  chain: T;
364
475
  derivationPath: DerivationPathArray;
365
476
  }) {
@@ -573,13 +684,16 @@ async function getTrezorWallet<T extends Chain>({
573
684
  throw new SwapKitError({ errorKey: "wallet_trezor_derivation_path_not_supported", info: { derivationPath } });
574
685
  }
575
686
 
687
+ const resolvedScriptType = scriptType;
576
688
  const coin = chain.toLowerCase();
577
689
 
578
690
  const getAddress = async (path: DerivationPathArray = derivationPath) => {
579
691
  const TrezorConnect = (await import("@trezor/connect-web")).default;
692
+ const pathString = derivationPathToString(path);
580
693
  const { success, payload } = await TrezorConnect.getAddress({
581
694
  coin,
582
- path: derivationPathToString(path),
695
+ ...TREZOR_KEEP_SESSION_PARAMS,
696
+ path: pathString,
583
697
  showOnTrezor: false,
584
698
  });
585
699
 
@@ -597,46 +711,7 @@ async function getTrezorWallet<T extends Chain>({
597
711
  return payload.address;
598
712
  };
599
713
 
600
- async function getAddressFromExtendedPublicKey() {
601
- const accountInfo = await getExtendedPublicKeyInfo();
602
- const addressIndex = Number(derivationPath[4] ?? 0);
603
- const change = Boolean(derivationPath[3] ?? 0);
604
-
605
- try {
606
- // deriveAddressesFromXpub returns both external and change branches for each index.
607
- const derivedAddress = deriveAddressesFromXpub({
608
- accountIndex: accountInfo.accountIndex,
609
- chain: utxoChain,
610
- count: 1,
611
- startIndex: addressIndex,
612
- xpub: accountInfo.xpub,
613
- }).find((derived) => derived.change === change && derived.index === addressIndex);
614
-
615
- if (!derivedAddress) {
616
- throw new SwapKitError({
617
- errorKey: "wallet_trezor_failed_to_get_address",
618
- info: { chain, error: "Unable to derive address from Trezor account public key" },
619
- });
620
- }
621
-
622
- return derivedAddress.address;
623
- } catch (error) {
624
- if (error instanceof SwapKitError) throw error;
625
-
626
- throw new SwapKitError({
627
- errorKey: "wallet_trezor_failed_to_get_address",
628
- info: {
629
- chain,
630
- error: error instanceof Error ? error.message : "Unable to derive address from Trezor xpub",
631
- },
632
- });
633
- }
634
- }
635
-
636
- const address =
637
- chain === Chain.Bitcoin || chain === Chain.Litecoin
638
- ? await getAddressFromExtendedPublicKey()
639
- : await getAddress();
714
+ const address = providedAddress ?? (await getAddress());
640
715
  const baseToolbox = getUtxoToolbox(chain);
641
716
 
642
717
  const signTransaction = async (tx: Transaction, inputs: UTXOType[], memo = "") => {
@@ -651,7 +726,7 @@ async function getTrezorWallet<T extends Chain>({
651
726
  address,
652
727
  memo,
653
728
  chain,
654
- scriptType,
729
+ resolvedScriptType,
655
730
  toCashAddress,
656
731
  stripPrefix,
657
732
  );
@@ -661,7 +736,7 @@ async function getTrezorWallet<T extends Chain>({
661
736
  amount: value,
662
737
  prev_hash: hash,
663
738
  prev_index: index,
664
- script_type: scriptType.input,
739
+ script_type: resolvedScriptType.input,
665
740
  }));
666
741
 
667
742
  const result = await TrezorConnect.signTransaction({ coin, inputs: trezorInputs, outputs });
@@ -687,7 +762,7 @@ async function getTrezorWallet<T extends Chain>({
687
762
  async function getFallbackDerivation(): Promise<TrezorBip32Derivation> {
688
763
  if (!fallbackPublicKey) {
689
764
  const accountInfo = await getExtendedPublicKeyInfo();
690
- const accountKey = HDKey.fromExtendedKey(accountInfo.xpub);
765
+ const accountKey = HDKey.fromExtendedKey(accountInfo.xpub, network.bip32);
691
766
  const leaf = accountKey.derive(`m/${Number(derivationPath[3] ?? 0)}/${Number(derivationPath[4] ?? 0)}`);
692
767
 
693
768
  if (!leaf.publicKey) {
@@ -730,7 +805,7 @@ async function getTrezorWallet<T extends Chain>({
730
805
  amount,
731
806
  prev_hash: hexEncode.encode(input.txid),
732
807
  prev_index: input.index,
733
- script_type: scriptType.input,
808
+ script_type: resolvedScriptType.input,
734
809
  ...(input.sequence !== undefined ? { sequence: input.sequence } : {}),
735
810
  });
736
811
  }
@@ -742,13 +817,14 @@ async function getTrezorWallet<T extends Chain>({
742
817
  address,
743
818
  "",
744
819
  chain,
745
- scriptType,
820
+ resolvedScriptType,
746
821
  toCashAddress,
747
822
  stripPrefix,
748
823
  );
749
824
 
750
825
  const result = await TrezorConnect.signTransaction({
751
826
  coin,
827
+ ...TREZOR_KEEP_SESSION_PARAMS,
752
828
  inputs: trezorInputs,
753
829
  locktime: tx.lockTime,
754
830
  outputs,
@@ -767,12 +843,71 @@ async function getTrezorWallet<T extends Chain>({
767
843
  const pubkey = signerPubkeys[inputIndex];
768
844
  if (!(signatureHex && pubkey)) return;
769
845
 
770
- tx.updateInput(inputIndex, { partialSig: [[pubkey, normalizeTrezorSignature(signatureHex)]] });
846
+ tx.updateInput(inputIndex, { partialSig: [[pubkey, normalizeTrezorSignature(signatureHex, chain)]] });
771
847
  });
772
848
 
773
849
  return tx;
774
850
  };
775
851
 
852
+ const signSerializedTransaction = async (tx: Transaction) => {
853
+ const TrezorConnect = (await import("@trezor/connect-web")).default;
854
+ const { hex: hexEncode } = await import("@scure/base");
855
+ const address_n = hardenDerivationPath(derivationPath);
856
+ const network = getNetworkForChain(chain as UTXOChain);
857
+
858
+ const trezorInputs = [];
859
+ for (let inputIndex = 0; inputIndex < tx.inputsLength; inputIndex++) {
860
+ const input = tx.getInput(inputIndex);
861
+ const amount = getPrevoutAmount(input);
862
+
863
+ if (!input.txid || input.index === undefined || !amount) {
864
+ throw new SwapKitError({
865
+ errorKey: "wallet_trezor_failed_to_sign_transaction",
866
+ info: { chain, error: `Input ${inputIndex} is missing prevout data required by Trezor` },
867
+ });
868
+ }
869
+
870
+ trezorInputs.push({
871
+ address_n,
872
+ amount,
873
+ prev_hash: hexEncode.encode(input.txid),
874
+ prev_index: input.index,
875
+ script_type: resolvedScriptType.input,
876
+ ...(input.sequence !== undefined ? { sequence: input.sequence } : {}),
877
+ });
878
+ }
879
+
880
+ const outputs = buildUtxoOutputsForTrezor(
881
+ tx,
882
+ network,
883
+ address_n,
884
+ address,
885
+ "",
886
+ chain,
887
+ resolvedScriptType,
888
+ toCashAddress,
889
+ stripPrefix,
890
+ );
891
+
892
+ const result = await TrezorConnect.signTransaction({
893
+ coin,
894
+ inputs: trezorInputs,
895
+ locktime: tx.lockTime,
896
+ outputs,
897
+ version: tx.version,
898
+ });
899
+
900
+ if (result.success) {
901
+ return result.payload.serializedTx;
902
+ }
903
+
904
+ const payload = result.payload as { error?: string; code?: string };
905
+ throw new SwapKitError({
906
+ errorKey: "wallet_trezor_failed_to_sign_transaction",
907
+ info: { chain, code: payload?.code ?? "unknown", error: payload?.error ?? "unknown", payload },
908
+ });
909
+ };
910
+
776
911
  const signTransactionWithMultipleInputs = async (
777
912
  tx: Transaction,
778
913
  inputs: Array<{ hash: string; index: number; value: number; derivationIndex: number; isChange: boolean }>,
@@ -789,7 +924,7 @@ async function getTrezorWallet<T extends Chain>({
789
924
  address,
790
925
  memo,
791
926
  chain,
792
- scriptType,
927
+ resolvedScriptType,
793
928
  toCashAddress,
794
929
  stripPrefix,
795
930
  );
@@ -802,7 +937,7 @@ async function getTrezorWallet<T extends Chain>({
802
937
  amount: value,
803
938
  prev_hash: hash,
804
939
  prev_index: inputIndex,
805
- script_type: scriptType.input,
940
+ script_type: resolvedScriptType.input,
806
941
  };
807
942
  });
808
943
 
@@ -897,12 +1032,15 @@ async function getTrezorWallet<T extends Chain>({
897
1032
  return txHash;
898
1033
  };
899
1034
 
900
- const toolbox =
901
- chain === Chain.Bitcoin || chain === Chain.Litecoin
902
- ? await getUtxoToolbox(utxoChain, {
903
- signer: { getAddress: async () => address, signTransaction: signPsbtTransaction },
904
- })
905
- : baseToolbox;
1035
+ const toolbox = shouldUseTrezorPsbtSigner(chain)
1036
+ ? await getUtxoToolbox(utxoChain, {
1037
+ signer: { getAddress: async () => address, signTransaction: signPsbtTransaction },
1038
+ })
1039
+ : baseToolbox;
1040
+
1041
+ const signAndBroadcastTransaction = shouldUseTrezorSerializedSigner(chain)
1042
+ ? async (tx: Transaction) => baseToolbox.broadcastTx(await signSerializedTransaction(tx))
1043
+ : toolbox.signAndBroadcastTransaction;
906
1044
 
907
1045
  async function getExtendedPublicKeyInfo({ accountIndex }: { accountIndex?: number } = {}) {
908
1046
  const TrezorConnect = (await import("@trezor/connect-web")).default;
@@ -912,7 +1050,8 @@ async function getTrezorWallet<T extends Chain>({
912
1050
  const cached = trezorXpubCache.get(cacheKey);
913
1051
  if (cached) return cached;
914
1052
 
915
- const { success, payload } = await TrezorConnect.getPublicKey({ coin, path });
1053
+ const result = await TrezorConnect.getPublicKey({ coin, path });
1054
+ const { success, payload } = result;
916
1055
 
917
1056
  if (!success) {
918
1057
  throw new SwapKitError({
@@ -921,6 +1060,8 @@ async function getTrezorWallet<T extends Chain>({
921
1060
  });
922
1061
  }
923
1062
 
1063
+ const xpub = normalizeTrezorExtendedPublicKey(payload.xpub, utxoChain);
1064
+ const xpubSegwit = tryNormalizeTrezorExtendedPublicKey(payload.xpubSegwit, utxoChain);
924
1065
  const info = {
925
1066
  accountIndex: getUTXOAccountIndexFromPath(resolvedAccountPath),
926
1067
  chainCode: payload.chainCode,
@@ -928,8 +1069,8 @@ async function getTrezorWallet<T extends Chain>({
928
1069
  fingerprint: payload.fingerprint,
929
1070
  path: payload.serializedPath,
930
1071
  publicKey: payload.publicKey,
931
- xpub: payload.xpub,
932
- xpubSegwit: payload.xpubSegwit,
1072
+ xpub,
1073
+ xpubSegwit,
933
1074
  };
934
1075
 
935
1076
  trezorXpubCache.set(cacheKey, info);
@@ -955,7 +1096,12 @@ async function getTrezorWallet<T extends Chain>({
955
1096
  const resolvedAccountPath = getUTXOAccountPath({ accountIndex, chain: utxoChain, derivationPath });
956
1097
  const fullPath = `${derivationPathToString(resolvedAccountPath)}/${Number(change)}/${index}`;
957
1098
 
958
- const { success, payload } = await TrezorConnect.getAddress({ coin, path: fullPath, showOnTrezor: false });
1099
+ const { success, payload } = await TrezorConnect.getAddress({
1100
+ coin,
1101
+ ...TREZOR_KEEP_SESSION_PARAMS,
1102
+ path: fullPath,
1103
+ showOnTrezor: false,
1104
+ });
959
1105
 
960
1106
  if (!success) {
961
1107
  return undefined;
@@ -967,7 +1113,13 @@ async function getTrezorWallet<T extends Chain>({
967
1113
  finalAddress = bchToolbox.stripPrefix(payload.address);
968
1114
  }
969
1115
 
970
- const pubKeyResult = await TrezorConnect.getPublicKey({ coin, path: fullPath });
1116
+ const pubKeyResult = await TrezorConnect.getPublicKey({
1117
+ coin,
1118
+ ...TREZOR_KEEP_SESSION_PARAMS,
1119
+ path: fullPath,
1120
+ scriptType: resolvedScriptType.input,
1121
+ showOnTrezor: false,
1122
+ });
971
1123
  const pubkey = pubKeyResult.success ? pubKeyResult.payload.publicKey : "";
972
1124
 
973
1125
  return {
@@ -1004,7 +1156,7 @@ async function getTrezorWallet<T extends Chain>({
1004
1156
  showOnTrezor: false,
1005
1157
  }));
1006
1158
 
1007
- const { success, payload } = await TrezorConnect.getAddress({ bundle: paths });
1159
+ const { success, payload } = await TrezorConnect.getAddress({ ...TREZOR_KEEP_SESSION_PARAMS, bundle: paths });
1008
1160
 
1009
1161
  if (!success || !Array.isArray(payload)) {
1010
1162
  return [];
@@ -1047,6 +1199,7 @@ async function getTrezorWallet<T extends Chain>({
1047
1199
  deriveAddresses: deriveAddressesBatch,
1048
1200
  getExtendedPublicKey,
1049
1201
  getExtendedPublicKeyInfo,
1202
+ signAndBroadcastTransaction,
1050
1203
  signTransaction,
1051
1204
  signTransactionWithMultipleInputs,
1052
1205
  transfer,
@@ -1059,9 +1212,61 @@ async function getTrezorWallet<T extends Chain>({
1059
1212
  }
1060
1213
  }
1061
1214
 
1215
+ export async function getTrezorExtendedPublicKey(
1216
+ chain: Chain,
1217
+ derivationPath?: DerivationPathArray,
1218
+ { accountIndex }: { accountIndex?: number } = {},
1219
+ ): Promise<HardwareExtendedPublicKeyInfo | undefined> {
1220
+ if (![Chain.BitcoinCash, Chain.Bitcoin, Chain.Dash, Chain.Dogecoin, Chain.Litecoin].includes(chain)) {
1221
+ throw new SwapKitError({ errorKey: "wallet_chain_not_supported", info: { chain, wallet: WalletOption.TREZOR } });
1222
+ }
1223
+
1224
+ const { TrezorConnect } = await initTrezorConnect();
1225
+ const utxoChain = chain as UTXOChain;
1226
+ const resolvedDerivationPath = derivationPath ?? (NetworkDerivationPath[chain] as DerivationPathArray);
1227
+ const coin = chain.toLowerCase();
1228
+
1229
+ const resolvedAccountPath = getUTXOAccountPath({
1230
+ accountIndex,
1231
+ chain: utxoChain,
1232
+ derivationPath: resolvedDerivationPath,
1233
+ });
1234
+ const path = derivationPathToString(resolvedAccountPath);
1235
+ const cacheKey = `${chain}:${path}`;
1236
+ const cached = trezorXpubCache.get(cacheKey);
1237
+ if (cached) return cached;
1238
+
1239
+ const { success, payload } = await TrezorConnect.getPublicKey({ coin, path, showOnTrezor: true });
1240
+
1241
+ if (!success) {
1242
+ throw new SwapKitError({
1243
+ errorKey: "wallet_trezor_failed_to_get_public_key",
1244
+ info: { chain, error: (payload as { error: string; code?: string }).error || "Unknown error" },
1245
+ });
1246
+ }
1247
+
1248
+ const info = {
1249
+ accountIndex: getUTXOAccountIndexFromPath(resolvedAccountPath),
1250
+ chainCode: payload.chainCode,
1251
+ depth: payload.depth,
1252
+ fingerprint: payload.fingerprint,
1253
+ path: payload.serializedPath,
1254
+ publicKey: payload.publicKey,
1255
+ xpub: normalizeTrezorExtendedPublicKey(payload.xpub, utxoChain),
1256
+ xpubSegwit: tryNormalizeTrezorExtendedPublicKey(payload.xpubSegwit, utxoChain),
1257
+ };
1258
+
1259
+ trezorXpubCache.set(cacheKey, info);
1260
+ return info;
1261
+ }
1262
+
1062
1263
  export const trezorWallet = createWallet({
1063
1264
  connect: ({ addChain, supportedChains, walletType }) =>
1064
- async function connectTrezor(chains: Chain[], derivationPath: DerivationPathArray) {
1265
+ async function connectTrezor(
1266
+ chains: Chain[],
1267
+ derivationPath: DerivationPathArray,
1268
+ { address }: ConnectTrezorOptions = {},
1269
+ ) {
1065
1270
  const [chain] = filterSupportedChains({ chains, supportedChains, walletType });
1066
1271
  if (!chain) {
1067
1272
  throw new SwapKitError({
@@ -1117,7 +1322,7 @@ export const trezorWallet = createWallet({
1117
1322
  transports: resolvedTransports,
1118
1323
  });
1119
1324
 
1120
- const wallet = await getTrezorWallet({ chain, derivationPath });
1325
+ const wallet = await getTrezorWallet({ address, chain, derivationPath });
1121
1326
 
1122
1327
  addChain({ ...wallet, chain, disconnect: disconnectTrezorSession, walletType });
1123
1328
 
@@ -1131,15 +1336,19 @@ export const trezorWallet = createWallet({
1131
1336
  [Chain.Berachain]: true,
1132
1337
  [Chain.BinanceSmartChain]: true,
1133
1338
  [Chain.Bitcoin]: true,
1339
+ [Chain.BitcoinCash]: true,
1340
+ [Chain.Dash]: true,
1134
1341
  [Chain.Ethereum]: true,
1135
1342
  [Chain.Gnosis]: true,
1343
+ [Chain.Dogecoin]: true,
1136
1344
  [Chain.Litecoin]: true,
1137
1345
  [Chain.Monad]: true,
1138
1346
  [Chain.Optimism]: true,
1139
1347
  [Chain.Polygon]: true,
1140
1348
  [Chain.XLayer]: true,
1141
- // BCH/DASH/DOGE/ZEC: pending PSBT→TrezorConnect converter (V3 plan PR)
1349
+ // ZEC: pending PCZT/TrezorConnect validation
1142
1350
  },
1351
+ getExtendedPublicKey: getTrezorExtendedPublicKey,
1143
1352
  name: "connectTrezor",
1144
1353
  supportedChains: [
1145
1354
  Chain.Arbitrum,
@@ -1,4 +0,0 @@
1
- var A=require("@scure/base");async function E(q){let{RawTx:D}=await import("@swapkit/utxo-signer"),v=[];for(let j=0;j<q.inputsLength;j++){let f=q.getInput(j);if(!f.txid||f.index===void 0)throw Error(`PSBT input ${j} is missing txid/index`);let B=f.nonWitnessUtxo?A.hex.encode(D.encode(f.nonWitnessUtxo)):"",z=f.witnessUtxo?{script:f.witnessUtxo.script,value:Number(f.witnessUtxo.amount)}:void 0;v.push({hash:A.hex.encode(f.txid),index:f.index,txHex:B,value:z?.value??0,witnessUtxo:z})}return v}function G({legacyClient:q,chain:D,address:v}){return{getAddress:async()=>v,signTransaction:async(j)=>{let f=await E(j),B=await q.signTransaction(j,f),{Transaction:z}=await import("@swapkit/utxo-signer");return z.fromRaw(A.hex.decode(B))}}}
2
-
3
- //# debugId=E761B05D18A24EBF64756E2164756E21
4
- //# sourceMappingURL=chunk-mrsfcest.js.map
@@ -1,10 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/ledger/clients/utxo-legacy-adapter.ts"],
4
- "sourcesContent": [
5
- "import { hex } from \"@scure/base\";\nimport type { UTXOChain } from \"@swapkit/helpers\";\nimport type { UTXOType } from \"@swapkit/toolboxes/utxo\";\nimport type { Transaction } from \"@swapkit/utxo-signer\";\n\n/**\n * Extract per-input metadata from a V3 PSBT in the shape the legacy\n * `@ledgerhq/hw-app-btc.createPaymentTransaction` adapter expects.\n *\n * For segwit inputs the SwapKit V3 API populates `witnessUtxo`; for legacy\n * (BCH/DOGE/DASH) it populates `nonWitnessUtxo` with the full prior-tx bytes.\n * We re-encode the parsed `nonWitnessUtxo` back to hex via `RawTx.encode` so\n * `btcApp.splitTransaction(hex)` can consume it.\n *\n * Single-address account assumption: all inputs share our derivation path.\n */\nexport async function extractInputsFromPsbt(tx: Transaction): Promise<UTXOType[]> {\n const { RawTx } = await import(\"@swapkit/utxo-signer\");\n const inputs: UTXOType[] = [];\n\n for (let i = 0; i < tx.inputsLength; i++) {\n const input = tx.getInput(i);\n\n if (!input.txid || input.index === undefined) {\n throw new Error(`PSBT input ${i} is missing txid/index`);\n }\n\n const txHex = input.nonWitnessUtxo ? hex.encode(RawTx.encode(input.nonWitnessUtxo)) : \"\";\n const witnessUtxo = input.witnessUtxo\n ? { script: input.witnessUtxo.script, value: Number(input.witnessUtxo.amount) }\n : undefined;\n\n inputs.push({\n hash: hex.encode(input.txid),\n index: input.index,\n txHex,\n value: witnessUtxo?.value ?? 0,\n witnessUtxo,\n } as UTXOType);\n }\n\n return inputs;\n}\n\n/**\n * Build a toolbox-compatible signer from the existing legacy Ledger UTXO\n * client. The toolbox synthesizes `signAndBroadcastTransaction` on top of\n * `signer.signTransaction(tx) → Transaction`.\n */\nexport function createLegacyPsbtSigner({\n legacyClient,\n chain: _chain,\n address,\n}: {\n legacyClient: { signTransaction: (tx: Transaction, inputUtxos: UTXOType[]) => Promise<string> };\n chain: UTXOChain;\n address: string;\n}) {\n return {\n getAddress: async () => address,\n signTransaction: async (tx: Transaction): Promise<Transaction> => {\n const inputUtxos = await extractInputsFromPsbt(tx);\n const signedTxHex = await legacyClient.signTransaction(tx, inputUtxos);\n\n const { Transaction: TxClass } = await import(\"@swapkit/utxo-signer\");\n // `Transaction.fromRaw` parses a serialised tx (no PSBT envelope) — exactly\n // what `createPaymentTransaction` returns.\n return TxClass.fromRaw(hex.decode(signedTxHex));\n },\n };\n}\n"
6
- ],
7
- "mappings": "AAAoB,IAApB,yBAgBA,eAAsB,CAAqB,CAAC,EAAsC,CAChF,IAAQ,SAAU,KAAa,gCACzB,EAAqB,CAAC,EAE5B,QAAS,EAAI,EAAG,EAAI,EAAG,aAAc,IAAK,CACxC,IAAM,EAAQ,EAAG,SAAS,CAAC,EAE3B,GAAI,CAAC,EAAM,MAAQ,EAAM,QAAU,OACjC,MAAU,MAAM,cAAc,yBAAyB,EAGzD,IAAM,EAAQ,EAAM,eAAiB,MAAI,OAAO,EAAM,OAAO,EAAM,cAAc,CAAC,EAAI,GAChF,EAAc,EAAM,YACtB,CAAE,OAAQ,EAAM,YAAY,OAAQ,MAAO,OAAO,EAAM,YAAY,MAAM,CAAE,EAC5E,OAEJ,EAAO,KAAK,CACV,KAAM,MAAI,OAAO,EAAM,IAAI,EAC3B,MAAO,EAAM,MACb,QACA,MAAO,GAAa,OAAS,EAC7B,aACF,CAAa,EAGf,OAAO,EAQF,SAAS,CAAsB,EACpC,eACA,MAAO,EACP,WAKC,CACD,MAAO,CACL,WAAY,SAAY,EACxB,gBAAiB,MAAO,IAA0C,CAChE,IAAM,EAAa,MAAM,EAAsB,CAAE,EAC3C,EAAc,MAAM,EAAa,gBAAgB,EAAI,CAAU,GAE7D,YAAa,GAAY,KAAa,gCAG9C,OAAO,EAAQ,QAAQ,MAAI,OAAO,CAAW,CAAC,EAElD",
8
- "debugId": "E761B05D18A24EBF64756E2164756E21",
9
- "names": []
10
- }
@@ -1,4 +0,0 @@
1
- import{d as E}from"./chunk-n05bv2n5.js";import{hex as B}from"@scure/base";async function G(q){let{RawTx:D}=await import("@swapkit/utxo-signer"),v=[];for(let j=0;j<q.inputsLength;j++){let f=q.getInput(j);if(!f.txid||f.index===void 0)throw Error(`PSBT input ${j} is missing txid/index`);let A=f.nonWitnessUtxo?B.encode(D.encode(f.nonWitnessUtxo)):"",z=f.witnessUtxo?{script:f.witnessUtxo.script,value:Number(f.witnessUtxo.amount)}:void 0;v.push({hash:B.encode(f.txid),index:f.index,txHex:A,value:z?.value??0,witnessUtxo:z})}return v}function K({legacyClient:q,chain:D,address:v}){return{getAddress:async()=>v,signTransaction:async(j)=>{let f=await G(j),A=await q.signTransaction(j,f),{Transaction:z}=await import("@swapkit/utxo-signer");return z.fromRaw(B.decode(A))}}}export{G as extractInputsFromPsbt,K as createLegacyPsbtSigner};
2
-
3
- //# debugId=95BF8F59DBE0DD4C64756E2164756E21
4
- //# sourceMappingURL=chunk-px09mwnt.js.map
@@ -1,10 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/ledger/clients/utxo-legacy-adapter.ts"],
4
- "sourcesContent": [
5
- "import { hex } from \"@scure/base\";\nimport type { UTXOChain } from \"@swapkit/helpers\";\nimport type { UTXOType } from \"@swapkit/toolboxes/utxo\";\nimport type { Transaction } from \"@swapkit/utxo-signer\";\n\n/**\n * Extract per-input metadata from a V3 PSBT in the shape the legacy\n * `@ledgerhq/hw-app-btc.createPaymentTransaction` adapter expects.\n *\n * For segwit inputs the SwapKit V3 API populates `witnessUtxo`; for legacy\n * (BCH/DOGE/DASH) it populates `nonWitnessUtxo` with the full prior-tx bytes.\n * We re-encode the parsed `nonWitnessUtxo` back to hex via `RawTx.encode` so\n * `btcApp.splitTransaction(hex)` can consume it.\n *\n * Single-address account assumption: all inputs share our derivation path.\n */\nexport async function extractInputsFromPsbt(tx: Transaction): Promise<UTXOType[]> {\n const { RawTx } = await import(\"@swapkit/utxo-signer\");\n const inputs: UTXOType[] = [];\n\n for (let i = 0; i < tx.inputsLength; i++) {\n const input = tx.getInput(i);\n\n if (!input.txid || input.index === undefined) {\n throw new Error(`PSBT input ${i} is missing txid/index`);\n }\n\n const txHex = input.nonWitnessUtxo ? hex.encode(RawTx.encode(input.nonWitnessUtxo)) : \"\";\n const witnessUtxo = input.witnessUtxo\n ? { script: input.witnessUtxo.script, value: Number(input.witnessUtxo.amount) }\n : undefined;\n\n inputs.push({\n hash: hex.encode(input.txid),\n index: input.index,\n txHex,\n value: witnessUtxo?.value ?? 0,\n witnessUtxo,\n } as UTXOType);\n }\n\n return inputs;\n}\n\n/**\n * Build a toolbox-compatible signer from the existing legacy Ledger UTXO\n * client. The toolbox synthesizes `signAndBroadcastTransaction` on top of\n * `signer.signTransaction(tx) → Transaction`.\n */\nexport function createLegacyPsbtSigner({\n legacyClient,\n chain: _chain,\n address,\n}: {\n legacyClient: { signTransaction: (tx: Transaction, inputUtxos: UTXOType[]) => Promise<string> };\n chain: UTXOChain;\n address: string;\n}) {\n return {\n getAddress: async () => address,\n signTransaction: async (tx: Transaction): Promise<Transaction> => {\n const inputUtxos = await extractInputsFromPsbt(tx);\n const signedTxHex = await legacyClient.signTransaction(tx, inputUtxos);\n\n const { Transaction: TxClass } = await import(\"@swapkit/utxo-signer\");\n // `Transaction.fromRaw` parses a serialised tx (no PSBT envelope) — exactly\n // what `createPaymentTransaction` returns.\n return TxClass.fromRaw(hex.decode(signedTxHex));\n },\n };\n}\n"
6
- ],
7
- "mappings": "wCAAA,cAAS,oBAgBT,eAAsB,CAAqB,CAAC,EAAsC,CAChF,IAAQ,SAAU,KAAa,gCACzB,EAAqB,CAAC,EAE5B,QAAS,EAAI,EAAG,EAAI,EAAG,aAAc,IAAK,CACxC,IAAM,EAAQ,EAAG,SAAS,CAAC,EAE3B,GAAI,CAAC,EAAM,MAAQ,EAAM,QAAU,OACjC,MAAU,MAAM,cAAc,yBAAyB,EAGzD,IAAM,EAAQ,EAAM,eAAiB,EAAI,OAAO,EAAM,OAAO,EAAM,cAAc,CAAC,EAAI,GAChF,EAAc,EAAM,YACtB,CAAE,OAAQ,EAAM,YAAY,OAAQ,MAAO,OAAO,EAAM,YAAY,MAAM,CAAE,EAC5E,OAEJ,EAAO,KAAK,CACV,KAAM,EAAI,OAAO,EAAM,IAAI,EAC3B,MAAO,EAAM,MACb,QACA,MAAO,GAAa,OAAS,EAC7B,aACF,CAAa,EAGf,OAAO,EAQF,SAAS,CAAsB,EACpC,eACA,MAAO,EACP,WAKC,CACD,MAAO,CACL,WAAY,SAAY,EACxB,gBAAiB,MAAO,IAA0C,CAChE,IAAM,EAAa,MAAM,EAAsB,CAAE,EAC3C,EAAc,MAAM,EAAa,gBAAgB,EAAI,CAAU,GAE7D,YAAa,GAAY,KAAa,gCAG9C,OAAO,EAAQ,QAAQ,EAAI,OAAO,CAAW,CAAC,EAElD",
8
- "debugId": "95BF8F59DBE0DD4C64756E2164756E21",
9
- "names": []
10
- }