@swapkit/wallet-hardware 6.0.0 → 6.1.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 (35) hide show
  1. package/dist/chunk-3h1f3hyp.js +4 -0
  2. package/dist/chunk-3h1f3hyp.js.map +10 -0
  3. package/dist/chunk-8m2sn76p.js +4 -0
  4. package/dist/chunk-8m2sn76p.js.map +10 -0
  5. package/dist/chunk-qjq7gvyx.js +4 -0
  6. package/dist/chunk-qjq7gvyx.js.map +10 -0
  7. package/dist/chunk-wfbg89d3.js +4 -0
  8. package/dist/chunk-wfbg89d3.js.map +10 -0
  9. package/dist/chunk-xn0fnag5.js +4 -0
  10. package/dist/chunk-xn0fnag5.js.map +10 -0
  11. package/dist/trezor/index.cjs +2 -2
  12. package/dist/trezor/index.cjs.map +9 -4
  13. package/dist/trezor/index.js +2 -2
  14. package/dist/trezor/index.js.map +3 -3
  15. package/dist/types/keepkey/index.d.ts +1 -1
  16. package/dist/types/ledger/index.d.ts +1 -1
  17. package/dist/types/trezor/cardanoSigner.d.ts +21 -0
  18. package/dist/types/trezor/cardanoSigner.d.ts.map +1 -0
  19. package/dist/types/trezor/index.d.ts +3 -3
  20. package/dist/types/trezor/index.d.ts.map +1 -1
  21. package/dist/types/trezor/rippleSigner.d.ts +23 -0
  22. package/dist/types/trezor/rippleSigner.d.ts.map +1 -0
  23. package/dist/types/trezor/solanaSigner.d.ts +17 -0
  24. package/dist/types/trezor/solanaSigner.d.ts.map +1 -0
  25. package/dist/types/trezor/stellarSigner.d.ts +99 -0
  26. package/dist/types/trezor/stellarSigner.d.ts.map +1 -0
  27. package/dist/types/trezor/tronSigner.d.ts +30 -0
  28. package/dist/types/trezor/tronSigner.d.ts.map +1 -0
  29. package/package.json +7 -3
  30. package/src/trezor/cardanoSigner.ts +226 -0
  31. package/src/trezor/index.ts +136 -43
  32. package/src/trezor/rippleSigner.ts +100 -0
  33. package/src/trezor/solanaSigner.ts +129 -0
  34. package/src/trezor/stellarSigner.ts +186 -0
  35. package/src/trezor/tronSigner.ts +148 -0
@@ -199,11 +199,55 @@ function tryNormalizeTrezorExtendedPublicKey(xpub: string | undefined, chain: UT
199
199
  }
200
200
  }
201
201
 
202
- function decodeOpReturnData(script: Uint8Array): string | null {
203
- if (script.length < 2 || script[0] !== 0x6a) return null;
204
- const dataLen = script[1];
205
- if (dataLen === undefined || script.length < 2 + dataLen) return null;
206
- return Buffer.from(script.slice(2, 2 + dataLen)).toString("hex");
202
+ async function decodeOpReturnData(script: Uint8Array) {
203
+ const { Script } = await import("@swapkit/utxo-signer");
204
+
205
+ try {
206
+ const [opcode, data] = Script.decode(script);
207
+ const bytes = data === 0 ? new Uint8Array() : data;
208
+ if (opcode !== "RETURN" || !(bytes instanceof Uint8Array)) return null;
209
+
210
+ // Trezor rebuilds the script from the data, so only accept scripts it reproduces byte for byte
211
+ return hex.encode(Script.encode(["RETURN", bytes])) === hex.encode(script) ? hex.encode(bytes) : null;
212
+ } catch {
213
+ return null;
214
+ }
215
+ }
216
+
217
+ async function toTrezorOpReturnOutput(script: Uint8Array, amount: bigint | undefined, chain: Chain) {
218
+ if (amount !== 0n) {
219
+ throw new SwapKitError({
220
+ errorKey: "wallet_trezor_failed_to_sign_transaction",
221
+ info: { chain, error: "OP_RETURN output carrying value cannot be signed" },
222
+ });
223
+ }
224
+
225
+ const opReturnData = await decodeOpReturnData(script);
226
+
227
+ if (opReturnData === null) {
228
+ throw new SwapKitError({
229
+ errorKey: "wallet_trezor_failed_to_sign_transaction",
230
+ info: { chain, error: "Malformed OP_RETURN output cannot be signed" },
231
+ });
232
+ }
233
+
234
+ return { amount: "0", op_return_data: opReturnData, script_type: "PAYTOOPRETURN" as const };
235
+ }
236
+
237
+ async function assertTransactionCarriesMemo(tx: Transaction, memo: string, chain: Chain) {
238
+ if (!memo) return;
239
+
240
+ const expected = hex.encode(new TextEncoder().encode(memo));
241
+
242
+ for (let i = 0; i < tx.outputsLength; i++) {
243
+ const script = tx.getOutput(i).script;
244
+ if (script?.[0] === 0x6a && (await decodeOpReturnData(script)) === expected) return;
245
+ }
246
+
247
+ throw new SwapKitError({
248
+ errorKey: "wallet_trezor_failed_to_sign_transaction",
249
+ info: { chain, error: "Transaction does not carry the provided memo" },
250
+ });
207
251
  }
208
252
 
209
253
  function getScriptType(chain: Chain, derivationPath: DerivationPathArray) {
@@ -327,16 +371,9 @@ async function buildPCZTOutputsForTrezor(pczt: PCZT, address_n: number[], myAddr
327
371
  const output = pczt.getOutput(i);
328
372
  const script = output.scriptPubkey;
329
373
 
330
- if (output.value === 0n && script?.length > 0 && script[0] === 0x6a) {
331
- const opReturnData = decodeOpReturnData(script);
332
- if (opReturnData) {
333
- outputs.push({ amount: "0", op_return_data: opReturnData, script_type: "PAYTOOPRETURN" as const });
334
- continue;
335
- }
336
- throw new SwapKitError({
337
- errorKey: "wallet_trezor_failed_to_sign_transaction",
338
- info: { chain, error: "Malformed OP_RETURN output cannot be signed" },
339
- });
374
+ if (script?.[0] === 0x6a) {
375
+ outputs.push(await toTrezorOpReturnOutput(script, output.value, chain));
376
+ continue;
340
377
  }
341
378
 
342
379
  const outputAddress = await decodeOutputAddress(script);
@@ -411,7 +448,7 @@ function buildZcashTxInputsForTrezor(
411
448
  return inputs;
412
449
  }
413
450
 
414
- function buildZcashTxOutputsForTrezor({
451
+ async function buildZcashTxOutputsForTrezor({
415
452
  addressN,
416
453
  chain,
417
454
  myAddress,
@@ -430,12 +467,8 @@ function buildZcashTxOutputsForTrezor({
430
467
  const outputAddress = tx.getOutputAddress(i, network);
431
468
  const script = output.script;
432
469
 
433
- if (output.amount === 0n && script?.length > 0 && script[0] === 0x6a) {
434
- const opReturnData = decodeOpReturnData(script);
435
- if (opReturnData) {
436
- outputs.push({ amount: "0", op_return_data: opReturnData, script_type: "PAYTOOPRETURN" as const });
437
- continue;
438
- }
470
+ if (script?.[0] === 0x6a) {
471
+ outputs.push(await toTrezorOpReturnOutput(script, output.amount, chain));
439
472
  continue;
440
473
  }
441
474
 
@@ -453,12 +486,11 @@ function buildZcashTxOutputsForTrezor({
453
486
  return outputs;
454
487
  }
455
488
 
456
- function buildUtxoOutputsForTrezor(
489
+ async function buildUtxoOutputsForTrezor(
457
490
  tx: Transaction,
458
491
  network: BTCNetwork,
459
492
  address_n: number[],
460
493
  myAddress: string,
461
- memo: string,
462
494
  chain: Chain,
463
495
  scriptType: { input: string; output: string },
464
496
  toCashAddress: (addr: string) => string,
@@ -469,17 +501,12 @@ function buildUtxoOutputsForTrezor(
469
501
  const output = tx.getOutput(i);
470
502
  const outputAddress = tx.getOutputAddress(i, network);
471
503
 
472
- if (!outputAddress) {
473
- const opReturnData = output.script ? decodeOpReturnData(output.script) : null;
474
- if (opReturnData !== null || memo) {
475
- outputs.push({
476
- amount: "0",
477
- op_return_data: opReturnData ?? Buffer.from(memo).toString("hex"),
478
- script_type: "PAYTOOPRETURN",
479
- });
480
- continue;
481
- }
504
+ if (output.script?.[0] === 0x6a) {
505
+ outputs.push(await toTrezorOpReturnOutput(output.script, output.amount, chain));
506
+ continue;
507
+ }
482
508
 
509
+ if (!outputAddress) {
483
510
  throw new SwapKitError({
484
511
  errorKey: "wallet_trezor_failed_to_sign_transaction",
485
512
  info: { chain, error: "Unable to decode output address from scriptPubkey" },
@@ -571,6 +598,62 @@ async function getTrezorWallet<T extends Chain>({
571
598
  return { ...toolbox, address };
572
599
  }
573
600
 
601
+ case Chain.Cardano: {
602
+ const { getCardanoToolbox } = await import("@swapkit/toolboxes/cardano");
603
+ const { getCardanoSigner } = await import("./cardanoSigner");
604
+
605
+ const connect = await import("@trezor/connect-web");
606
+ const signer = await getCardanoSigner({ connect, derivationPath });
607
+ const address = signer.getAddress();
608
+ const toolbox = getCardanoToolbox({ signer });
609
+
610
+ return { ...toolbox, address };
611
+ }
612
+
613
+ case Chain.Ripple: {
614
+ const { getRippleToolbox } = await import("@swapkit/toolboxes/ripple");
615
+ const { getRippleSigner } = await import("./rippleSigner");
616
+
617
+ const signer = getRippleSigner({ derivationPath });
618
+ const address = providedAddress ?? (await signer.getAddress());
619
+ const toolbox = getRippleToolbox({ signer });
620
+
621
+ return { ...toolbox, address };
622
+ }
623
+
624
+ case Chain.Solana: {
625
+ const { getSolanaToolbox } = await import("@swapkit/toolboxes/solana");
626
+ const { getSolanaSigner } = await import("./solanaSigner");
627
+
628
+ const signer = getSolanaSigner({ address: providedAddress, derivationPath });
629
+ const address = providedAddress ?? (await signer.getAddress());
630
+ const toolbox = getSolanaToolbox({ signer });
631
+
632
+ return { ...toolbox, address };
633
+ }
634
+
635
+ case Chain.Stellar: {
636
+ const { getStellarToolbox } = await import("@swapkit/toolboxes/stellar");
637
+ const { getStellarSigner } = await import("./stellarSigner");
638
+
639
+ const signer = getStellarSigner({ derivationPath });
640
+ const address = providedAddress ?? (await signer.getAddress());
641
+ const toolbox = getStellarToolbox({ signer });
642
+
643
+ return { ...toolbox, address };
644
+ }
645
+
646
+ case Chain.Tron: {
647
+ const { getTronToolbox } = await import("@swapkit/toolboxes/tron");
648
+ const { getTronSigner } = await import("./tronSigner");
649
+
650
+ const signer = getTronSigner({ address: providedAddress, derivationPath });
651
+ const address = await signer.getAddress();
652
+ const toolbox = getTronToolbox({ signer });
653
+
654
+ return { ...toolbox, address };
655
+ }
656
+
574
657
  case Chain.Zcash: {
575
658
  const { getUtxoToolbox } = await import("@swapkit/toolboxes/utxo");
576
659
 
@@ -637,7 +720,7 @@ async function getTrezorWallet<T extends Chain>({
637
720
  const address_n = hardenDerivationPath(derivationPath);
638
721
 
639
722
  const inputs = buildZcashTxInputsForTrezor(tx, [], address_n, hexEncode);
640
- const outputs = buildZcashTxOutputsForTrezor({
723
+ const outputs = await buildZcashTxOutputsForTrezor({
641
724
  addressN: address_n,
642
725
  chain,
643
726
  myAddress: address,
@@ -779,16 +862,17 @@ async function getTrezorWallet<T extends Chain>({
779
862
  const baseToolbox = getUtxoToolbox(chain, toolboxParams);
780
863
 
781
864
  const signTransaction = async (tx: Transaction, inputs: UTXOType[], memo = "") => {
865
+ await assertTransactionCarriesMemo(tx, memo, chain);
866
+
782
867
  const TrezorConnect = (await import("@trezor/connect-web")).default;
783
868
  const address_n = hardenDerivationPath(derivationPath);
784
869
  const network = getNetworkForChain(chain as UTXOChain);
785
870
 
786
- const outputs = buildUtxoOutputsForTrezor(
871
+ const outputs = await buildUtxoOutputsForTrezor(
787
872
  tx,
788
873
  network,
789
874
  address_n,
790
875
  address,
791
- memo,
792
876
  chain,
793
877
  resolvedScriptType,
794
878
  toCashAddress,
@@ -902,12 +986,11 @@ async function getTrezorWallet<T extends Chain>({
902
986
  });
903
987
  }
904
988
 
905
- const outputs = buildUtxoOutputsForTrezor(
989
+ const outputs = await buildUtxoOutputsForTrezor(
906
990
  tx,
907
991
  network,
908
992
  address_n,
909
993
  address,
910
- "",
911
994
  chain,
912
995
  resolvedScriptType,
913
996
  toCashAddress,
@@ -977,12 +1060,11 @@ async function getTrezorWallet<T extends Chain>({
977
1060
  });
978
1061
  }
979
1062
 
980
- const outputs = buildUtxoOutputsForTrezor(
1063
+ const outputs = await buildUtxoOutputsForTrezor(
981
1064
  tx,
982
1065
  network,
983
1066
  address_n,
984
1067
  address,
985
- "",
986
1068
  chain,
987
1069
  resolvedScriptType,
988
1070
  toCashAddress,
@@ -1020,16 +1102,17 @@ async function getTrezorWallet<T extends Chain>({
1020
1102
  }>,
1021
1103
  memo = "",
1022
1104
  ) => {
1105
+ await assertTransactionCarriesMemo(tx, memo, chain);
1106
+
1023
1107
  const TrezorConnect = (await import("@trezor/connect-web")).default;
1024
1108
  const network = getNetworkForChain(chain as UTXOChain);
1025
1109
  const baseAddressN = hardenDerivationPath(derivationPath.slice(0, 3) as unknown as DerivationPathArray);
1026
1110
 
1027
- const outputs = buildUtxoOutputsForTrezor(
1111
+ const outputs = await buildUtxoOutputsForTrezor(
1028
1112
  tx,
1029
1113
  network,
1030
1114
  baseAddressN,
1031
1115
  address,
1032
- memo,
1033
1116
  chain,
1034
1117
  resolvedScriptType,
1035
1118
  toCashAddress,
@@ -1458,6 +1541,7 @@ export const trezorWallet = createWallet({
1458
1541
  [Chain.BinanceSmartChain]: true,
1459
1542
  [Chain.Bitcoin]: true,
1460
1543
  [Chain.BitcoinCash]: true,
1544
+ [Chain.Cardano]: true,
1461
1545
  [Chain.Dash]: true,
1462
1546
  [Chain.Ethereum]: true,
1463
1547
  [Chain.Gnosis]: true,
@@ -1467,7 +1551,11 @@ export const trezorWallet = createWallet({
1467
1551
  [Chain.Monad]: true,
1468
1552
  [Chain.Optimism]: true,
1469
1553
  [Chain.Polygon]: true,
1554
+ [Chain.Ripple]: true,
1470
1555
  [Chain.Robinhood]: true,
1556
+ [Chain.Solana]: true,
1557
+ [Chain.Stellar]: true,
1558
+ [Chain.Tron]: true,
1471
1559
  [Chain.XLayer]: true,
1472
1560
  [Chain.Zcash]: true,
1473
1561
  },
@@ -1483,6 +1571,7 @@ export const trezorWallet = createWallet({
1483
1571
  Chain.BinanceSmartChain,
1484
1572
  Chain.Bitcoin,
1485
1573
  Chain.BitcoinCash,
1574
+ Chain.Cardano,
1486
1575
  Chain.Dash,
1487
1576
  Chain.Dogecoin,
1488
1577
  Chain.Ethereum,
@@ -1492,7 +1581,11 @@ export const trezorWallet = createWallet({
1492
1581
  Chain.Monad,
1493
1582
  Chain.Optimism,
1494
1583
  Chain.Polygon,
1584
+ Chain.Ripple,
1495
1585
  Chain.Robinhood,
1586
+ Chain.Solana,
1587
+ Chain.Stellar,
1588
+ Chain.Tron,
1496
1589
  Chain.XLayer,
1497
1590
  Chain.Zcash,
1498
1591
  ],
@@ -0,0 +1,100 @@
1
+ import { Chain, type DerivationPathArray, derivationPathToString, SwapKitError } from "@swapkit/helpers";
2
+ import type { RippleTransaction } from "@swapkit/toolboxes/ripple";
3
+ import { GlobalFlags, hashes, isValidClassicAddress } from "xrpl";
4
+
5
+ // Fields Trezor serializes itself; anything else would be dropped from the signed transaction
6
+ const TREZOR_RIPPLE_FIELDS = new Set([
7
+ "Account",
8
+ "Amount",
9
+ "Destination",
10
+ "DestinationTag",
11
+ "Fee",
12
+ "Flags",
13
+ "LastLedgerSequence",
14
+ "Sequence",
15
+ "SigningPubKey",
16
+ "TransactionType",
17
+ ]);
18
+
19
+ function notSupported(reason: string) {
20
+ return new SwapKitError({ errorKey: "wallet_trezor_method_not_supported", info: { chain: Chain.Ripple, reason } });
21
+ }
22
+
23
+ export function toTrezorRippleTransaction(transaction: RippleTransaction) {
24
+ const tx = transaction as unknown as Record<string, unknown>;
25
+
26
+ if (tx.TransactionType !== "Payment") {
27
+ throw notSupported(`Trezor only signs XRP payments, got ${String(tx.TransactionType)}`);
28
+ }
29
+
30
+ const unsupportedFields = Object.keys(tx).filter(
31
+ (field) => tx[field] !== undefined && tx[field] !== null && !TREZOR_RIPPLE_FIELDS.has(field),
32
+ );
33
+ if (unsupportedFields.length > 0) {
34
+ throw notSupported(`Trezor cannot sign XRP payments with ${unsupportedFields.join(", ")}`);
35
+ }
36
+
37
+ const { Amount, Destination, DestinationTag, Fee, Flags, LastLedgerSequence, Sequence } = tx;
38
+
39
+ if (typeof Amount !== "string" || !/^\d+$/.test(Amount)) throw notSupported("Trezor only signs native XRP amounts");
40
+ if (typeof Destination !== "string") throw notSupported("Missing destination");
41
+ if (!isValidClassicAddress(Destination)) throw notSupported("Destination must be a classic XRP address");
42
+ if (typeof Fee !== "string" || typeof Sequence !== "number") throw notSupported("Transaction is not autofilled");
43
+ if (Flags !== undefined && typeof Flags !== "number") throw notSupported("Flags must be numeric");
44
+ if (((Flags ?? 0) & GlobalFlags.tfInnerBatchTxn) !== 0) {
45
+ throw notSupported("Trezor cannot sign Batch inner XRP transactions");
46
+ }
47
+
48
+ return {
49
+ fee: Fee,
50
+ flags: Flags as number | undefined,
51
+ maxLedgerVersion: LastLedgerSequence as number | undefined,
52
+ payment: { amount: Amount, destination: Destination, destinationTag: DestinationTag as number | undefined },
53
+ sequence: Sequence,
54
+ };
55
+ }
56
+
57
+ export function getRippleSigner({ derivationPath }: { derivationPath: DerivationPathArray }) {
58
+ const path = derivationPathToString(derivationPath);
59
+ let address = "";
60
+
61
+ async function getAddress() {
62
+ if (address) return address;
63
+
64
+ const TrezorConnect = (await import("@trezor/connect-web")).default;
65
+ const result = await TrezorConnect.rippleGetAddress({ path, showOnTrezor: true });
66
+
67
+ if (!result.success) {
68
+ throw new SwapKitError({
69
+ errorKey: "wallet_trezor_failed_to_get_address",
70
+ info: { chain: Chain.Ripple, derivationPath, error: result.payload.error },
71
+ });
72
+ }
73
+
74
+ address = result.payload.address;
75
+ return address;
76
+ }
77
+
78
+ async function signTransaction(transaction: RippleTransaction) {
79
+ const trezorTransaction = toTrezorRippleTransaction(transaction);
80
+ if (transaction.Account !== (await getAddress())) {
81
+ throw notSupported("Transaction account does not match the Trezor address");
82
+ }
83
+
84
+ const TrezorConnect = (await import("@trezor/connect-web")).default;
85
+
86
+ const result = await TrezorConnect.rippleSignTransaction({ path, transaction: trezorTransaction });
87
+
88
+ if (!result.success) {
89
+ throw new SwapKitError({
90
+ errorKey: "wallet_trezor_failed_to_sign_transaction",
91
+ info: { chain: Chain.Ripple, error: result.payload.error },
92
+ });
93
+ }
94
+
95
+ const tx_blob = result.payload.serializedTx.toUpperCase();
96
+ return { hash: hashes.hashSignedTx(tx_blob), tx_blob };
97
+ }
98
+
99
+ return { getAddress, signTransaction };
100
+ }
@@ -0,0 +1,129 @@
1
+ import { ed25519 } from "@noble/curves/ed25519.js";
2
+ import { hex } from "@scure/base";
3
+ import { PublicKey, type Transaction, type VersionedTransaction } from "@solana/web3.js";
4
+ import { Chain, type DerivationPathArray, derivationPathToString, SwapKitError } from "@swapkit/helpers";
5
+
6
+ type SolanaTransaction = Transaction | VersionedTransaction;
7
+
8
+ function notSupported(reason: string) {
9
+ return new SwapKitError({ errorKey: "wallet_trezor_method_not_supported", info: { chain: Chain.Solana, reason } });
10
+ }
11
+
12
+ function getSigners(transaction: SolanaTransaction) {
13
+ if ("version" in transaction) {
14
+ const { header, staticAccountKeys } = transaction.message;
15
+ return {
16
+ message: transaction.message.serialize(),
17
+ signerKeys: staticAccountKeys.slice(0, header.numRequiredSignatures),
18
+ };
19
+ }
20
+
21
+ return {
22
+ message: transaction.serializeMessage(),
23
+ signerKeys: transaction.signatures.map(({ publicKey }) => publicKey),
24
+ };
25
+ }
26
+
27
+ function getSolanaSigningPayload(transaction: SolanaTransaction, address: string) {
28
+ const { message, signerKeys } = getSigners(transaction);
29
+ const signerKey = signerKeys.find((key) => key.toBase58() === address);
30
+
31
+ if (!signerKey) throw notSupported("Trezor account is not a required signer of this transaction");
32
+
33
+ return { message, signerKey };
34
+ }
35
+
36
+ function addSolanaSignature<T extends SolanaTransaction>(
37
+ transaction: T,
38
+ { message, signature, signerKey }: { message: Uint8Array; signature: Uint8Array; signerKey: PublicKey },
39
+ ) {
40
+ if (signature.length !== 64) {
41
+ throw new SwapKitError({
42
+ errorKey: "wallet_trezor_failed_to_sign_transaction",
43
+ info: { chain: Chain.Solana, error: `Invalid signature length: ${signature.length}` },
44
+ });
45
+ }
46
+
47
+ if (!ed25519.verify(signature, message, signerKey.toBytes())) {
48
+ throw new SwapKitError({
49
+ errorKey: "wallet_trezor_failed_to_sign_transaction",
50
+ info: { chain: Chain.Solana, error: "Signature does not match the Trezor account" },
51
+ });
52
+ }
53
+
54
+ transaction.addSignature(signerKey, Buffer.from(signature));
55
+ return transaction;
56
+ }
57
+
58
+ export function getSolanaSigner({
59
+ address,
60
+ derivationPath,
61
+ }: {
62
+ address?: string;
63
+ derivationPath: DerivationPathArray;
64
+ }) {
65
+ // Trezor firmware only accepts m/44'/501' plus up to two more levels.
66
+ const [purpose, coinType] = derivationPath;
67
+ if (purpose !== 44 || coinType !== 501 || derivationPath.length > 4) {
68
+ throw new SwapKitError({
69
+ errorKey: "wallet_trezor_derivation_path_not_supported",
70
+ info: { chain: Chain.Solana, derivationPath },
71
+ });
72
+ }
73
+
74
+ const path = derivationPathToString(derivationPath, { allHardened: true });
75
+ const knownKey = address ? new PublicKey(address) : null;
76
+ let publicKey: PublicKey | null = null;
77
+
78
+ async function loadAddress(showOnTrezor: boolean) {
79
+ if (publicKey) return publicKey.toBase58();
80
+
81
+ const TrezorConnect = (await import("@trezor/connect-web")).default;
82
+ const result = await TrezorConnect.solanaGetAddress({ path, showOnTrezor });
83
+
84
+ if (!result.success) {
85
+ throw new SwapKitError({
86
+ errorKey: "wallet_trezor_failed_to_get_address",
87
+ info: { chain: Chain.Solana, derivationPath, error: result.payload.error },
88
+ });
89
+ }
90
+
91
+ const deviceKey = new PublicKey(result.payload.address);
92
+ if (knownKey && !deviceKey.equals(knownKey)) {
93
+ throw new SwapKitError({
94
+ errorKey: "wallet_trezor_failed_to_get_address",
95
+ info: { chain: Chain.Solana, derivationPath, error: "Trezor account does not match the restored address" },
96
+ });
97
+ }
98
+
99
+ publicKey = deviceKey;
100
+ return result.payload.address;
101
+ }
102
+
103
+ async function signTransaction<T extends SolanaTransaction>(transaction: T) {
104
+ const { message, signerKey } = getSolanaSigningPayload(transaction, await loadAddress(false));
105
+ const TrezorConnect = (await import("@trezor/connect-web")).default;
106
+
107
+ const result = await TrezorConnect.solanaSignTransaction({ path, serializedTx: hex.encode(message) });
108
+
109
+ if (!result.success) {
110
+ throw new SwapKitError({
111
+ errorKey: "wallet_trezor_failed_to_sign_transaction",
112
+ info: { chain: Chain.Solana, error: result.payload.error },
113
+ });
114
+ }
115
+
116
+ const signature = hex.decode(result.payload.signature.replace(/^0x/, ""));
117
+ return addSolanaSignature(transaction, { message, signature, signerKey });
118
+ }
119
+
120
+ return {
121
+ connect: async () => ({ publicKey: new PublicKey(await loadAddress(true)) }),
122
+ disconnect: () => Promise.resolve(),
123
+ getAddress: () => loadAddress(true),
124
+ get publicKey() {
125
+ return publicKey ?? knownKey;
126
+ },
127
+ signTransaction,
128
+ };
129
+ }