@swapkit/wallet-hardware 4.9.13 → 4.9.15

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.
@@ -22,7 +22,7 @@ import {
22
22
  type UTXOToolboxes,
23
23
  type UTXOType,
24
24
  } from "@swapkit/toolboxes/utxo";
25
- import type { BTCNetwork, PCZT, Transaction, ZcashTransaction } from "@swapkit/utxo-signer";
25
+ import type { BTCNetwork, PCZT, Transaction, ZcashPSBT, ZcashTransaction } from "@swapkit/utxo-signer";
26
26
  import { BCHSigHash, NETWORKS, ZcashConsensusBranchId, ZcashVersionGroupId } from "@swapkit/utxo-signer";
27
27
  import { createWallet, getWalletSupportedChains, type HardwareExtendedPublicKeyInfo } from "@swapkit/wallet-core";
28
28
 
@@ -30,6 +30,8 @@ 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
32
  type ConnectTrezorOptions = { address?: string };
33
+ type TrezorAccountRefTransaction = { details: Record<string, never>; hex: string; txid: string };
34
+ type ZcashSignableTransaction = PCZT | ZcashPSBT | ZcashTransaction;
33
35
  type TrezorExtendedPublicKeyInfo = {
34
36
  accountIndex: number;
35
37
  chainCode?: string;
@@ -274,7 +276,7 @@ function buildPCZTInputsForTrezor(
274
276
  inputs.push({
275
277
  address_n,
276
278
  amount: input.value.toString(),
277
- prev_hash: hexEncode.encode(new Uint8Array([...input.txid].reverse())),
279
+ prev_hash: hexEncode.encode(input.txid),
278
280
  prev_index: input.index,
279
281
  script_type: "SPENDADDRESS" as const,
280
282
  });
@@ -363,8 +365,8 @@ function buildZcashTxInputsForTrezor(
363
365
  const utxoInfo = utxoInputs[i];
364
366
  inputs.push({
365
367
  address_n,
366
- amount: utxoInfo?.value?.toString() || "0",
367
- prev_hash: input.txid ? hexEncode.encode(new Uint8Array([...input.txid].reverse())) : "",
368
+ amount: utxoInfo?.value?.toString() || input.value?.toString() || "0",
369
+ prev_hash: input.txid ? hexEncode.encode(input.txid) : "",
368
370
  prev_index: input.index ?? 0,
369
371
  script_type: "SPENDADDRESS" as const,
370
372
  });
@@ -465,6 +467,25 @@ function shouldUseTrezorSerializedSigner(chain: Chain) {
465
467
  return chain === Chain.BitcoinCash || chain === Chain.Dash || chain === Chain.Dogecoin;
466
468
  }
467
469
 
470
+ function buildTrezorRefTxs(chain: Chain, inputs: UTXOType[]): TrezorAccountRefTransaction[] | undefined {
471
+ if (chain !== Chain.Dash) return undefined;
472
+
473
+ const refs = new Map<string, TrezorAccountRefTransaction>();
474
+
475
+ for (const input of inputs) {
476
+ if (!input.txHex) {
477
+ throw new SwapKitError({
478
+ errorKey: "wallet_trezor_failed_to_sign_transaction",
479
+ info: { chain, error: `Missing previous transaction hex for ${input.hash}:${input.index}` },
480
+ });
481
+ }
482
+
483
+ refs.set(input.hash, { details: {}, hex: input.txHex, txid: input.hash });
484
+ }
485
+
486
+ return [...refs.values()];
487
+ }
488
+
468
489
  async function getTrezorWallet<T extends Chain>({
469
490
  address: providedAddress,
470
491
  chain,
@@ -517,158 +538,115 @@ async function getTrezorWallet<T extends Chain>({
517
538
  return payload.address;
518
539
  };
519
540
 
520
- const address = await getAddress();
521
-
522
- const signer = {
523
- getAddress: async () => address,
524
-
525
- signPCZT: async (pczt: PCZT): Promise<PCZT> => {
526
- const TrezorConnect = (await import("@trezor/connect-web")).default;
527
- const { hex: hexEncode } = await import("@scure/base");
528
- const address_n = hardenDerivationPath(derivationPath);
529
- const global = pczt.getGlobal();
530
-
531
- const inputs = buildPCZTInputsForTrezor(pczt, address_n, hexEncode);
532
- const outputs = await buildPCZTOutputsForTrezor(pczt, address_n, address, chain);
533
-
534
- const result = await TrezorConnect.signTransaction({
535
- branchId: global.consensusBranchId,
536
- coin: "zcash",
537
- expiry: global.expiryHeight,
538
- inputs,
539
- locktime: global.lockTime,
540
- outputs: outputs as any,
541
- overwintered: true,
542
- version: global.txVersion,
543
- versionGroupId: global.versionGroupId,
544
- });
545
-
546
- if (!result.success) {
547
- throw new SwapKitError({
548
- errorKey: "wallet_trezor_failed_to_sign_transaction",
549
- info: { chain, error: (result.payload as { error: string; code?: string }).error },
550
- });
551
- }
552
-
553
- return extractSignaturesFromSignedTx(result.payload.serializedTx, pczt);
554
- },
541
+ const address = providedAddress ?? (await getAddress());
555
542
 
556
- signTransaction: async (tx: ZcashTransaction, utxoInputs: UTXOType[]) => {
557
- const TrezorConnect = (await import("@trezor/connect-web")).default;
558
- const { hex: hexEncode } = await import("@scure/base");
559
- const address_n = hardenDerivationPath(derivationPath);
560
-
561
- const inputs = buildZcashTxInputsForTrezor(tx, utxoInputs, address_n, hexEncode);
562
- const outputs = buildZcashTxOutputsForTrezor(tx, address_n, address, chain);
563
-
564
- const result = await TrezorConnect.signTransaction({
565
- branchId: ZcashConsensusBranchId.NU6,
566
- coin: "zcash",
567
- expiry: 0,
568
- inputs,
569
- locktime: 0,
570
- outputs: outputs as any,
571
- overwintered: true,
572
- version: 4,
573
- versionGroupId: ZcashVersionGroupId.SAPLING,
574
- });
543
+ const signPCZTWithSerializedTx = async (pczt: PCZT) => {
544
+ const TrezorConnect = (await import("@trezor/connect-web")).default;
545
+ const { hex: hexEncode } = await import("@scure/base");
546
+ const address_n = hardenDerivationPath(derivationPath);
547
+ const global = pczt.getGlobal();
575
548
 
576
- if (result.success) {
577
- return result.payload.serializedTx;
578
- }
549
+ const inputs = buildPCZTInputsForTrezor(pczt, address_n, hexEncode);
550
+ const outputs = await buildPCZTOutputsForTrezor(pczt, address_n, address, chain);
551
+ const result = await TrezorConnect.signTransaction({
552
+ branchId: global.consensusBranchId,
553
+ coin: "zcash",
554
+ expiry: global.expiryHeight,
555
+ inputs,
556
+ locktime: global.lockTime,
557
+ outputs: outputs as any,
558
+ overwintered: true,
559
+ version: global.txVersion,
560
+ versionGroupId: global.versionGroupId,
561
+ });
579
562
 
563
+ if (!result.success) {
580
564
  throw new SwapKitError({
581
565
  errorKey: "wallet_trezor_failed_to_sign_transaction",
582
566
  info: { chain, error: (result.payload as { error: string; code?: string }).error },
583
567
  });
584
- },
568
+ }
569
+
570
+ return {
571
+ serializedTx: result.payload.serializedTx,
572
+ signedPczt: await extractSignaturesFromSignedTx(result.payload.serializedTx, pczt),
573
+ };
585
574
  };
586
575
 
587
- const toolbox = getUtxoToolbox(Chain.Zcash);
576
+ const parseSignedZcashTransaction = async (signedTxHex: string) => {
577
+ const { ZcashTransaction: ZcashTx } = await import("@swapkit/utxo-signer");
578
+ return ZcashTx.fromHex(signedTxHex, { allowUnknownOutputs: true });
579
+ };
588
580
 
589
- const transfer = async (params: GenericTransferParams) => {
590
- if (!(address && params.recipient)) {
591
- throw new SwapKitError({
592
- errorKey: "wallet_missing_params",
593
- info: { address, recipient: params.recipient, wallet: WalletOption.TREZOR },
594
- });
595
- }
581
+ const signZcashTransaction = async (tx: ZcashTransaction) => {
582
+ const TrezorConnect = (await import("@trezor/connect-web")).default;
583
+ const { hex: hexEncode } = await import("@scure/base");
584
+ const address_n = hardenDerivationPath(derivationPath);
596
585
 
597
- const feeRate = params.feeRate || (await toolbox.getFeeRates())[params.feeOptionKey || FeeOption.Fast];
586
+ const inputs = buildZcashTxInputsForTrezor(tx, [], address_n, hexEncode);
587
+ const outputs = buildZcashTxOutputsForTrezor(tx, address_n, address, chain);
598
588
 
599
- const { tx, inputs: txInputs } = await toolbox.createTransaction({
600
- ...params,
601
- feeRate,
602
- fetchTxHex: false,
603
- sender: address,
589
+ const result = await TrezorConnect.signTransaction({
590
+ branchId: ZcashConsensusBranchId.NU6,
591
+ coin: "zcash",
592
+ expiry: 0,
593
+ inputs,
594
+ locktime: 0,
595
+ outputs: outputs as any,
596
+ overwintered: true,
597
+ version: 4,
598
+ versionGroupId: ZcashVersionGroupId.SAPLING,
604
599
  });
605
600
 
606
- const txHex = await signer.signTransaction(tx, txInputs);
607
- const broadcastResult = await toolbox.broadcastTx(txHex);
601
+ if (result.success) {
602
+ return parseSignedZcashTransaction(result.payload.serializedTx);
603
+ }
608
604
 
609
- return broadcastResult;
605
+ throw new SwapKitError({
606
+ errorKey: "wallet_trezor_failed_to_sign_transaction",
607
+ info: { chain, error: (result.payload as { error: string; code?: string }).error },
608
+ });
610
609
  };
611
610
 
612
- const transferWithPCZT = async (params: GenericTransferParams) => {
613
- if (!(address && params.recipient)) {
614
- throw new SwapKitError({
615
- errorKey: "wallet_missing_params",
616
- info: { address, recipient: params.recipient, wallet: WalletOption.TREZOR },
617
- });
618
- }
619
-
620
- const { createPCZT, OutScript } = await import("@swapkit/utxo-signer");
621
- const { hex: hexEncode } = await import("@scure/base");
622
- const { getUtxoApi } = await import("@swapkit/toolboxes/utxo");
611
+ const signer = {
612
+ getAddress: async () => address,
623
613
 
624
- const feeRate = params.feeRate || (await toolbox.getFeeRates())[params.feeOptionKey || FeeOption.Fast];
614
+ signPCZT: async (pczt: PCZT): Promise<PCZT> => {
615
+ return (await signPCZTWithSerializedTx(pczt)).signedPczt;
616
+ },
625
617
 
626
- const utxos = await getUtxoApi(Chain.Zcash).getUtxos({ address });
618
+ signTransaction: async (tx: ZcashSignableTransaction): Promise<ZcashTransaction> => {
619
+ if ("toPCZT" in tx) {
620
+ const { serializedTx } = await signPCZTWithSerializedTx(tx.toPCZT());
621
+ return parseSignedZcashTransaction(serializedTx);
622
+ }
627
623
 
628
- const { tx, inputs: txInputs } = await toolbox.createTransaction({
629
- ...params,
630
- feeRate,
631
- fetchTxHex: false,
632
- sender: address,
633
- });
624
+ if ("getGlobal" in tx) {
625
+ const { serializedTx } = await signPCZTWithSerializedTx(tx);
626
+ return parseSignedZcashTransaction(serializedTx);
627
+ }
634
628
 
635
- const pczt = createPCZT();
629
+ return signZcashTransaction(tx);
630
+ },
631
+ };
636
632
 
637
- for (const utxoInput of txInputs) {
638
- const utxo = utxos.find((u) => u.hash === utxoInput.hash && u.index === utxoInput.index);
639
- const scriptPubkey = utxo?.witnessUtxo?.script
640
- ? new Uint8Array(utxo.witnessUtxo.script)
641
- : OutScript.encode({ hash: hexEncode.decode((utxoInput as any).address || ""), type: "pkh" });
633
+ const toolbox = getUtxoToolbox(Chain.Zcash, { signer });
642
634
 
643
- pczt.addInput({
644
- index: utxoInput.index,
645
- scriptPubkey,
646
- txid: hexEncode.decode(utxoInput.hash).reverse() as unknown as Uint8Array,
647
- value: BigInt(utxoInput.value),
648
- });
635
+ const signAndBroadcastTransaction = async (tx: ZcashSignableTransaction) => {
636
+ if ("toPCZT" in tx) {
637
+ const { serializedTx } = await signPCZTWithSerializedTx(tx.toPCZT());
638
+ return toolbox.broadcastTx(serializedTx);
649
639
  }
650
640
 
651
- for (let i = 0; i < tx.outputsLength; i++) {
652
- const output = tx.getOutput(i);
653
- pczt.addOutput({ scriptPubkey: output.script || new Uint8Array(), value: output.amount || 0n });
641
+ if ("getGlobal" in tx) {
642
+ const { serializedTx } = await signPCZTWithSerializedTx(tx);
643
+ return toolbox.broadcastTx(serializedTx);
654
644
  }
655
645
 
656
- const signedPczt = await signer.signPCZT(pczt);
657
- signedPczt.finalizeAllInputs();
658
- const finalTx = signedPczt.extract();
659
- const broadcastResult = await toolbox.broadcastTx(finalTx.toHex());
660
-
661
- return broadcastResult;
646
+ return toolbox.signAndBroadcastTransaction(tx);
662
647
  };
663
648
 
664
- return {
665
- ...toolbox,
666
- address,
667
- signPCZT: signer.signPCZT,
668
- signTransaction: signer.signTransaction,
669
- transfer,
670
- transferWithPCZT,
671
- };
649
+ return { ...toolbox, address, signAndBroadcastTransaction, signPCZT: signer.signPCZT };
672
650
  }
673
651
 
674
652
  case Chain.Bitcoin:
@@ -739,7 +717,12 @@ async function getTrezorWallet<T extends Chain>({
739
717
  script_type: resolvedScriptType.input,
740
718
  }));
741
719
 
742
- const result = await TrezorConnect.signTransaction({ coin, inputs: trezorInputs, outputs });
720
+ const result = await TrezorConnect.signTransaction({
721
+ coin,
722
+ inputs: trezorInputs,
723
+ outputs,
724
+ refTxs: buildTrezorRefTxs(chain, inputs) as never,
725
+ });
743
726
 
744
727
  if (result.success) {
745
728
  return result.payload.serializedTx;
@@ -910,7 +893,14 @@ async function getTrezorWallet<T extends Chain>({
910
893
 
911
894
  const signTransactionWithMultipleInputs = async (
912
895
  tx: Transaction,
913
- inputs: Array<{ hash: string; index: number; value: number; derivationIndex: number; isChange: boolean }>,
896
+ inputs: Array<{
897
+ derivationIndex: number;
898
+ hash: string;
899
+ index: number;
900
+ isChange: boolean;
901
+ txHex?: string;
902
+ value: number;
903
+ }>,
914
904
  memo = "",
915
905
  ) => {
916
906
  const TrezorConnect = (await import("@trezor/connect-web")).default;
@@ -941,7 +931,12 @@ async function getTrezorWallet<T extends Chain>({
941
931
  };
942
932
  });
943
933
 
944
- const result = await TrezorConnect.signTransaction({ coin, inputs: trezorInputs, outputs });
934
+ const result = await TrezorConnect.signTransaction({
935
+ coin,
936
+ inputs: trezorInputs,
937
+ outputs,
938
+ refTxs: buildTrezorRefTxs(chain, inputs) as never,
939
+ });
945
940
 
946
941
  if (result.success) {
947
942
  return result.payload.serializedTx;
@@ -982,7 +977,7 @@ async function getTrezorWallet<T extends Chain>({
982
977
  const { tx, inputs: selectedInputs } = await toolbox.createTransaction({
983
978
  assetValue: assetValue as any,
984
979
  feeRate: txFeeRate,
985
- fetchTxHex: true,
980
+ fetchTxHex: chain === Chain.Dash,
986
981
  memo,
987
982
  recipient,
988
983
  sender: address,
@@ -990,7 +985,12 @@ async function getTrezorWallet<T extends Chain>({
990
985
 
991
986
  const inputsWithDerivation = selectedInputs.map((input: { hash: string; index: number; value: number }) => {
992
987
  const utxoInfo = utxos.find((u) => u.hash === input.hash && u.index === input.index);
993
- return { ...input, derivationIndex: utxoInfo?.derivationIndex ?? 0, isChange: utxoInfo?.isChange ?? false };
988
+ return {
989
+ ...input,
990
+ derivationIndex: utxoInfo?.derivationIndex ?? 0,
991
+ isChange: utxoInfo?.isChange ?? false,
992
+ txHex: utxoInfo?.txHex,
993
+ };
994
994
  });
995
995
 
996
996
  const signedTxHex = await signTransactionWithMultipleInputs(tx as Transaction, inputsWithDerivation, memo);
@@ -1020,7 +1020,7 @@ async function getTrezorWallet<T extends Chain>({
1020
1020
  const { tx, inputs } = await createTxMethod({
1021
1021
  ...rest,
1022
1022
  feeRate,
1023
- fetchTxHex: true,
1023
+ fetchTxHex: chain === Chain.Dash,
1024
1024
  memo,
1025
1025
  recipient,
1026
1026
  sender: address,
@@ -1041,6 +1041,8 @@ async function getTrezorWallet<T extends Chain>({
1041
1041
  const signAndBroadcastTransaction = shouldUseTrezorSerializedSigner(chain)
1042
1042
  ? async (tx: Transaction) => baseToolbox.broadcastTx(await signSerializedTransaction(tx))
1043
1043
  : toolbox.signAndBroadcastTransaction;
1044
+ const walletSignTransaction = shouldUseTrezorPsbtSigner(chain) ? toolbox.signTransaction : signTransaction;
1045
+ const walletTransfer = shouldUseTrezorPsbtSigner(chain) ? toolbox.transfer : transfer;
1044
1046
 
1045
1047
  async function getExtendedPublicKeyInfo({ accountIndex }: { accountIndex?: number } = {}) {
1046
1048
  const TrezorConnect = (await import("@trezor/connect-web")).default;
@@ -1200,9 +1202,9 @@ async function getTrezorWallet<T extends Chain>({
1200
1202
  getExtendedPublicKey,
1201
1203
  getExtendedPublicKeyInfo,
1202
1204
  signAndBroadcastTransaction,
1203
- signTransaction,
1205
+ signTransaction: walletSignTransaction,
1204
1206
  signTransactionWithMultipleInputs,
1205
- transfer,
1207
+ transfer: walletTransfer,
1206
1208
  transferFromMultipleAddresses,
1207
1209
  };
1208
1210
  }
@@ -1217,14 +1219,14 @@ export async function getTrezorExtendedPublicKey(
1217
1219
  derivationPath?: DerivationPathArray,
1218
1220
  { accountIndex }: { accountIndex?: number } = {},
1219
1221
  ): Promise<HardwareExtendedPublicKeyInfo | undefined> {
1220
- if (![Chain.BitcoinCash, Chain.Bitcoin, Chain.Dash, Chain.Dogecoin, Chain.Litecoin].includes(chain)) {
1222
+ if (![Chain.BitcoinCash, Chain.Bitcoin, Chain.Dash, Chain.Dogecoin, Chain.Litecoin, Chain.Zcash].includes(chain)) {
1221
1223
  throw new SwapKitError({ errorKey: "wallet_chain_not_supported", info: { chain, wallet: WalletOption.TREZOR } });
1222
1224
  }
1223
1225
 
1224
1226
  const { TrezorConnect } = await initTrezorConnect();
1225
1227
  const utxoChain = chain as UTXOChain;
1226
1228
  const resolvedDerivationPath = derivationPath ?? (NetworkDerivationPath[chain] as DerivationPathArray);
1227
- const coin = chain.toLowerCase();
1229
+ const coin = chain === Chain.Zcash ? "zcash" : chain.toLowerCase();
1228
1230
 
1229
1231
  const resolvedAccountPath = getUTXOAccountPath({
1230
1232
  accountIndex,
@@ -1346,7 +1348,7 @@ export const trezorWallet = createWallet({
1346
1348
  [Chain.Optimism]: true,
1347
1349
  [Chain.Polygon]: true,
1348
1350
  [Chain.XLayer]: true,
1349
- // ZEC: pending PCZT/TrezorConnect validation
1351
+ [Chain.Zcash]: true,
1350
1352
  },
1351
1353
  getExtendedPublicKey: getTrezorExtendedPublicKey,
1352
1354
  name: "connectTrezor",