@sidhujag/sysweb3-keyring 2.1.2 → 2.1.4

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 (148) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +55 -47
  3. package/cjs/current-vault-refusal.js +6 -0
  4. package/cjs/current-vault-refusal.js.map +1 -0
  5. package/cjs/evm-fees.js +29 -1
  6. package/cjs/evm-fees.js.map +1 -1
  7. package/cjs/hardware-transaction-authority.js +36 -0
  8. package/cjs/hardware-transaction-authority.js.map +1 -0
  9. package/cjs/index.js +5 -5
  10. package/cjs/index.js.map +1 -1
  11. package/cjs/keyring-core.js +466 -250
  12. package/cjs/keyring-core.js.map +1 -1
  13. package/cjs/keyring-host.js +113 -0
  14. package/cjs/keyring-host.js.map +1 -0
  15. package/cjs/keyring-manager.js +15 -55
  16. package/cjs/keyring-manager.js.map +1 -1
  17. package/cjs/ledger/host-types.js.map +1 -1
  18. package/cjs/ledger/index.js +44 -19
  19. package/cjs/ledger/index.js.map +1 -1
  20. package/cjs/providers.js +3 -1
  21. package/cjs/providers.js.map +1 -1
  22. package/cjs/software.js +10 -43
  23. package/cjs/software.js.map +1 -1
  24. package/cjs/storage-core.js +44 -4
  25. package/cjs/storage-core.js.map +1 -1
  26. package/cjs/transactions/ethereum.js +358 -294
  27. package/cjs/transactions/ethereum.js.map +1 -1
  28. package/cjs/transactions/evm-cancellation.js +43 -0
  29. package/cjs/transactions/evm-cancellation.js.map +1 -0
  30. package/cjs/transactions/evm-local-signer.js +15 -0
  31. package/cjs/transactions/evm-local-signer.js.map +1 -1
  32. package/cjs/transactions/evm-submission.js +61 -65
  33. package/cjs/transactions/evm-submission.js.map +1 -1
  34. package/cjs/transactions/hardware-psbt.js +11 -8
  35. package/cjs/transactions/hardware-psbt.js.map +1 -1
  36. package/cjs/transactions/index.js +10 -2
  37. package/cjs/transactions/index.js.map +1 -1
  38. package/cjs/transactions/prepared-evm.js +10 -7
  39. package/cjs/transactions/prepared-evm.js.map +1 -1
  40. package/cjs/transactions/syscoin-core.js +282 -202
  41. package/cjs/transactions/syscoin-core.js.map +1 -1
  42. package/cjs/trezor/connect.js +19 -2
  43. package/cjs/trezor/connect.js.map +1 -1
  44. package/cjs/trezor/index.js +38 -16
  45. package/cjs/trezor/index.js.map +1 -1
  46. package/cjs/types.js.map +1 -1
  47. package/cjs/utils/owned-blockbook-account.js +45 -0
  48. package/cjs/utils/owned-blockbook-account.js.map +1 -0
  49. package/esm/current-vault-refusal.js +3 -0
  50. package/esm/current-vault-refusal.js.map +1 -0
  51. package/esm/evm-fees.js +242 -0
  52. package/esm/evm-fees.js.map +1 -0
  53. package/esm/hardware-transaction-authority.js +33 -0
  54. package/esm/hardware-transaction-authority.js.map +1 -0
  55. package/esm/initial-state.js +102 -0
  56. package/esm/initial-state.js.map +1 -0
  57. package/esm/keyring-core.js +2781 -0
  58. package/esm/keyring-core.js.map +1 -0
  59. package/esm/keyring-host.js +108 -0
  60. package/esm/keyring-host.js.map +1 -0
  61. package/esm/ledger/dmk-host.js +31 -0
  62. package/esm/ledger/dmk-host.js.map +1 -0
  63. package/esm/ledger/dmk-selected-host.js +6 -0
  64. package/esm/ledger/dmk-selected-host.js.map +1 -0
  65. package/esm/ledger/dmk-session.js +154 -34
  66. package/esm/ledger/dmk-session.js.map +1 -1
  67. package/esm/ledger/dmk-utxo.js +9 -3
  68. package/esm/ledger/dmk-utxo.js.map +1 -1
  69. package/esm/ledger/host-types.js.map +1 -1
  70. package/esm/ledger/index.js +44 -19
  71. package/esm/ledger/index.js.map +1 -1
  72. package/esm/ledger-host.js +3 -27
  73. package/esm/ledger-host.js.map +1 -1
  74. package/esm/network-utils.js +70 -0
  75. package/esm/network-utils.js.map +1 -0
  76. package/esm/providers.js +3 -1
  77. package/esm/providers.js.map +1 -1
  78. package/esm/software-hd-account.js +20 -0
  79. package/esm/software-hd-account.js.map +1 -0
  80. package/esm/storage-core.js +258 -0
  81. package/esm/storage-core.js.map +1 -0
  82. package/esm/transactions/ethereum.js +2753 -0
  83. package/esm/transactions/ethereum.js.map +1 -0
  84. package/esm/transactions/evm-cancellation.js +40 -0
  85. package/esm/transactions/evm-cancellation.js.map +1 -0
  86. package/esm/transactions/evm-local-signer.js +202 -0
  87. package/esm/transactions/evm-local-signer.js.map +1 -0
  88. package/esm/transactions/evm-submission.js +187 -0
  89. package/esm/transactions/evm-submission.js.map +1 -0
  90. package/esm/transactions/prepared-evm.js +194 -0
  91. package/esm/transactions/prepared-evm.js.map +1 -0
  92. package/esm/transactions/syscoin-core.js +691 -0
  93. package/esm/transactions/syscoin-core.js.map +1 -0
  94. package/esm/trezor/connect.js +19 -2
  95. package/esm/trezor/connect.js.map +1 -1
  96. package/esm/trezor/index.js +38 -16
  97. package/esm/trezor/index.js.map +1 -1
  98. package/esm/types.js.map +1 -1
  99. package/esm/utils/excluded-utxos.js +53 -0
  100. package/esm/utils/excluded-utxos.js.map +1 -0
  101. package/esm/utils/owned-blockbook-account.js +9 -0
  102. package/esm/utils/owned-blockbook-account.js.map +1 -0
  103. package/esm/utils/psbt.js +32 -0
  104. package/esm/utils/psbt.js.map +1 -0
  105. package/esm/utils/watch-descriptor.js +138 -0
  106. package/esm/utils/watch-descriptor.js.map +1 -0
  107. package/package.json +17 -11
  108. package/types/current-vault-refusal.d.ts +2 -0
  109. package/types/evm-fees.d.ts +25 -0
  110. package/types/hardware-transaction-authority.d.ts +5 -0
  111. package/types/index.d.ts +3 -3
  112. package/types/keyring-core.d.ts +48 -44
  113. package/types/keyring-host.d.ts +46 -0
  114. package/types/keyring-manager.d.ts +14 -7
  115. package/types/ledger/dmk-host.d.ts +7 -0
  116. package/types/ledger/dmk-selected-host.d.ts +7 -0
  117. package/types/ledger/dmk-session.d.ts +10 -2
  118. package/types/ledger/host-types.d.ts +4 -1
  119. package/types/ledger/index.d.ts +1 -1
  120. package/types/ledger/types.d.ts +3 -2
  121. package/types/ledger-host.d.ts +6 -2
  122. package/types/providers.d.ts +1 -1
  123. package/types/software.d.ts +7 -19
  124. package/types/storage-core.d.ts +3 -1
  125. package/types/transactions/ethereum.d.ts +14 -11
  126. package/types/transactions/evm-cancellation.d.ts +10 -0
  127. package/types/transactions/evm-local-signer.d.ts +2 -2
  128. package/types/transactions/evm-submission.d.ts +11 -4
  129. package/types/transactions/hardware-psbt.d.ts +1 -1
  130. package/types/transactions/index.d.ts +3 -2
  131. package/types/transactions/prepared-evm.d.ts +4 -1
  132. package/types/transactions/syscoin-core.d.ts +48 -21
  133. package/types/trezor/connect.d.ts +6 -3
  134. package/types/trezor/index.d.ts +4 -3
  135. package/types/types.d.ts +95 -63
  136. package/types/utils/owned-blockbook-account.d.ts +3 -0
  137. package/cjs/hardware-wallet-manager-singleton.js +0 -34
  138. package/cjs/hardware-wallet-manager-singleton.js.map +0 -1
  139. package/cjs/storage.js +0 -20
  140. package/cjs/storage.js.map +0 -1
  141. package/cjs/transactions/syscoin.js +0 -16
  142. package/cjs/transactions/syscoin.js.map +0 -1
  143. package/cjs/utils/blockbook-cache.js +0 -76
  144. package/cjs/utils/blockbook-cache.js.map +0 -1
  145. package/types/hardware-wallet-manager-singleton.d.ts +0 -19
  146. package/types/storage.d.ts +0 -5
  147. package/types/transactions/syscoin.d.ts +0 -9
  148. package/types/utils/blockbook-cache.d.ts +0 -6
@@ -14,8 +14,10 @@ const ethers_v6_1 = require("../ethers-v6");
14
14
  const evm_fees_1 = require("../evm-fees");
15
15
  const providers_1 = require("../providers");
16
16
  const types_1 = require("../types");
17
+ const evm_cancellation_1 = require("./evm-cancellation");
17
18
  const evm_local_signer_1 = require("./evm-local-signer");
18
19
  const evm_submission_1 = require("./evm-submission");
20
+ const prepared_evm_1 = require("./prepared-evm");
19
21
  const derivation_paths_1 = require("../utils/derivation-paths");
20
22
  const hardwareSignerUnavailable = (hardware) => Object.assign(new Error(`${hardware} signer is unavailable in this host`), {
21
23
  code: 'HARDWARE_SIGNER_UNAVAILABLE',
@@ -115,14 +117,16 @@ const authenticateReplacementOriginal = (transaction, requestedHash, expectedSen
115
117
  });
116
118
  };
117
119
  const assertSynchronousOriginalValidation = (result, message = 'Original transaction validator must be synchronous') => {
120
+ if (result === undefined)
121
+ return;
118
122
  if (result !== null &&
119
123
  (typeof result === 'object' || typeof result === 'function') &&
120
124
  typeof result.then === 'function') {
121
125
  // A void callback type also accepts async functions. Decline their pending
122
126
  // decision and consume a later rejection without allowing signing to start.
123
127
  void Promise.resolve(result).catch(() => undefined);
124
- throw new Error(message);
125
128
  }
129
+ throw new Error(message);
126
130
  };
127
131
  class EthereumTransactions {
128
132
  constructor(getNetwork, getDecryptedPrivateKey, getState, ledgerSigner, trezorSigner, captureSigningContext, captureSubmissionLifecycle) {
@@ -148,6 +152,14 @@ class EthereumTransactions {
148
152
  if (type === types_1.KeyringAccountType.Trezor || account?.isTrezorWallet)
149
153
  this.requireTrezorSigner();
150
154
  };
155
+ this.captureSessionGuard = () => {
156
+ const guard = this.captureSigningContext();
157
+ if (typeof guard !== 'function') {
158
+ void Promise.resolve(guard).catch(() => undefined);
159
+ throw new TypeError('Signing context must return a synchronous guard');
160
+ }
161
+ return () => assertSynchronousOriginalValidation(guard(), 'Signing context guard must be synchronous');
162
+ };
151
163
  this.captureEvmSigningContext = (target) => {
152
164
  this.assertHardwareSignerAvailable(target);
153
165
  const state = this.getState();
@@ -161,10 +173,10 @@ class EthereumTransactions {
161
173
  throw new Error('Active account not found');
162
174
  const account = { address: selectedAccount.address };
163
175
  const network = { ...state.activeNetwork };
164
- const assertSession = this.captureSigningContext?.();
176
+ const assertSession = this.captureSessionGuard();
165
177
  const provider = this.web3Provider;
166
178
  const assertCurrent = () => {
167
- assertSession?.();
179
+ assertSession();
168
180
  const current = this.getState();
169
181
  const currentAccount = current.accounts[activeAccountType]?.[activeAccountId];
170
182
  if (current.activeAccountId !== selectedId ||
@@ -190,17 +202,30 @@ class EthereumTransactions {
190
202
  assertCurrent,
191
203
  };
192
204
  };
193
- this.captureSubmission = (operation, account, type, id, network, assertCurrent, originalHash) => (0, evm_submission_1.captureEvmSubmission)({
194
- operation,
195
- account: { address: account.address, type, id },
196
- network: {
197
- kind: network.kind,
198
- chainId: network.chainId,
199
- slip44: network.slip44,
200
- url: network.url,
201
- },
202
- ...(originalHash !== undefined ? { originalHash } : {}),
203
- }, assertCurrent, this.captureSubmissionLifecycle);
205
+ this.captureSubmission = (operation, account, type, id, network, assertCurrent, originalHash) => {
206
+ if (!this.captureSubmissionLifecycle)
207
+ throw new TypeError('A durable EVM submission lifecycle is required');
208
+ return (0, evm_submission_1.captureEvmSubmission)({
209
+ operation,
210
+ account: { address: account.address, type, id },
211
+ network: {
212
+ kind: network.kind,
213
+ chainId: network.chainId,
214
+ slip44: network.slip44,
215
+ url: network.url,
216
+ },
217
+ ...(originalHash !== undefined ? { originalHash } : {}),
218
+ }, assertCurrent, this.captureSubmissionLifecycle);
219
+ };
220
+ this.authorizeHardwareTransaction = async (provider, assertCurrent, signal = this.abortController.signal) => {
221
+ assertCurrent();
222
+ await provider.verifyConfiguredChainId();
223
+ assertCurrent();
224
+ return { assertCurrent, signal };
225
+ };
226
+ this.submitSignedHardwareTransaction = (provider, raw, broadcast, submission, beforeBroadcast, expectedUnsigned) => (0, evm_submission_1.submitSignedHardwareEvmTransaction)(raw, broadcast, submission, beforeBroadcast, expectedUnsigned,
227
+ // A device confirmation or journal write can outlast the earlier check.
228
+ () => provider.verifyConfiguredChainId(true));
204
229
  this.signTypedData = async (addr, typedData, version) => {
205
230
  const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
206
231
  const { activeAccountType, accounts, activeAccountId } = this.getState();
@@ -554,35 +579,45 @@ class EthereumTransactions {
554
579
  throw new Error(`Invalid numeric field "${fieldName}" for EVM tx: ${String(value)}`);
555
580
  }
556
581
  };
557
- this.resolveEvmFeeParams = async ({ gasPrice, isLegacy = false, maxFeePerGas, maxPriorityFeePerGas, }, provider = this.web3Provider) => {
558
- if (isLegacy) {
559
- return {
560
- gasPrice: gasPrice || (await provider.getGasPrice()),
561
- isLegacy: true,
562
- maxFeePerGas: undefined,
563
- maxPriorityFeePerGas: undefined,
564
- };
565
- }
566
- if (!maxFeePerGas || maxPriorityFeePerGas == null) {
567
- const feeData = await provider.getFeeData();
568
- maxFeePerGas = maxFeePerGas || feeData.maxFeePerGas || feeData.gasPrice;
569
- maxPriorityFeePerGas =
570
- maxPriorityFeePerGas ?? feeData.maxPriorityFeePerGas;
571
- if (!maxFeePerGas || maxPriorityFeePerGas == null) {
572
- return {
573
- gasPrice: feeData.gasPrice || (await provider.getGasPrice()),
574
- isLegacy: true,
575
- maxFeePerGas: undefined,
576
- maxPriorityFeePerGas: undefined,
577
- };
578
- }
579
- }
580
- return {
581
- gasPrice: undefined,
582
- isLegacy: false,
583
- maxFeePerGas,
584
- maxPriorityFeePerGas,
582
+ this.captureReviewedTokenApproval = (approval, standard) => {
583
+ if (!approval ||
584
+ typeof approval.assertCurrent !== 'function' ||
585
+ !(approval.signal instanceof AbortSignal))
586
+ throw new Error(`${standard} transaction approval required`);
587
+ const { assertCurrent, signal } = approval;
588
+ const assertApproval = () => {
589
+ if (signal.aborted)
590
+ throw Object.assign(new Error(`${standard} transaction approval expired`), {
591
+ transactionNotBroadcast: true,
592
+ });
593
+ assertSynchronousOriginalValidation(assertCurrent(), `${standard} transaction approval must be synchronous`);
585
594
  };
595
+ assertApproval();
596
+ return { assertApproval, signal };
597
+ };
598
+ this.reviewedTokenFees = (request, chainId, standard) => {
599
+ const { isLegacy } = request;
600
+ if (typeof isLegacy !== 'boolean' ||
601
+ (isLegacy
602
+ ? request.maxFeePerGas !== undefined ||
603
+ request.maxPriorityFeePerGas !== undefined
604
+ : request.gasPrice !== undefined))
605
+ throw new Error(`Invalid ${standard} transaction fee model`);
606
+ const gasLimit = ethers_v6_1.BigNumber.from((0, evm_fees_1.assertEvmGasLimit)((0, evm_fees_1.parseEvmFeeQuantity)(request.gasLimit, `${standard} gas limit`), BigInt(chainId)));
607
+ const gasPrice = isLegacy
608
+ ? ethers_v6_1.BigNumber.from((0, evm_fees_1.parseEvmFeeQuantity)(request.gasPrice, `${standard} gas price`))
609
+ : undefined;
610
+ const maxFeePerGas = !isLegacy
611
+ ? ethers_v6_1.BigNumber.from((0, evm_fees_1.parseEvmFeeQuantity)(request.maxFeePerGas, `${standard} maximum fee`))
612
+ : undefined;
613
+ const maxPriorityFeePerGas = !isLegacy
614
+ ? ethers_v6_1.BigNumber.from((0, evm_fees_1.parseEvmFeeQuantity)(request.maxPriorityFeePerGas, `${standard} priority fee`))
615
+ : undefined;
616
+ if (gasPrice?.isZero() ||
617
+ (maxFeePerGas &&
618
+ (maxFeePerGas.isZero() || maxPriorityFeePerGas.gt(maxFeePerGas))))
619
+ throw new Error(`Invalid ${standard} fees`);
620
+ return { gasLimit, isLegacy, gasPrice, maxFeePerGas, maxPriorityFeePerGas };
586
621
  };
587
622
  this.getData = ({ contractAddress, receivingAddress, value, }) => {
588
623
  const abi = (0, sysweb3_utils_1.getErc20Abi)();
@@ -673,7 +708,8 @@ class EthereumTransactions {
673
708
  !/^(0|[1-9][0-9]{0,77})$/.test(approvedMaximumFee))) {
674
709
  throw new Error('Cancellation fee approval required');
675
710
  }
676
- const assertHostContext = options.assertCurrentContext;
711
+ const { assertCurrentContext, validateOriginal } = options;
712
+ const assertHostContext = () => assertSynchronousOriginalValidation(assertCurrentContext(), 'Transaction context guard must be synchronous');
677
713
  assertHostContext();
678
714
  const state = this.getState();
679
715
  const selectedType = state.activeAccountType;
@@ -696,9 +732,9 @@ class EthereumTransactions {
696
732
  if (!previewOnly)
697
733
  this.assertHardwareSignerAvailable(targetAccount);
698
734
  const provider = this.web3Provider;
699
- const assertSession = this.captureSigningContext?.();
735
+ const assertSession = this.captureSessionGuard();
700
736
  const assertKeyringContext = () => {
701
- assertSession?.();
737
+ assertSession();
702
738
  const current = this.getState();
703
739
  if (current.activeAccountType !== selectedType ||
704
740
  current.activeAccountId !== selectedId ||
@@ -712,14 +748,15 @@ class EthereumTransactions {
712
748
  throw new Error('Wallet signing context changed');
713
749
  }
714
750
  };
715
- const assertCurrent = () => {
751
+ const assertSigningCurrent = () => {
716
752
  assertHostContext();
717
753
  assertKeyringContext();
718
754
  };
719
- assertCurrent();
755
+ assertSigningCurrent();
720
756
  const submission = previewOnly
721
757
  ? undefined
722
- : this.captureSubmission('cancel', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertCurrent, txHash);
758
+ : this.captureSubmission('cancel', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, txHash);
759
+ const assertCurrent = submission?.assertCurrent ?? assertSigningCurrent;
723
760
  const currentEip1559Fees = async () => {
724
761
  const feeData = await this.getFeeDataWithDynamicMaxPriorityFeePerGas(provider);
725
762
  assertCurrent();
@@ -738,7 +775,18 @@ class EthereumTransactions {
738
775
  let tx = response === null
739
776
  ? null
740
777
  : authenticateReplacementOriginal(response, txHash.toLowerCase(), activeAccount.address.toLowerCase(), activeNetwork.chainId);
741
- assertSynchronousOriginalValidation(options.validateOriginal(tx));
778
+ const software = activeAccountType !== types_1.KeyringAccountType.Ledger &&
779
+ activeAccountType !== types_1.KeyringAccountType.Trezor;
780
+ const softwareNonce = software ? tx?.nonce ?? fallbackNonce : undefined;
781
+ // The host validator may retain the wrapped response and its fee objects.
782
+ const softwareOriginalFees = software && tx
783
+ ? {
784
+ gasPrice: tx.gasPrice.toBigInt(),
785
+ maxFeePerGas: tx.maxFeePerGas.toBigInt(),
786
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas.toBigInt(),
787
+ }
788
+ : undefined;
789
+ assertSynchronousOriginalValidation(validateOriginal(tx));
742
790
  assertCurrent();
743
791
  // If transaction not found, create a minimal tx object with current gas prices
744
792
  // This handles cases where tx with 0 gas never made it to the mempool
@@ -753,7 +801,6 @@ class EthereumTransactions {
753
801
  value: ethers_v6_1.Zero,
754
802
  nonce: fallbackNonce,
755
803
  gasPrice: currentGasPrice,
756
- gasLimit: ethers_v6_1.BigNumber.from(42000),
757
804
  data: '0x',
758
805
  };
759
806
  }
@@ -766,7 +813,6 @@ class EthereumTransactions {
766
813
  nonce: fallbackNonce,
767
814
  maxFeePerGas: feeData.maxFeePerGas,
768
815
  maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
769
- gasLimit: ethers_v6_1.BigNumber.from(42000),
770
816
  data: '0x',
771
817
  };
772
818
  }
@@ -778,67 +824,36 @@ class EthereumTransactions {
778
824
  error: true,
779
825
  };
780
826
  }
781
- // If the original tx has 0 or very low gas price, fetch current network gas prices
782
- const oldTxsGasValues = {
783
- maxFeePerGas: tx.maxFeePerGas,
784
- maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
785
- gasPrice: tx.gasPrice,
786
- gasLimit: tx.gasLimit,
787
- };
827
+ const { gasPrice: originalGasPrice, maxFeePerGas: originalMaxFeePerGas, maxPriorityFeePerGas: originalMaxPriorityFeePerGas, } = softwareOriginalFees ?? tx;
828
+ let feePlan;
788
829
  if (isLegacy) {
789
- const currentGasPrice = nonNegativeFeeQuantity(await provider.getGasPrice(), 'cancellation gas price');
830
+ const currentGasPrice = (0, evm_fees_1.parseEvmFeeQuantity)(await provider.getGasPrice(), 'cancellation gas price');
790
831
  assertCurrent();
791
- if (oldTxsGasValues.gasPrice != null) {
792
- oldTxsGasValues.gasPrice = nonNegativeFeeQuantity(oldTxsGasValues.gasPrice, 'original gas price');
793
- }
794
- if (!oldTxsGasValues.gasPrice ||
795
- oldTxsGasValues.gasPrice.isZero() ||
796
- oldTxsGasValues.gasPrice.lt(currentGasPrice)) {
797
- oldTxsGasValues.gasPrice = currentGasPrice;
798
- }
832
+ feePlan = (0, evm_fees_1.planEvmCancellationFees)({
833
+ type: 0,
834
+ currentGasPrice,
835
+ originalGasPrice: originalGasPrice == null
836
+ ? undefined
837
+ : (0, evm_fees_1.parseEvmFeeQuantity)(originalGasPrice, 'original gas price'),
838
+ });
799
839
  }
800
840
  else {
801
- const { maxFeePerGas: currentMaxFeePerGas, maxPriorityFeePerGas: currentMaxPriorityFeePerGas, } = await currentEip1559Fees();
802
- if (oldTxsGasValues.maxFeePerGas != null) {
803
- oldTxsGasValues.maxFeePerGas = nonNegativeFeeQuantity(oldTxsGasValues.maxFeePerGas, 'original maximum fee');
804
- }
805
- if (oldTxsGasValues.maxPriorityFeePerGas != null) {
806
- oldTxsGasValues.maxPriorityFeePerGas = nonNegativeFeeQuantity(oldTxsGasValues.maxPriorityFeePerGas, 'original priority fee');
807
- }
808
- if (!oldTxsGasValues.maxFeePerGas ||
809
- oldTxsGasValues.maxFeePerGas.isZero() ||
810
- oldTxsGasValues.maxFeePerGas.lt(currentMaxFeePerGas)) {
811
- oldTxsGasValues.maxFeePerGas = currentMaxFeePerGas;
812
- }
813
- if (!oldTxsGasValues.maxPriorityFeePerGas ||
814
- oldTxsGasValues.maxPriorityFeePerGas.lt(currentMaxPriorityFeePerGas)) {
815
- oldTxsGasValues.maxPriorityFeePerGas = currentMaxPriorityFeePerGas;
816
- }
817
- if (oldTxsGasValues.maxFeePerGas.lt(oldTxsGasValues.maxPriorityFeePerGas)) {
818
- oldTxsGasValues.maxFeePerGas = oldTxsGasValues.maxPriorityFeePerGas;
819
- }
820
- }
821
- // Preserve the existing 20% replacement policy with exact integer math.
822
- // Nodes also require a strictly larger cap and tip than an old pending
823
- // transaction; zero/small fees therefore need at least a one-wei bump.
824
- // This is a replacement bump, not a fabricated network priority floor.
825
- const bumpCancelFee = (fee) => {
826
- const percentageBump = fee.mul(120).div(100);
827
- const strictBump = fee.add(1);
828
- return percentageBump.gt(strictBump) ? percentageBump : strictBump;
829
- };
830
- const newGasValues = {
831
- gasLimit: ethers_v6_1.BigNumber.from(42000),
832
- ...(isLegacy
833
- ? { gasPrice: bumpCancelFee(oldTxsGasValues.gasPrice) }
834
- : {
835
- maxFeePerGas: bumpCancelFee(oldTxsGasValues.maxFeePerGas),
836
- maxPriorityFeePerGas: bumpCancelFee(oldTxsGasValues.maxPriorityFeePerGas),
837
- }),
838
- };
839
- const maximumFee = newGasValues.gasLimit.mul(isLegacy ? newGasValues.gasPrice : newGasValues.maxFeePerGas);
841
+ const currentFees = await currentEip1559Fees();
842
+ feePlan = (0, evm_fees_1.planEvmCancellationFees)({
843
+ type: 2,
844
+ currentMaxFeePerGas: currentFees.maxFeePerGas.toBigInt(),
845
+ currentMaxPriorityFeePerGas: currentFees.maxPriorityFeePerGas.toBigInt(),
846
+ originalMaxFeePerGas: originalMaxFeePerGas == null
847
+ ? undefined
848
+ : (0, evm_fees_1.parseEvmFeeQuantity)(originalMaxFeePerGas, 'original maximum fee'),
849
+ originalMaxPriorityFeePerGas: originalMaxPriorityFeePerGas == null
850
+ ? undefined
851
+ : (0, evm_fees_1.parseEvmFeeQuantity)(originalMaxPriorityFeePerGas, 'original priority fee'),
852
+ });
853
+ }
854
+ const maximumFee = ethers_v6_1.BigNumber.from(feePlan.maximumFee);
840
855
  assertCurrent();
841
- if (previewOnly)
856
+ if (!submission)
842
857
  return {
843
858
  isCanceled: false,
844
859
  error: false,
@@ -849,27 +864,27 @@ class EthereumTransactions {
849
864
  const assertApprovedEnvelope = (transaction) => {
850
865
  const gas = nonNegativeFeeQuantity(transaction.gasLimit, 'cancellation gas limit');
851
866
  const cap = nonNegativeFeeQuantity(isLegacy ? transaction.gasPrice : transaction.maxFeePerGas, 'cancellation maximum fee');
852
- if (!gas.eq(42000) || gas.mul(cap).gt(approvedMaximumFee))
867
+ if (!gas.eq(feePlan.gasLimit) || gas.mul(cap).gt(approvedMaximumFee))
853
868
  throw new Error('Cancellation fee exceeds approval');
854
869
  };
855
870
  // Base cancel transaction parameters (same for all wallet types)
856
871
  const baseCancelTx = {
857
- nonce: tx.nonce,
872
+ nonce: software ? softwareNonce : tx.nonce,
858
873
  from: activeAccount.address,
859
874
  to: activeAccount.address,
860
875
  value: ethers_v6_1.Zero,
861
- gasLimit: newGasValues.gasLimit,
876
+ gasLimit: ethers_v6_1.BigNumber.from(feePlan.gasLimit),
862
877
  };
863
- const changedTxToCancel = isLegacy
878
+ const changedTxToCancel = feePlan.type === 0
864
879
  ? {
865
880
  ...baseCancelTx,
866
- gasPrice: newGasValues.gasPrice,
881
+ gasPrice: ethers_v6_1.BigNumber.from(feePlan.gasPrice),
867
882
  type: 0, // Force Type 0 for legacy cancel
868
883
  }
869
884
  : {
870
885
  ...baseCancelTx,
871
- maxFeePerGas: newGasValues.maxFeePerGas,
872
- maxPriorityFeePerGas: newGasValues.maxPriorityFeePerGas,
886
+ maxFeePerGas: ethers_v6_1.BigNumber.from(feePlan.maxFeePerGas),
887
+ maxPriorityFeePerGas: ethers_v6_1.BigNumber.from(feePlan.maxPriorityFeePerGas),
873
888
  // Don't set type - let ethers auto-detect
874
889
  };
875
890
  // Ledger cancel handler
@@ -901,7 +916,7 @@ class EthereumTransactions {
901
916
  rawTx: rawTx.replace('0x', ''),
902
917
  accountIndex: activeAccountId,
903
918
  expectedAddress: activeAccount.address,
904
- });
919
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent));
905
920
  assertCurrent();
906
921
  const formattedSignature = {
907
922
  r: `0x${signature.r}`,
@@ -912,7 +927,7 @@ class EthereumTransactions {
912
927
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
913
928
  assertCurrent();
914
929
  assertHardwareTransactionSender(signedTx, activeAccount.address);
915
- const transactionResponse = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
930
+ const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
916
931
  return {
917
932
  isCanceled: true,
918
933
  transaction: transactionResponse,
@@ -974,13 +989,13 @@ class EthereumTransactions {
974
989
  tx: txFormattedForTrezor,
975
990
  index: activeAccountId.toString(),
976
991
  slip44: activeNetwork.slip44,
977
- });
992
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent));
978
993
  assertCurrent();
979
994
  if (signature.success) {
980
995
  const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
981
996
  assertCurrent();
982
997
  assertHardwareTransactionSender(signedTx, activeAccount.address);
983
- const transactionResponse = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
998
+ const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
984
999
  return {
985
1000
  isCanceled: true,
986
1001
  transaction: transactionResponse,
@@ -1009,7 +1024,15 @@ class EthereumTransactions {
1009
1024
  if (address?.toLowerCase() !== activeAccount.address.toLowerCase()) {
1010
1025
  throw new Error('Wallet signing context changed');
1011
1026
  }
1012
- const transactionResponse = await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, changedTxToCancel, assertCurrent, assertCurrent, assertApprovedEnvelope, submission);
1027
+ const transactionResponse = await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, changedTxToCancel, assertCurrent, assertCurrent, (transaction) => {
1028
+ assertApprovedEnvelope(transaction);
1029
+ (0, evm_cancellation_1.assertEvmCancellationEnvelope)(serializeTransaction(transaction), {
1030
+ payer: activeAccount.address,
1031
+ chainId: activeNetwork.chainId,
1032
+ nonce: softwareNonce,
1033
+ feePlan,
1034
+ });
1035
+ }, submission);
1013
1036
  if (transactionResponse) {
1014
1037
  return {
1015
1038
  isCanceled: true,
@@ -1041,21 +1064,31 @@ class EthereumTransactions {
1041
1064
  }
1042
1065
  };
1043
1066
  //TODO: This function needs to be refactored
1044
- this.sendFormattedTransaction = async (params, isLegacy, options) => {
1067
+ this.sendFormattedTransaction = async (...args) => {
1068
+ const [params, isLegacy, options] = args;
1045
1069
  let broadcastAttempted = false;
1046
1070
  try {
1047
- if (options !== undefined &&
1048
- (!options || typeof options !== 'object' || Array.isArray(options)))
1049
- throw new Error('Invalid transaction context options');
1071
+ if (!options ||
1072
+ typeof options !== 'object' ||
1073
+ Array.isArray(options) ||
1074
+ (options.mode !== 'reviewed-native' &&
1075
+ options.mode !== 'specialized') ||
1076
+ typeof options.assertCurrent !== 'function')
1077
+ throw new Error('Explicit transaction approval is required');
1050
1078
  // Capture once; later caller mutations cannot replace this invocation's
1051
1079
  // admission decision while chain checks or device approval are pending.
1052
- const assertHostCurrent = options?.assertCurrent;
1053
- if (assertHostCurrent !== undefined &&
1054
- typeof assertHostCurrent !== 'function')
1055
- throw new Error('Invalid transaction context guard');
1080
+ const { assertCurrent: assertHostCurrent, mode } = options;
1081
+ const assertApproved = () => {
1082
+ const result = assertHostCurrent();
1083
+ if (result !== undefined) {
1084
+ assertSynchronousOriginalValidation(result, 'Transaction context guard must be synchronous');
1085
+ throw new Error('Transaction context guard must return undefined');
1086
+ }
1087
+ };
1088
+ assertApproved();
1056
1089
  return await this.sendFormattedTransactionInternal(params, isLegacy, () => {
1057
1090
  broadcastAttempted = true;
1058
- }, assertHostCurrent);
1091
+ }, assertApproved, mode);
1059
1092
  }
1060
1093
  catch (error) {
1061
1094
  // A broadcast transport error may mean the node accepted the signed
@@ -1072,26 +1105,58 @@ class EthereumTransactions {
1072
1105
  throw submissionError;
1073
1106
  }
1074
1107
  };
1075
- this.sendFormattedTransactionInternal = async (params, isLegacy, beforeBroadcast, assertHostCurrent) => {
1108
+ this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode) => {
1076
1109
  // Hardware approval may outlive the selected account, network or unlocked
1077
1110
  // session. Keep immutable identities and one provider for this invocation.
1078
1111
  const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertKeyringCurrent, } = this.captureEvmSigningContext();
1079
- const assertCurrent = () => {
1080
- const result = assertHostCurrent?.();
1081
- if (result !== undefined) {
1082
- assertSynchronousOriginalValidation(result, 'Transaction context guard must be synchronous');
1083
- throw new Error('Transaction context guard must return undefined');
1084
- }
1112
+ const assertSigningCurrent = () => {
1113
+ assertHostCurrent();
1085
1114
  assertKeyringCurrent();
1086
1115
  };
1087
1116
  const assertBeforeBroadcast = () => {
1088
1117
  assertCurrent();
1089
1118
  beforeBroadcast();
1090
1119
  };
1091
- assertCurrent();
1092
- const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertCurrent);
1120
+ const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent);
1121
+ const assertCurrent = submission.assertCurrent;
1122
+ if (mode === 'reviewed-native') {
1123
+ const { transaction } = (0, prepared_evm_1.snapshotPreparedEvmRequest)({
1124
+ account: {
1125
+ id: activeAccountId,
1126
+ type: activeAccountType,
1127
+ expectedAddress: activeAccount.address,
1128
+ },
1129
+ network: {
1130
+ kind: activeNetwork.kind,
1131
+ chainId: String(activeNetwork.chainId),
1132
+ slip44: activeNetwork.slip44,
1133
+ url: activeNetwork.url,
1134
+ },
1135
+ transaction: input,
1136
+ });
1137
+ const minimum = this.gasOverrides.minGasLimit;
1138
+ if (transaction.data !== '0x' ||
1139
+ BigInt(transaction.value) === 0n ||
1140
+ BigInt(transaction.gasLimit) < 21000n ||
1141
+ (transaction.type === 2 && BigInt(transaction.maxFeePerGas) === 0n) ||
1142
+ (minimum && BigInt(transaction.gasLimit) < minimum.toBigInt()) ||
1143
+ (isLegacy !== undefined && isLegacy !== (transaction.type === 0)))
1144
+ throw new Error('Invalid reviewed native transfer approval');
1145
+ assertCurrent();
1146
+ await provider.verifyConfiguredChainId();
1147
+ assertCurrent();
1148
+ if (this.gasOverrides.minGasLimit !== minimum)
1149
+ throw new Error('Reviewed native gas policy changed');
1150
+ const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
1151
+ if (address.toLowerCase() !== transaction.from.toLowerCase())
1152
+ throw new Error('Reviewed native signer does not match the approved account');
1153
+ assertCurrent();
1154
+ const { signed, expectedUnsigned } = (0, prepared_evm_1.signPreparedEvmEnvelope)(transaction, decryptedPrivateKey);
1155
+ return (0, evm_submission_1.submitSignedEvmTransaction)(signed.rawTransaction, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, expectedUnsigned);
1156
+ }
1093
1157
  // Detach supplied values before any RPC/device await. Autofill updates only
1094
1158
  // this invocation; caller mutations cannot replace already supplied fields.
1159
+ let params = input;
1095
1160
  if (!params || typeof params !== 'object' || Array.isArray(params))
1096
1161
  throw new TypeError('Invalid EVM transaction request');
1097
1162
  const supplied = { ...params };
@@ -1232,7 +1297,7 @@ class EthereumTransactions {
1232
1297
  rawTx: rawTx.replace('0x', ''),
1233
1298
  accountIndex: activeAccountId,
1234
1299
  expectedAddress: activeAccount.address,
1235
- });
1300
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent));
1236
1301
  assertCurrent();
1237
1302
  const formattedSignature = {
1238
1303
  r: `0x${signature.r}`,
@@ -1243,7 +1308,7 @@ class EthereumTransactions {
1243
1308
  try {
1244
1309
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
1245
1310
  assertHardwareTransactionSender(signedTx, activeAccount.address);
1246
- const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, rawTx);
1311
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, rawTx);
1247
1312
  return finalTx;
1248
1313
  }
1249
1314
  catch (error) {
@@ -1314,7 +1379,7 @@ class EthereumTransactions {
1314
1379
  tx: txFormattedForTrezor,
1315
1380
  coin: activeNetwork.currency,
1316
1381
  slip44: activeNetwork.slip44,
1317
- });
1382
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent));
1318
1383
  assertCurrent();
1319
1384
  if (signature.success) {
1320
1385
  try {
@@ -1333,7 +1398,7 @@ class EthereumTransactions {
1333
1398
  };
1334
1399
  const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
1335
1400
  assertHardwareTransactionSender(signedTx, activeAccount.address);
1336
- const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
1401
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
1337
1402
  return finalTx;
1338
1403
  }
1339
1404
  catch (error) {
@@ -1377,35 +1442,37 @@ class EthereumTransactions {
1377
1442
  return await sendEVMTransaction();
1378
1443
  }
1379
1444
  };
1380
- this.sendTransactionWithEditedFee = async (txHash, isLegacy = false, options = {}) => {
1381
- const { assertCurrentContext, validateOriginal, beforeBroadcast, approvedMaximumFee, } = options;
1382
- const previewOnly = options.previewOnly === true;
1383
- if (!previewOnly)
1384
- this.assertHardwareSignerAvailable(options.targetAccount);
1445
+ this.sendTransactionWithEditedFee = async (txHash, isLegacy, options) => {
1446
+ const previewOnly = options?.previewOnly === true;
1447
+ const approvedMaximumFee = options?.approvedMaximumFee;
1385
1448
  if (!previewOnly &&
1386
1449
  (typeof approvedMaximumFee !== 'string' ||
1387
- !/^\d{1,78}$/.test(approvedMaximumFee))) {
1450
+ !/^(0|[1-9][0-9]{0,77})$/.test(approvedMaximumFee)))
1388
1451
  throw new Error('Replacement fee approval required');
1389
- }
1390
- let signing;
1391
- const assertCurrent = () => {
1392
- const result = assertCurrentContext?.();
1393
- assertSynchronousOriginalValidation(result, 'Transaction context guard must be synchronous');
1394
- if (result !== undefined)
1395
- throw new Error('Transaction context guard must return undefined');
1396
- signing?.assertCurrent();
1452
+ if (typeof options?.assertCurrentContext !== 'function')
1453
+ throw new Error('Replacement context required');
1454
+ if (typeof options.validateOriginal !== 'function')
1455
+ throw new Error('Replacement original validator required');
1456
+ const { assertCurrentContext, validateOriginal, beforeBroadcast } = options;
1457
+ if (!previewOnly)
1458
+ this.assertHardwareSignerAvailable(options.targetAccount);
1459
+ isLegacy ?? (isLegacy = false);
1460
+ let assertCapturedSignerCurrent;
1461
+ const assertSigningCurrent = () => {
1462
+ assertSynchronousOriginalValidation(assertCurrentContext(), 'Transaction context guard must be synchronous');
1463
+ assertCapturedSignerCurrent?.();
1397
1464
  };
1398
1465
  const assertBeforeBroadcast = () => {
1399
- assertCurrent();
1400
- beforeBroadcast?.();
1401
- assertCurrent();
1466
+ assertSigningCurrent();
1467
+ assertSynchronousOriginalValidation(beforeBroadcast?.(), 'Transaction broadcast guard must be synchronous');
1468
+ assertSigningCurrent();
1402
1469
  };
1403
1470
  const assertSignedSender = (signedTx) => {
1404
1471
  if (transaction_1.Transaction.from(signedTx).from?.toLowerCase() !== expectedSender) {
1405
1472
  throw new Error('Replacement signature does not match account');
1406
1473
  }
1407
1474
  };
1408
- assertCurrent();
1475
+ assertSigningCurrent();
1409
1476
  const state = this.getState();
1410
1477
  const activeAccountType = options.targetAccount?.type ?? state.activeAccountType;
1411
1478
  const activeAccountId = options.targetAccount?.id ?? state.activeAccountId;
@@ -1421,14 +1488,16 @@ class EthereumTransactions {
1421
1488
  throw new Error('Replacement signing account is unavailable');
1422
1489
  }
1423
1490
  if (!previewOnly)
1424
- signing = this.captureEvmSigningContext(targetAccount);
1491
+ assertCapturedSignerCurrent =
1492
+ this.captureEvmSigningContext(targetAccount).assertCurrent;
1425
1493
  const expectedSender = activeAccount.address.toLowerCase();
1426
1494
  const requestedHash = txHash.toLowerCase();
1427
1495
  const provider = this.web3Provider;
1428
- assertCurrent();
1496
+ assertSigningCurrent();
1429
1497
  const submission = previewOnly
1430
1498
  ? undefined
1431
- : this.captureSubmission('speed-up', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertCurrent, txHash);
1499
+ : this.captureSubmission('speed-up', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, txHash);
1500
+ const assertCurrent = submission?.assertCurrent ?? assertSigningCurrent;
1432
1501
  const validateLookup = (transaction) => {
1433
1502
  assertCurrent();
1434
1503
  if (!transaction)
@@ -1442,7 +1511,7 @@ class EthereumTransactions {
1442
1511
  activeAccountType === types_1.KeyringAccountType.Trezor) {
1443
1512
  throw new Error('Trezor does not support type 1 replacement signing');
1444
1513
  }
1445
- assertSynchronousOriginalValidation(validateOriginal?.(authenticated));
1514
+ assertSynchronousOriginalValidation(validateOriginal(authenticated));
1446
1515
  assertCurrent();
1447
1516
  return authenticated;
1448
1517
  };
@@ -1541,7 +1610,7 @@ class EthereumTransactions {
1541
1610
  maxPriorityFeePerGas: newGasValues.maxPriorityFeePerGas,
1542
1611
  }),
1543
1612
  };
1544
- if (previewOnly)
1613
+ if (!submission)
1545
1614
  return { isSpeedUp: false, maximumFee: maximumFee.toString() };
1546
1615
  if (maximumFee.gt(approvedMaximumFee))
1547
1616
  throw new Error('Replacement fee exceeds approval');
@@ -1572,7 +1641,7 @@ class EthereumTransactions {
1572
1641
  rawTx: rawTx.replace('0x', ''),
1573
1642
  accountIndex: activeAccountId,
1574
1643
  expectedAddress: activeAccount.address,
1575
- });
1644
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent));
1576
1645
  const formattedSignature = {
1577
1646
  r: `0x${signature.r}`,
1578
1647
  s: `0x${signature.s}`,
@@ -1582,7 +1651,7 @@ class EthereumTransactions {
1582
1651
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
1583
1652
  assertSignedSender(signedTx);
1584
1653
  assertHardwareTransactionSender(signedTx, activeAccount.address);
1585
- const transactionResponse = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, rawTx);
1654
+ const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, rawTx);
1586
1655
  return {
1587
1656
  isSpeedUp: true,
1588
1657
  transaction: transactionResponse,
@@ -1658,12 +1727,12 @@ class EthereumTransactions {
1658
1727
  tx: txFormattedForTrezor,
1659
1728
  index: activeAccountId.toString(),
1660
1729
  slip44: activeNetwork.slip44,
1661
- });
1730
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent));
1662
1731
  if (signature.success) {
1663
1732
  const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
1664
1733
  assertSignedSender(signedTx);
1665
1734
  assertHardwareTransactionSender(signedTx, activeAccount.address);
1666
- const transactionResponse = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
1735
+ const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
1667
1736
  return {
1668
1737
  isSpeedUp: true,
1669
1738
  transaction: transactionResponse,
@@ -1748,44 +1817,51 @@ class EthereumTransactions {
1748
1817
  return await speedUpWithPrivateKey();
1749
1818
  }
1750
1819
  };
1751
- this.sendSignedErc20Transaction = async ({ receiver, tokenAddress, tokenAmount, isLegacy = false, maxPriorityFeePerGas, maxFeePerGas, gasPrice, decimals, gasLimit, saveTrezorTx, }) => {
1752
- const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent, } = this.captureEvmSigningContext();
1820
+ this.sendSignedErc20Transaction = async (request, approval) => {
1821
+ const { assertApproval, signal } = this.captureReviewedTokenApproval(approval, 'ERC20');
1822
+ const { receiver, tokenAddress, tokenAmountUnits, saveTrezorTx, networkUrl, } = request;
1823
+ const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertSigningCurrent, } = this.captureEvmSigningContext();
1753
1824
  const activeAccountAddress = activeAccount.address;
1754
- const submission = this.captureSubmission('erc20', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertCurrent);
1755
- ({ gasPrice, isLegacy, maxFeePerGas, maxPriorityFeePerGas } =
1756
- await this.resolveEvmFeeParams({
1757
- gasPrice,
1758
- isLegacy,
1759
- maxFeePerGas,
1760
- maxPriorityFeePerGas,
1761
- }, provider));
1762
- assertCurrent();
1825
+ if (networkUrl !== activeNetwork.url)
1826
+ throw new Error('ERC20 transaction approval network changed');
1827
+ if ('tokenAmount' in request ||
1828
+ 'decimals' in request ||
1829
+ 'tokenId' in request ||
1830
+ typeof tokenAmountUnits !== 'string' ||
1831
+ !/^(0|[1-9][0-9]{0,77})$/.test(tokenAmountUnits))
1832
+ throw new Error('Invalid ERC20 amount');
1833
+ const exactTokenAmount = (0, evm_fees_1.parseEvmFeeQuantity)(tokenAmountUnits, 'ERC20 amount');
1834
+ const { gasLimit, isLegacy, gasPrice, maxFeePerGas, maxPriorityFeePerGas } = this.reviewedTokenFees(request, activeNetwork.chainId, 'ERC20');
1835
+ const submission = this.captureSubmission('erc20', activeAccount, activeAccountType, activeAccountId, activeNetwork, () => {
1836
+ assertApproval();
1837
+ assertSigningCurrent();
1838
+ });
1839
+ const assertCurrent = submission.assertCurrent;
1763
1840
  const sendERC20Token = async () => {
1764
1841
  assertCurrent();
1765
1842
  const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
1766
1843
  try {
1767
1844
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
1768
- // Preserve zero-decimal tokens: use provided decimals when defined (including 0).
1769
- const resolvedDecimals = decimals === undefined || decimals === null ? 18 : Number(decimals);
1770
- const calculatedTokenAmount = (0, ethers_v6_1.parseUnits)(tokenAmount, resolvedDecimals).toBigInt();
1771
1845
  let transferMethod;
1772
1846
  if (isLegacy) {
1773
1847
  const overrides = {
1774
1848
  nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
1775
1849
  gasPrice,
1776
- ...(gasLimit && { gasLimit }),
1850
+ gasLimit,
1777
1851
  type: 0, // Explicitly set Type 0 for legacy token transfers
1778
1852
  };
1779
- transferMethod = await _contract.transfer.populateTransaction(receiver, calculatedTokenAmount, normalizeEthersOverrides(overrides));
1853
+ assertCurrent();
1854
+ transferMethod = await _contract.transfer.populateTransaction(receiver, exactTokenAmount, normalizeEthersOverrides(overrides));
1780
1855
  }
1781
1856
  else {
1782
1857
  const overrides = {
1783
1858
  nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
1784
1859
  maxPriorityFeePerGas,
1785
1860
  maxFeePerGas,
1786
- ...(gasLimit && { gasLimit }),
1861
+ gasLimit,
1787
1862
  };
1788
- transferMethod = await _contract.transfer.populateTransaction(receiver, calculatedTokenAmount, normalizeEthersOverrides(overrides));
1863
+ assertCurrent();
1864
+ transferMethod = await _contract.transfer.populateTransaction(receiver, exactTokenAmount, normalizeEthersOverrides(overrides));
1789
1865
  }
1790
1866
  return await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, {
1791
1867
  ...transferMethod,
@@ -1804,20 +1880,16 @@ class EthereumTransactions {
1804
1880
  assertCurrent();
1805
1881
  try {
1806
1882
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
1807
- const resolvedDecimals = decimals === undefined || decimals === null ? 18 : Number(decimals);
1808
- const calculatedTokenAmount = (0, ethers_v6_1.parseUnits)(tokenAmount, resolvedDecimals).toBigInt();
1809
1883
  const txData = _contract.interface.encodeFunctionData('transfer', [
1810
1884
  receiver,
1811
- calculatedTokenAmount,
1885
+ exactTokenAmount,
1812
1886
  ]);
1813
- // Use fallback gas limit if not provided (for auto-estimation)
1814
- const effectiveGasLimit = gasLimit || this.toBigNumber('100000'); // ERC20 fallback
1815
1887
  let txFormattedForEthers;
1816
1888
  if (isLegacy) {
1817
1889
  txFormattedForEthers = {
1818
1890
  to: tokenAddress,
1819
1891
  value: '0x0',
1820
- gasLimit: effectiveGasLimit,
1892
+ gasLimit: gasLimit,
1821
1893
  gasPrice,
1822
1894
  data: txData,
1823
1895
  nonce: transactionNonce,
@@ -1829,7 +1901,7 @@ class EthereumTransactions {
1829
1901
  txFormattedForEthers = {
1830
1902
  to: tokenAddress,
1831
1903
  value: '0x0',
1832
- gasLimit: effectiveGasLimit,
1904
+ gasLimit: gasLimit,
1833
1905
  maxFeePerGas,
1834
1906
  maxPriorityFeePerGas,
1835
1907
  data: txData,
@@ -1843,7 +1915,7 @@ class EthereumTransactions {
1843
1915
  rawTx: rawTx.replace('0x', ''),
1844
1916
  accountIndex: activeAccountId,
1845
1917
  expectedAddress: activeAccount.address,
1846
- });
1918
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
1847
1919
  assertCurrent();
1848
1920
  const formattedSignature = {
1849
1921
  r: `0x${signature.r}`,
@@ -1855,7 +1927,7 @@ class EthereumTransactions {
1855
1927
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
1856
1928
  assertCurrent();
1857
1929
  assertHardwareTransactionSender(signedTx, activeAccount.address);
1858
- const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, async (raw) => {
1930
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async (raw) => {
1859
1931
  const response = await provider.sendTransaction(raw);
1860
1932
  saveTrezorTx && saveTrezorTx(response);
1861
1933
  return response;
@@ -1880,20 +1952,16 @@ class EthereumTransactions {
1880
1952
  assertCurrent();
1881
1953
  try {
1882
1954
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
1883
- const resolvedDecimals = decimals === undefined || decimals === null ? 18 : Number(decimals);
1884
- const calculatedTokenAmount = (0, ethers_v6_1.parseUnits)(tokenAmount, resolvedDecimals).toBigInt();
1885
1955
  const txData = _contract.interface.encodeFunctionData('transfer', [
1886
1956
  receiver,
1887
- calculatedTokenAmount,
1957
+ exactTokenAmount,
1888
1958
  ]);
1889
- // Use fallback gas limit if not provided (for auto-estimation)
1890
- const effectiveGasLimit = gasLimit || this.toBigNumber('100000'); // ERC20 fallback
1891
1959
  let txToBeSignedByTrezor;
1892
1960
  if (isLegacy) {
1893
1961
  txToBeSignedByTrezor = {
1894
1962
  to: tokenAddress,
1895
1963
  value: '0x0',
1896
- gasLimit: this.toHex0x(effectiveGasLimit, 'gasLimit'),
1964
+ gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
1897
1965
  gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
1898
1966
  nonce: this.toBigNumber(transactionNonce)._hex,
1899
1967
  chainId: activeNetwork.chainId,
@@ -1904,7 +1972,7 @@ class EthereumTransactions {
1904
1972
  txToBeSignedByTrezor = {
1905
1973
  to: tokenAddress,
1906
1974
  value: '0x0',
1907
- gasLimit: this.toHex0x(effectiveGasLimit, 'gasLimit'),
1975
+ gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
1908
1976
  maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
1909
1977
  maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
1910
1978
  nonce: this.toBigNumber(transactionNonce)._hex,
@@ -1922,7 +1990,7 @@ class EthereumTransactions {
1922
1990
  tx: txToBeSignedByTrezor,
1923
1991
  coin: activeNetwork.currency,
1924
1992
  slip44: activeNetwork.slip44,
1925
- });
1993
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
1926
1994
  assertCurrent();
1927
1995
  if (signature.success) {
1928
1996
  try {
@@ -1931,7 +1999,7 @@ class EthereumTransactions {
1931
1999
  txFormattedForEthers = {
1932
2000
  to: tokenAddress,
1933
2001
  value: '0x0',
1934
- gasLimit: effectiveGasLimit,
2002
+ gasLimit: gasLimit,
1935
2003
  gasPrice,
1936
2004
  data: txData,
1937
2005
  nonce: transactionNonce,
@@ -1943,7 +2011,7 @@ class EthereumTransactions {
1943
2011
  txFormattedForEthers = {
1944
2012
  to: tokenAddress,
1945
2013
  value: '0x0',
1946
- gasLimit: effectiveGasLimit,
2014
+ gasLimit: gasLimit,
1947
2015
  maxFeePerGas,
1948
2016
  maxPriorityFeePerGas,
1949
2017
  data: txData,
@@ -1955,7 +2023,7 @@ class EthereumTransactions {
1955
2023
  const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
1956
2024
  assertCurrent();
1957
2025
  assertHardwareTransactionSender(signedTx, activeAccount.address);
1958
- const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, async (raw) => {
2026
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async (raw) => {
1959
2027
  const response = await provider.sendTransaction(raw);
1960
2028
  saveTrezorTx && saveTrezorTx(response);
1961
2029
  return response;
@@ -1983,22 +2051,26 @@ class EthereumTransactions {
1983
2051
  return (await sendERC20Token());
1984
2052
  }
1985
2053
  };
1986
- this.sendSignedErc721Transaction = async ({ receiver, tokenAddress, tokenId, isLegacy, maxPriorityFeePerGas, maxFeePerGas, gasPrice, gasLimit, }) => {
1987
- const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent, } = this.captureEvmSigningContext();
2054
+ this.sendSignedErc721Transaction = async (request, approval) => {
2055
+ const { assertApproval, signal } = this.captureReviewedTokenApproval(approval, 'ERC721');
2056
+ const { receiver, tokenAddress, tokenId, networkUrl } = request;
2057
+ const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertSigningCurrent, } = this.captureEvmSigningContext();
2058
+ if (networkUrl !== activeNetwork.url)
2059
+ throw new Error('ERC721 transaction approval network changed');
2060
+ if ('tokenAmountUnits' in request || 'decimals' in request)
2061
+ throw new Error('ERC20 amount is not allowed for NFT transfers');
2062
+ if (typeof tokenId !== 'string' || !/^(0|[1-9][0-9]{0,77})$/.test(tokenId))
2063
+ throw new Error('Invalid ERC721 token ID');
2064
+ const normalizedTokenId = (0, evm_fees_1.parseEvmFeeQuantity)(tokenId, 'ERC721 token ID');
2065
+ if ('tokenAmount' in request)
2066
+ throw new Error('ERC721 transfer must contain one token');
2067
+ const { gasLimit, isLegacy, gasPrice, maxFeePerGas, maxPriorityFeePerGas } = this.reviewedTokenFees(request, activeNetwork.chainId, 'ERC721');
1988
2068
  const activeAccountAddress = activeAccount.address;
1989
- const submission = this.captureSubmission('erc721', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertCurrent);
1990
- if (tokenId === undefined || tokenId === null) {
1991
- throw new Error('ERC721 tokenId is required');
1992
- }
1993
- ({ gasPrice, isLegacy, maxFeePerGas, maxPriorityFeePerGas } =
1994
- await this.resolveEvmFeeParams({
1995
- gasPrice,
1996
- isLegacy,
1997
- maxFeePerGas,
1998
- maxPriorityFeePerGas,
1999
- }, provider));
2000
- assertCurrent();
2001
- const normalizedTokenId = ethers_v6_1.BigNumber.from(tokenId).toBigInt();
2069
+ const submission = this.captureSubmission('erc721', activeAccount, activeAccountType, activeAccountId, activeNetwork, () => {
2070
+ assertApproval();
2071
+ assertSigningCurrent();
2072
+ });
2073
+ const assertCurrent = submission.assertCurrent;
2002
2074
  const sendERC721Token = async () => {
2003
2075
  assertCurrent();
2004
2076
  const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
@@ -2009,7 +2081,7 @@ class EthereumTransactions {
2009
2081
  const overrides = {
2010
2082
  nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
2011
2083
  gasPrice,
2012
- ...(gasLimit && { gasLimit }),
2084
+ gasLimit,
2013
2085
  type: 0, // Explicitly set Type 0 for legacy NFT transfers
2014
2086
  };
2015
2087
  transferMethod = await _contract.transferFrom.populateTransaction(activeAccountAddress, receiver, normalizedTokenId, normalizeEthersOverrides(overrides));
@@ -2019,7 +2091,7 @@ class EthereumTransactions {
2019
2091
  nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
2020
2092
  maxPriorityFeePerGas,
2021
2093
  maxFeePerGas,
2022
- ...(gasLimit && { gasLimit }),
2094
+ gasLimit,
2023
2095
  };
2024
2096
  transferMethod = await _contract.transferFrom.populateTransaction(activeAccountAddress, receiver, normalizedTokenId, normalizeEthersOverrides(overrides));
2025
2097
  }
@@ -2045,14 +2117,12 @@ class EthereumTransactions {
2045
2117
  receiver,
2046
2118
  normalizedTokenId,
2047
2119
  ]);
2048
- // Use fallback gas limit if not provided (for auto-estimation)
2049
- const effectiveGasLimit = gasLimit || this.toBigNumber('150000'); // ERC721 fallback
2050
2120
  let txFormattedForEthers;
2051
2121
  if (isLegacy) {
2052
2122
  txFormattedForEthers = {
2053
2123
  to: tokenAddress,
2054
2124
  value: '0x0',
2055
- gasLimit: effectiveGasLimit,
2125
+ gasLimit,
2056
2126
  gasPrice,
2057
2127
  data: txData,
2058
2128
  nonce: transactionNonce,
@@ -2064,7 +2134,7 @@ class EthereumTransactions {
2064
2134
  txFormattedForEthers = {
2065
2135
  to: tokenAddress,
2066
2136
  value: '0x0',
2067
- gasLimit: effectiveGasLimit,
2137
+ gasLimit,
2068
2138
  maxFeePerGas,
2069
2139
  maxPriorityFeePerGas,
2070
2140
  data: txData,
@@ -2078,7 +2148,7 @@ class EthereumTransactions {
2078
2148
  rawTx: rawTx.replace('0x', ''),
2079
2149
  accountIndex: activeAccountId,
2080
2150
  expectedAddress: activeAccount.address,
2081
- });
2151
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2082
2152
  assertCurrent();
2083
2153
  const formattedSignature = {
2084
2154
  r: `0x${signature.r}`,
@@ -2090,7 +2160,7 @@ class EthereumTransactions {
2090
2160
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
2091
2161
  assertCurrent();
2092
2162
  assertHardwareTransactionSender(signedTx, activeAccount.address);
2093
- const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
2163
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
2094
2164
  return finalTx;
2095
2165
  }
2096
2166
  catch (error) {
@@ -2116,14 +2186,12 @@ class EthereumTransactions {
2116
2186
  receiver,
2117
2187
  normalizedTokenId,
2118
2188
  ]);
2119
- // Use fallback gas limit if not provided (for auto-estimation)
2120
- const effectiveGasLimit = gasLimit || this.toBigNumber('150000'); // ERC721 fallback
2121
2189
  let txToBeSignedByTrezor;
2122
2190
  if (isLegacy) {
2123
2191
  txToBeSignedByTrezor = {
2124
2192
  to: tokenAddress,
2125
2193
  value: '0x0',
2126
- gasLimit: this.toHex0x(effectiveGasLimit, 'gasLimit'),
2194
+ gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
2127
2195
  gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
2128
2196
  nonce: this.toBigNumber(transactionNonce)._hex,
2129
2197
  chainId: activeNetwork.chainId,
@@ -2135,7 +2203,7 @@ class EthereumTransactions {
2135
2203
  txToBeSignedByTrezor = {
2136
2204
  to: tokenAddress,
2137
2205
  value: '0x0',
2138
- gasLimit: this.toHex0x(effectiveGasLimit, 'gasLimit'),
2206
+ gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
2139
2207
  maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
2140
2208
  maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
2141
2209
  nonce: this.toBigNumber(transactionNonce)._hex,
@@ -2155,7 +2223,7 @@ class EthereumTransactions {
2155
2223
  tx: txToBeSignedByTrezor,
2156
2224
  coin: trezorCoin,
2157
2225
  slip44: activeNetwork.slip44,
2158
- });
2226
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2159
2227
  assertCurrent();
2160
2228
  if (signature.success) {
2161
2229
  try {
@@ -2164,7 +2232,7 @@ class EthereumTransactions {
2164
2232
  txFormattedForEthers = {
2165
2233
  to: tokenAddress,
2166
2234
  value: '0x0',
2167
- gasLimit: effectiveGasLimit,
2235
+ gasLimit,
2168
2236
  gasPrice,
2169
2237
  data: txData,
2170
2238
  nonce: transactionNonce,
@@ -2176,7 +2244,7 @@ class EthereumTransactions {
2176
2244
  txFormattedForEthers = {
2177
2245
  to: tokenAddress,
2178
2246
  value: '0x0',
2179
- gasLimit: effectiveGasLimit,
2247
+ gasLimit,
2180
2248
  maxFeePerGas,
2181
2249
  maxPriorityFeePerGas,
2182
2250
  data: txData,
@@ -2188,7 +2256,7 @@ class EthereumTransactions {
2188
2256
  const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
2189
2257
  assertCurrent();
2190
2258
  assertHardwareTransactionSender(signedTx, activeAccount.address);
2191
- const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
2259
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
2192
2260
  return finalTx;
2193
2261
  }
2194
2262
  catch (error) {
@@ -2214,18 +2282,29 @@ class EthereumTransactions {
2214
2282
  return (await sendERC721Token());
2215
2283
  }
2216
2284
  };
2217
- this.sendSignedErc1155Transaction = async ({ receiver, tokenAddress, tokenId, tokenAmount, isLegacy, maxPriorityFeePerGas, maxFeePerGas, gasPrice, gasLimit, }) => {
2218
- const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent, } = this.captureEvmSigningContext();
2285
+ this.sendSignedErc1155Transaction = async (request, approval) => {
2286
+ const { assertApproval, signal } = this.captureReviewedTokenApproval(approval, 'ERC1155');
2287
+ const { receiver, tokenAddress, tokenId, networkUrl } = request;
2288
+ const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertSigningCurrent, } = this.captureEvmSigningContext();
2289
+ if (networkUrl !== activeNetwork.url)
2290
+ throw new Error('ERC1155 transaction approval network changed');
2291
+ if ('tokenAmountUnits' in request || 'decimals' in request)
2292
+ throw new Error('ERC20 amount is not allowed for NFT transfers');
2293
+ if (typeof tokenId !== 'string' || !/^(0|[1-9][0-9]{0,77})$/.test(tokenId))
2294
+ throw new Error('Invalid ERC1155 token ID');
2295
+ const normalizedTokenId = (0, evm_fees_1.parseEvmFeeQuantity)(tokenId, 'ERC1155 token ID');
2296
+ const { tokenAmount } = request;
2297
+ if (typeof tokenAmount !== 'string' ||
2298
+ !/^[1-9][0-9]{0,77}$/.test(tokenAmount))
2299
+ throw new Error('Invalid ERC1155 amount');
2300
+ const amount = (0, evm_fees_1.parseEvmFeeQuantity)(tokenAmount, 'ERC1155 amount');
2301
+ const { gasLimit, isLegacy, gasPrice, maxFeePerGas, maxPriorityFeePerGas } = this.reviewedTokenFees(request, activeNetwork.chainId, 'ERC1155');
2219
2302
  const activeAccountAddress = activeAccount.address;
2220
- const submission = this.captureSubmission('erc1155', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertCurrent);
2221
- ({ gasPrice, isLegacy, maxFeePerGas, maxPriorityFeePerGas } =
2222
- await this.resolveEvmFeeParams({
2223
- gasPrice,
2224
- isLegacy,
2225
- maxFeePerGas,
2226
- maxPriorityFeePerGas,
2227
- }, provider));
2228
- assertCurrent();
2303
+ const submission = this.captureSubmission('erc1155', activeAccount, activeAccountType, activeAccountId, activeNetwork, () => {
2304
+ assertApproval();
2305
+ assertSigningCurrent();
2306
+ });
2307
+ const assertCurrent = submission.assertCurrent;
2229
2308
  const sendERC1155Token = async () => {
2230
2309
  assertCurrent();
2231
2310
  const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
@@ -2233,13 +2312,12 @@ class EthereumTransactions {
2233
2312
  try {
2234
2313
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
2235
2314
  // Use BigNumber to avoid JS number overflow/precision loss
2236
- const amount = ethers_v6_1.BigNumber.from(tokenAmount ?? '1');
2237
2315
  let overrides;
2238
2316
  if (isLegacy) {
2239
2317
  overrides = {
2240
2318
  nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
2241
2319
  gasPrice,
2242
- ...(gasLimit && { gasLimit }),
2320
+ gasLimit,
2243
2321
  type: 0, // Explicitly set Type 0 for legacy ERC1155 transfers
2244
2322
  };
2245
2323
  }
@@ -2248,10 +2326,10 @@ class EthereumTransactions {
2248
2326
  nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
2249
2327
  maxPriorityFeePerGas,
2250
2328
  maxFeePerGas,
2251
- ...(gasLimit && { gasLimit }),
2329
+ gasLimit,
2252
2330
  };
2253
2331
  }
2254
- transferMethod = await _contract.safeTransferFrom.populateTransaction(activeAccountAddress, receiver, tokenId, amount.toBigInt(), '0x', normalizeEthersOverrides(overrides));
2332
+ transferMethod = await _contract.safeTransferFrom.populateTransaction(activeAccountAddress, receiver, normalizedTokenId, amount, '0x', normalizeEthersOverrides(overrides));
2255
2333
  return await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, {
2256
2334
  ...transferMethod,
2257
2335
  chainId: activeNetwork.chainId,
@@ -2269,16 +2347,13 @@ class EthereumTransactions {
2269
2347
  assertCurrent();
2270
2348
  try {
2271
2349
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
2272
- const amount = ethers_v6_1.BigNumber.from(tokenAmount ?? '1');
2273
- const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, tokenId, amount.toBigInt(), '0x']);
2274
- // Use fallback gas limit if not provided (for auto-estimation)
2275
- const effectiveGasLimit = gasLimit || this.toBigNumber('200000'); // ERC1155 fallback
2350
+ const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, normalizedTokenId, amount, '0x']);
2276
2351
  let txFormattedForEthers;
2277
2352
  if (isLegacy) {
2278
2353
  txFormattedForEthers = {
2279
2354
  to: tokenAddress,
2280
2355
  value: '0x0',
2281
- gasLimit: effectiveGasLimit,
2356
+ gasLimit,
2282
2357
  gasPrice,
2283
2358
  data: txData,
2284
2359
  nonce: transactionNonce,
@@ -2290,7 +2365,7 @@ class EthereumTransactions {
2290
2365
  txFormattedForEthers = {
2291
2366
  to: tokenAddress,
2292
2367
  value: '0x0',
2293
- gasLimit: effectiveGasLimit,
2368
+ gasLimit,
2294
2369
  maxFeePerGas,
2295
2370
  maxPriorityFeePerGas,
2296
2371
  data: txData,
@@ -2304,7 +2379,7 @@ class EthereumTransactions {
2304
2379
  rawTx: rawTx.replace('0x', ''),
2305
2380
  accountIndex: activeAccountId,
2306
2381
  expectedAddress: activeAccount.address,
2307
- });
2382
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2308
2383
  assertCurrent();
2309
2384
  const formattedSignature = {
2310
2385
  r: `0x${signature.r}`,
@@ -2316,7 +2391,7 @@ class EthereumTransactions {
2316
2391
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
2317
2392
  assertCurrent();
2318
2393
  assertHardwareTransactionSender(signedTx, activeAccount.address);
2319
- const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
2394
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
2320
2395
  return finalTx;
2321
2396
  }
2322
2397
  catch (error) {
@@ -2337,16 +2412,13 @@ class EthereumTransactions {
2337
2412
  assertCurrent();
2338
2413
  try {
2339
2414
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
2340
- const amount = ethers_v6_1.BigNumber.from(tokenAmount ?? '1');
2341
- const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, tokenId, amount.toBigInt(), '0x']);
2342
- // Use fallback gas limit if not provided (for auto-estimation)
2343
- const effectiveGasLimit = gasLimit || this.toBigNumber('200000'); // ERC1155 fallback
2415
+ const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, normalizedTokenId, amount, '0x']);
2344
2416
  let txToBeSignedByTrezor;
2345
2417
  if (isLegacy) {
2346
2418
  txToBeSignedByTrezor = {
2347
2419
  to: tokenAddress,
2348
2420
  value: '0x0',
2349
- gasLimit: this.toHex0x(effectiveGasLimit, 'gasLimit'),
2421
+ gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
2350
2422
  gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
2351
2423
  nonce: this.toBigNumber(transactionNonce)._hex,
2352
2424
  chainId: activeNetwork.chainId,
@@ -2357,7 +2429,7 @@ class EthereumTransactions {
2357
2429
  txToBeSignedByTrezor = {
2358
2430
  to: tokenAddress,
2359
2431
  value: '0x0',
2360
- gasLimit: this.toHex0x(effectiveGasLimit, 'gasLimit'),
2432
+ gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
2361
2433
  maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
2362
2434
  maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
2363
2435
  nonce: this.toBigNumber(transactionNonce)._hex,
@@ -2375,7 +2447,7 @@ class EthereumTransactions {
2375
2447
  tx: txToBeSignedByTrezor,
2376
2448
  coin: activeNetwork.currency,
2377
2449
  slip44: activeNetwork.slip44,
2378
- });
2450
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2379
2451
  assertCurrent();
2380
2452
  if (signature.success) {
2381
2453
  try {
@@ -2384,7 +2456,7 @@ class EthereumTransactions {
2384
2456
  txFormattedForEthers = {
2385
2457
  to: tokenAddress,
2386
2458
  value: '0x0',
2387
- gasLimit: effectiveGasLimit,
2459
+ gasLimit,
2388
2460
  gasPrice,
2389
2461
  data: txData,
2390
2462
  nonce: transactionNonce,
@@ -2396,7 +2468,7 @@ class EthereumTransactions {
2396
2468
  txFormattedForEthers = {
2397
2469
  to: tokenAddress,
2398
2470
  value: '0x0',
2399
- gasLimit: effectiveGasLimit,
2471
+ gasLimit,
2400
2472
  maxFeePerGas,
2401
2473
  maxPriorityFeePerGas,
2402
2474
  data: txData,
@@ -2408,7 +2480,7 @@ class EthereumTransactions {
2408
2480
  const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
2409
2481
  assertCurrent();
2410
2482
  assertHardwareTransactionSender(signedTx, activeAccount.address);
2411
- const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
2483
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
2412
2484
  return finalTx;
2413
2485
  }
2414
2486
  catch (error) {
@@ -2608,6 +2680,11 @@ class EthereumTransactions {
2608
2680
  maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
2609
2681
  };
2610
2682
  };
2683
+ if (typeof captureSigningContext !== 'function')
2684
+ throw new TypeError('Ethereum transactions require a signing context');
2685
+ if (captureSubmissionLifecycle !== null &&
2686
+ typeof captureSubmissionLifecycle !== 'function')
2687
+ throw new TypeError('Ethereum transactions require an explicit submission capability');
2611
2688
  this.getNetwork = getNetwork;
2612
2689
  this.getDecryptedPrivateKey = (targetAccount) => {
2613
2690
  this.assertHardwareSignerAvailable(targetAccount);
@@ -2634,11 +2711,13 @@ class EthereumTransactions {
2634
2711
  // RPC/network than `activeNetwork`, nodes will reject broadcasts with chain-id mismatch errors.
2635
2712
  try {
2636
2713
  const currentNetwork = this.getNetwork();
2637
- const currentUrl = currentNetwork?.url;
2638
- const currentKey = `${currentNetwork?.chainId ?? 'unknown'}|${currentUrl ?? ''}`;
2639
- const needsInit = !this._web3Provider ||
2640
- !this._web3ProviderKey ||
2641
- (currentUrl && this._web3ProviderKey !== currentKey);
2714
+ if (currentNetwork?.kind !== sysweb3_network_1.INetworkType.Ethereum) {
2715
+ if (this._web3Provider)
2716
+ this.setWeb3Provider(currentNetwork);
2717
+ return;
2718
+ }
2719
+ const currentKey = `${currentNetwork.chainId}|${currentNetwork.url ?? ''}`;
2720
+ const needsInit = !this._web3Provider || this._web3ProviderKey !== currentKey;
2642
2721
  if (needsInit) {
2643
2722
  this.setWeb3Provider(currentNetwork);
2644
2723
  }
@@ -2649,31 +2728,16 @@ class EthereumTransactions {
2649
2728
  console.log('[EthereumTransactions] Deferring provider initialization:', error?.message || error);
2650
2729
  }
2651
2730
  }
2652
- // Helper method to detect UTXO networks
2653
- isUtxoNetwork(network) {
2654
- // Generic UTXO network detection patterns:
2655
- // 1. URL contains blockbook or trezor (most reliable)
2656
- // 2. Network kind is explicitly set to 'syscoin'
2657
- const hasBlockbookUrl = !!(network.url?.includes('blockbook') || network.url?.includes('trezor'));
2658
- const hasUtxoKind = network.kind === sysweb3_network_1.INetworkType.Syscoin;
2659
- return hasBlockbookUrl || hasUtxoKind;
2660
- }
2661
2731
  setWeb3Provider(network) {
2662
2732
  this.abortController.abort();
2663
2733
  this.abortController = new AbortController();
2664
- // Check if network is a UTXO network to avoid creating web3 providers for blockbook URLs
2665
- const isUtxoNetwork = this.isUtxoNetwork(network);
2666
- if (isUtxoNetwork) {
2667
- // For UTXO networks, don't create web3 providers at all since they won't be used
2668
- console.log('[EthereumTransactions] setWeb3Provider: Skipping web3Provider creation for UTXO network:', network.url);
2669
- // Clear any existing providers for UTXO networks
2734
+ if (network?.kind !== sysweb3_network_1.INetworkType.Ethereum) {
2670
2735
  this._web3Provider = undefined;
2671
2736
  this._web3ProviderKey = undefined;
2737
+ return;
2672
2738
  }
2673
- else {
2674
- this._web3Provider = new providers_1.CustomJsonRpcProvider(this.abortController.signal, network.url, network.chainId);
2675
- this._web3ProviderKey = `${network.chainId}|${network.url ?? ''}`;
2676
- }
2739
+ this._web3Provider = new providers_1.CustomJsonRpcProvider(this.abortController.signal, network.url, network.chainId);
2740
+ this._web3ProviderKey = `${network.chainId}|${network.url ?? ''}`;
2677
2741
  }
2678
2742
  // Method to configure gas overrides for networks with validation issues
2679
2743
  setGasOverrides(overrides) {