@sidhujag/sysweb3-keyring 2.1.4 → 2.1.6

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 (42) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/cjs/evm-fees.js +73 -0
  3. package/cjs/evm-fees.js.map +1 -1
  4. package/cjs/software.js.map +1 -1
  5. package/cjs/transactions/ethereum.js +220 -160
  6. package/cjs/transactions/ethereum.js.map +1 -1
  7. package/cjs/transactions/evm-cancellation.js +51 -0
  8. package/cjs/transactions/evm-cancellation.js.map +1 -1
  9. package/cjs/transactions/evm-local-signer.js +1 -1
  10. package/cjs/transactions/evm-local-signer.js.map +1 -1
  11. package/cjs/transactions/evm-submission.js +78 -8
  12. package/cjs/transactions/evm-submission.js.map +1 -1
  13. package/cjs/transactions/syscoin-core.js +155 -10
  14. package/cjs/transactions/syscoin-core.js.map +1 -1
  15. package/cjs/types.js.map +1 -1
  16. package/cjs/utils/excluded-utxos.js +6 -2
  17. package/cjs/utils/excluded-utxos.js.map +1 -1
  18. package/esm/evm-fees.js +72 -0
  19. package/esm/evm-fees.js.map +1 -1
  20. package/esm/transactions/ethereum.js +222 -162
  21. package/esm/transactions/ethereum.js.map +1 -1
  22. package/esm/transactions/evm-cancellation.js +50 -0
  23. package/esm/transactions/evm-cancellation.js.map +1 -1
  24. package/esm/transactions/evm-local-signer.js +1 -1
  25. package/esm/transactions/evm-local-signer.js.map +1 -1
  26. package/esm/transactions/evm-submission.js +78 -8
  27. package/esm/transactions/evm-submission.js.map +1 -1
  28. package/esm/transactions/syscoin-core.js +156 -11
  29. package/esm/transactions/syscoin-core.js.map +1 -1
  30. package/esm/types.js.map +1 -1
  31. package/esm/utils/excluded-utxos.js +6 -2
  32. package/esm/utils/excluded-utxos.js.map +1 -1
  33. package/package.json +3 -3
  34. package/types/evm-fees.d.ts +31 -0
  35. package/types/software.d.ts +1 -1
  36. package/types/transactions/ethereum.d.ts +9 -3
  37. package/types/transactions/evm-cancellation.d.ts +3 -0
  38. package/types/transactions/evm-local-signer.d.ts +1 -1
  39. package/types/transactions/evm-submission.d.ts +14 -2
  40. package/types/transactions/syscoin-core.d.ts +3 -1
  41. package/types/types.d.ts +49 -1
  42. package/types/utils/excluded-utxos.d.ts +1 -1
@@ -202,7 +202,7 @@ class EthereumTransactions {
202
202
  assertCurrent,
203
203
  };
204
204
  };
205
- this.captureSubmission = (operation, account, type, id, network, assertCurrent, originalHash) => {
205
+ this.captureSubmission = (operation, account, type, id, network, assertCurrent, details = {}) => {
206
206
  if (!this.captureSubmissionLifecycle)
207
207
  throw new TypeError('A durable EVM submission lifecycle is required');
208
208
  return (0, evm_submission_1.captureEvmSubmission)({
@@ -214,7 +214,7 @@ class EthereumTransactions {
214
214
  slip44: network.slip44,
215
215
  url: network.url,
216
216
  },
217
- ...(originalHash !== undefined ? { originalHash } : {}),
217
+ ...details,
218
218
  }, assertCurrent, this.captureSubmissionLifecycle);
219
219
  };
220
220
  this.authorizeHardwareTransaction = async (provider, assertCurrent, signal = this.abortController.signal) => {
@@ -584,7 +584,9 @@ class EthereumTransactions {
584
584
  typeof approval.assertCurrent !== 'function' ||
585
585
  !(approval.signal instanceof AbortSignal))
586
586
  throw new Error(`${standard} transaction approval required`);
587
- const { assertCurrent, signal } = approval;
587
+ const { assertCurrent, signal, nonce } = approval;
588
+ if (!Number.isSafeInteger(nonce) || nonce < 0)
589
+ throw new Error(`Invalid ${standard} transaction approval nonce`);
588
590
  const assertApproval = () => {
589
591
  if (signal.aborted)
590
592
  throw Object.assign(new Error(`${standard} transaction approval expired`), {
@@ -593,7 +595,7 @@ class EthereumTransactions {
593
595
  assertSynchronousOriginalValidation(assertCurrent(), `${standard} transaction approval must be synchronous`);
594
596
  };
595
597
  assertApproval();
596
- return { assertApproval, signal };
598
+ return { assertApproval, signal, nonce };
597
599
  };
598
600
  this.reviewedTokenFees = (request, chainId, standard) => {
599
601
  const { isLegacy } = request;
@@ -650,7 +652,7 @@ class EthereumTransactions {
650
652
  maxPriorityFeePerGas: ethers_v6_1.BigNumber.from(fee.maxPriorityFeePerGas),
651
653
  };
652
654
  };
653
- this.calculateNewGasValues = (oldTxsParams, isForCancel, isLegacy) => {
655
+ this.calculateNewGasValues = (oldTxsParams, isForCancel, isLegacy, bounds) => {
654
656
  const newGasValues = {
655
657
  maxFeePerGas: undefined,
656
658
  maxPriorityFeePerGas: undefined,
@@ -658,8 +660,34 @@ class EthereumTransactions {
658
660
  gasLimit: undefined,
659
661
  };
660
662
  const { maxFeePerGas, maxPriorityFeePerGas, gasLimit, gasPrice } = oldTxsParams;
663
+ if (!isForCancel) {
664
+ const originalGasLimit = (0, evm_fees_1.parseEvmFeeQuantity)(gasLimit, 'replacement gas limit');
665
+ const plan = isLegacy
666
+ ? (0, evm_fees_1.planEvmSpeedUpFees)({
667
+ ...bounds,
668
+ type: 0,
669
+ gasLimit: originalGasLimit,
670
+ gasPrice: (0, evm_fees_1.parseEvmFeeQuantity)(gasPrice, 'replacement gas price'),
671
+ })
672
+ : (0, evm_fees_1.planEvmSpeedUpFees)({
673
+ ...bounds,
674
+ type: 2,
675
+ gasLimit: originalGasLimit,
676
+ maxFeePerGas: (0, evm_fees_1.parseEvmFeeQuantity)(maxFeePerGas, 'replacement fee cap'),
677
+ maxPriorityFeePerGas: (0, evm_fees_1.parseEvmFeeQuantity)(maxPriorityFeePerGas, 'replacement priority fee'),
678
+ });
679
+ newGasValues.gasLimit = ethers_v6_1.BigNumber.from(plan.gasLimit);
680
+ if (plan.type === 0)
681
+ newGasValues.gasPrice = ethers_v6_1.BigNumber.from(plan.gasPrice);
682
+ else {
683
+ newGasValues.maxFeePerGas = ethers_v6_1.BigNumber.from(plan.maxFeePerGas);
684
+ newGasValues.maxPriorityFeePerGas = ethers_v6_1.BigNumber.from(plan.maxPriorityFeePerGas);
685
+ }
686
+ return newGasValues;
687
+ }
688
+ // Keep the legacy direct cancellation helper. The guarded cancellation
689
+ // transaction path already uses planEvmCancellationFees.
661
690
  const calculateAndConvertNewValue = (feeValue) => {
662
- // Apply multiplier for replacement transaction (cancel or speedup)
663
691
  const newValue = feeValue * multiplierToUse;
664
692
  const calculateValue = String(newValue);
665
693
  const convertValueToHex = '0x' + parseInt(calculateValue, 10).toString(16);
@@ -667,7 +695,6 @@ class EthereumTransactions {
667
695
  };
668
696
  const maxFeePerGasToNumber = maxFeePerGas?.toNumber();
669
697
  const maxPriorityFeePerGasToNumber = maxPriorityFeePerGas?.toNumber();
670
- const gasLimitToNumber = gasLimit?.toNumber();
671
698
  const gasPriceToNumber = gasPrice?.toNumber();
672
699
  const multiplierToUse = 1.2; //The same calculation we used in the edit fee modal, always using the 0.2 multiplier
673
700
  if (!isLegacy) {
@@ -678,14 +705,8 @@ class EthereumTransactions {
678
705
  if (isLegacy) {
679
706
  newGasValues.gasPrice = calculateAndConvertNewValue(gasPriceToNumber);
680
707
  }
681
- if (isForCancel) {
682
- const DEFAULT_GAS_LIMIT_VALUE = '42000';
683
- const convertToHex = '0x' + parseInt(DEFAULT_GAS_LIMIT_VALUE, 10).toString(16);
684
- newGasValues.gasLimit = ethers_v6_1.BigNumber.from(convertToHex);
685
- }
686
- if (!isForCancel) {
687
- newGasValues.gasLimit = calculateAndConvertNewValue(gasLimitToNumber);
688
- }
708
+ newGasValues.gasLimit = ethers_v6_1.BigNumber.from(42000);
709
+ (0, evm_fees_1.assertEvmGasLimit)(newGasValues.gasLimit.toBigInt(), bounds.chainId, bounds.sampledBlockGasLimit);
689
710
  return newGasValues;
690
711
  };
691
712
  this.cancelSentTransaction = async (txHash, isLegacy, fallbackNonce, options) => {
@@ -694,8 +715,13 @@ class EthereumTransactions {
694
715
  if (typeof options?.assertCurrentContext !== 'function') {
695
716
  throw new Error('Cancellation context required');
696
717
  }
697
- if (typeof options.validateOriginal !== 'function') {
698
- throw new Error('Cancellation original validator required');
718
+ const { assertCurrentContext, validateOriginal, origin } = options;
719
+ if (origin === undefined) {
720
+ if (typeof validateOriginal !== 'function')
721
+ throw new Error('Cancellation original validator required');
722
+ }
723
+ else if (validateOriginal !== undefined || fallbackNonce !== undefined) {
724
+ throw new Error('Retained cancellation cannot use RPC fallback');
699
725
  }
700
726
  if (fallbackNonce !== undefined &&
701
727
  (!Number.isSafeInteger(fallbackNonce) || fallbackNonce < 0)) {
@@ -708,7 +734,6 @@ class EthereumTransactions {
708
734
  !/^(0|[1-9][0-9]{0,77})$/.test(approvedMaximumFee))) {
709
735
  throw new Error('Cancellation fee approval required');
710
736
  }
711
- const { assertCurrentContext, validateOriginal } = options;
712
737
  const assertHostContext = () => assertSynchronousOriginalValidation(assertCurrentContext(), 'Transaction context guard must be synchronous');
713
738
  assertHostContext();
714
739
  const state = this.getState();
@@ -753,9 +778,22 @@ class EthereumTransactions {
753
778
  assertKeyringContext();
754
779
  };
755
780
  assertSigningCurrent();
781
+ const retained = origin === undefined
782
+ ? undefined
783
+ : (0, evm_cancellation_1.authenticateRetainedCancellation)(origin, txHash.toLowerCase(), activeAccount.address, activeNetwork.chainId);
784
+ if (retained) {
785
+ const legacy = retained.feeFloor.type === 0;
786
+ if (activeNetwork.kind !== sysweb3_network_1.INetworkType.Ethereum ||
787
+ (isLegacy !== undefined && isLegacy !== legacy))
788
+ throw new Error('Retained cancellation network or type changed');
789
+ isLegacy = legacy;
790
+ }
756
791
  const submission = previewOnly
757
792
  ? undefined
758
- : this.captureSubmission('cancel', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, txHash);
793
+ : this.captureSubmission('cancel', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, {
794
+ originalHash: retained ? txHash.toLowerCase() : txHash,
795
+ origin: retained,
796
+ });
759
797
  const assertCurrent = submission?.assertCurrent ?? assertSigningCurrent;
760
798
  const currentEip1559Fees = async () => {
761
799
  const feeData = await this.getFeeDataWithDynamicMaxPriorityFeePerGas(provider);
@@ -767,64 +805,53 @@ class EthereumTransactions {
767
805
  }
768
806
  return { maxFeePerGas, maxPriorityFeePerGas };
769
807
  };
770
- const response = await provider.getTransaction(txHash);
771
- assertCurrent();
772
- if (response?.blockNumber != null || response?.blockHash != null) {
773
- throw new Error('Original transaction is already mined');
774
- }
775
- let tx = response === null
776
- ? null
777
- : authenticateReplacementOriginal(response, txHash.toLowerCase(), activeAccount.address.toLowerCase(), activeNetwork.chainId);
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));
790
- assertCurrent();
791
- // If transaction not found, create a minimal tx object with current gas prices
792
- // This handles cases where tx with 0 gas never made it to the mempool
793
- if (!tx && fallbackNonce !== undefined) {
794
- // Fetch current network gas prices for the cancellation
795
- if (isLegacy) {
796
- const currentGasPrice = nonNegativeFeeQuantity(await provider.getGasPrice(), 'cancellation gas price');
797
- assertCurrent();
798
- tx = {
799
- from: activeAccount.address,
800
- to: activeAccount.address,
801
- value: ethers_v6_1.Zero,
802
- nonce: fallbackNonce,
803
- gasPrice: currentGasPrice,
804
- data: '0x',
805
- };
808
+ let cancellationNonce;
809
+ let originalFees;
810
+ if (retained) {
811
+ cancellationNonce = retained.nonce;
812
+ originalFees = retained.feeFloor;
813
+ }
814
+ else {
815
+ const response = await provider.getTransaction(txHash);
816
+ assertCurrent();
817
+ if (response?.blockNumber != null || response?.blockHash != null) {
818
+ throw new Error('Original transaction is already mined');
819
+ }
820
+ const tx = response === null
821
+ ? null
822
+ : authenticateReplacementOriginal(response, txHash.toLowerCase(), activeAccount.address.toLowerCase(), activeNetwork.chainId);
823
+ const software = activeAccountType !== types_1.KeyringAccountType.Ledger &&
824
+ activeAccountType !== types_1.KeyringAccountType.Trezor;
825
+ // The host validator may retain the wrapped response and its fee objects.
826
+ const softwareNonce = software ? tx?.nonce ?? fallbackNonce : undefined;
827
+ const softwareOriginalFees = software && tx
828
+ ? {
829
+ gasPrice: tx.gasPrice.toBigInt(),
830
+ maxFeePerGas: tx.maxFeePerGas.toBigInt(),
831
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas.toBigInt(),
832
+ }
833
+ : undefined;
834
+ assertSynchronousOriginalValidation(validateOriginal(tx));
835
+ assertCurrent();
836
+ if (tx) {
837
+ cancellationNonce = software ? softwareNonce : tx.nonce;
838
+ originalFees = softwareOriginalFees ?? tx;
806
839
  }
807
840
  else {
808
- const feeData = await currentEip1559Fees();
809
- tx = {
810
- from: activeAccount.address,
811
- to: activeAccount.address,
812
- value: ethers_v6_1.Zero,
813
- nonce: fallbackNonce,
814
- maxFeePerGas: feeData.maxFeePerGas,
815
- maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
816
- data: '0x',
817
- };
841
+ if (fallbackNonce === undefined)
842
+ return { isCanceled: false, error: true };
843
+ cancellationNonce = fallbackNonce;
844
+ // Preserve the ordinary host's explicitly admitted missing-RPC path.
845
+ if (isLegacy) {
846
+ const gasPrice = nonNegativeFeeQuantity(await provider.getGasPrice(), 'cancellation gas price');
847
+ assertCurrent();
848
+ originalFees = { gasPrice };
849
+ }
850
+ else
851
+ originalFees = await currentEip1559Fees();
818
852
  }
819
853
  }
820
- else if (!tx) {
821
- // No fallback nonce provided and tx not found
822
- return {
823
- isCanceled: false,
824
- error: true,
825
- };
826
- }
827
- const { gasPrice: originalGasPrice, maxFeePerGas: originalMaxFeePerGas, maxPriorityFeePerGas: originalMaxPriorityFeePerGas, } = softwareOriginalFees ?? tx;
854
+ const { gasPrice: originalGasPrice, maxFeePerGas: originalMaxFeePerGas, maxPriorityFeePerGas: originalMaxPriorityFeePerGas, } = originalFees;
828
855
  let feePlan;
829
856
  if (isLegacy) {
830
857
  const currentGasPrice = (0, evm_fees_1.parseEvmFeeQuantity)(await provider.getGasPrice(), 'cancellation gas price');
@@ -861,6 +888,16 @@ class EthereumTransactions {
861
888
  };
862
889
  if (maximumFee.gt(approvedMaximumFee))
863
890
  throw new Error('Cancellation fee exceeds approval');
891
+ const assertCancellationEnvelope = (unsigned) => {
892
+ if (cancellationNonce === undefined)
893
+ throw new Error('Cancellation nonce required');
894
+ (0, evm_cancellation_1.assertEvmCancellationEnvelope)(unsigned, {
895
+ payer: activeAccount.address,
896
+ chainId: activeNetwork.chainId,
897
+ nonce: cancellationNonce,
898
+ feePlan,
899
+ });
900
+ };
864
901
  const assertApprovedEnvelope = (transaction) => {
865
902
  const gas = nonNegativeFeeQuantity(transaction.gasLimit, 'cancellation gas limit');
866
903
  const cap = nonNegativeFeeQuantity(isLegacy ? transaction.gasPrice : transaction.maxFeePerGas, 'cancellation maximum fee');
@@ -869,7 +906,7 @@ class EthereumTransactions {
869
906
  };
870
907
  // Base cancel transaction parameters (same for all wallet types)
871
908
  const baseCancelTx = {
872
- nonce: software ? softwareNonce : tx.nonce,
909
+ nonce: cancellationNonce,
873
910
  from: activeAccount.address,
874
911
  to: activeAccount.address,
875
912
  value: ethers_v6_1.Zero,
@@ -895,7 +932,7 @@ class EthereumTransactions {
895
932
  assertCurrent();
896
933
  const formatParams = {
897
934
  ...resolvedParams,
898
- nonce: resolvedParams.nonce
935
+ nonce: resolvedParams.nonce != null
899
936
  ? Number(resolvedParams.nonce.toString())
900
937
  : undefined,
901
938
  };
@@ -912,6 +949,8 @@ class EthereumTransactions {
912
949
  };
913
950
  assertApprovedEnvelope(txFormattedForEthers);
914
951
  const rawTx = serializeTransaction(txFormattedForEthers);
952
+ if (retained)
953
+ assertCancellationEnvelope(rawTx);
915
954
  const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
916
955
  rawTx: rawTx.replace('0x', ''),
917
956
  accountIndex: activeAccountId,
@@ -927,7 +966,7 @@ class EthereumTransactions {
927
966
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
928
967
  assertCurrent();
929
968
  assertHardwareTransactionSender(signedTx, activeAccount.address);
930
- const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
969
+ const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
931
970
  return {
932
971
  isCanceled: true,
933
972
  transaction: transactionResponse,
@@ -984,6 +1023,8 @@ class EthereumTransactions {
984
1023
  assertApprovedEnvelope(txFormattedForTrezor);
985
1024
  assertTransactionGasLimit(txFormattedForTrezor);
986
1025
  const expectedUnsignedTransaction = serializeTransaction(txFormattedForTrezor);
1026
+ if (retained)
1027
+ assertCancellationEnvelope(expectedUnsignedTransaction);
987
1028
  const signature = await this.requireTrezorSigner().signEthTransaction({
988
1029
  coin: trezorCoin,
989
1030
  tx: txFormattedForTrezor,
@@ -995,7 +1036,7 @@ class EthereumTransactions {
995
1036
  const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
996
1037
  assertCurrent();
997
1038
  assertHardwareTransactionSender(signedTx, activeAccount.address);
998
- const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
1039
+ const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, expectedUnsignedTransaction);
999
1040
  return {
1000
1041
  isCanceled: true,
1001
1042
  transaction: transactionResponse,
@@ -1026,12 +1067,7 @@ class EthereumTransactions {
1026
1067
  }
1027
1068
  const transactionResponse = await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, changedTxToCancel, assertCurrent, assertCurrent, (transaction) => {
1028
1069
  assertApprovedEnvelope(transaction);
1029
- (0, evm_cancellation_1.assertEvmCancellationEnvelope)(serializeTransaction(transaction), {
1030
- payer: activeAccount.address,
1031
- chainId: activeNetwork.chainId,
1032
- nonce: softwareNonce,
1033
- feePlan,
1034
- });
1070
+ assertCancellationEnvelope(serializeTransaction(transaction));
1035
1071
  }, submission);
1036
1072
  if (transactionResponse) {
1037
1073
  return {
@@ -1077,7 +1113,8 @@ class EthereumTransactions {
1077
1113
  throw new Error('Explicit transaction approval is required');
1078
1114
  // Capture once; later caller mutations cannot replace this invocation's
1079
1115
  // admission decision while chain checks or device approval are pending.
1080
- const { assertCurrent: assertHostCurrent, mode } = options;
1116
+ const { assertCurrent: assertHostCurrent, mode, walletNonce } = options;
1117
+ const nonceApproval = walletNonce === undefined ? undefined : { ...walletNonce };
1081
1118
  const assertApproved = () => {
1082
1119
  const result = assertHostCurrent();
1083
1120
  if (result !== undefined) {
@@ -1088,7 +1125,7 @@ class EthereumTransactions {
1088
1125
  assertApproved();
1089
1126
  return await this.sendFormattedTransactionInternal(params, isLegacy, () => {
1090
1127
  broadcastAttempted = true;
1091
- }, assertApproved, mode);
1128
+ }, assertApproved, mode, nonceApproval);
1092
1129
  }
1093
1130
  catch (error) {
1094
1131
  // A broadcast transport error may mean the node accepted the signed
@@ -1105,7 +1142,7 @@ class EthereumTransactions {
1105
1142
  throw submissionError;
1106
1143
  }
1107
1144
  };
1108
- this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode) => {
1145
+ this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode, walletNonce) => {
1109
1146
  // Hardware approval may outlive the selected account, network or unlocked
1110
1147
  // session. Keep immutable identities and one provider for this invocation.
1111
1148
  const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertKeyringCurrent, } = this.captureEvmSigningContext();
@@ -1117,8 +1154,13 @@ class EthereumTransactions {
1117
1154
  assertCurrent();
1118
1155
  beforeBroadcast();
1119
1156
  };
1120
- const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent);
1157
+ const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, { walletNonce });
1121
1158
  const assertCurrent = submission.assertCurrent;
1159
+ const approvedNonce = submission.context.walletNonce?.nonce;
1160
+ const assertApprovedNonce = (nonce) => {
1161
+ if (approvedNonce !== undefined && nonce !== approvedNonce)
1162
+ throw new Error('EVM transaction nonce does not match wallet approval');
1163
+ };
1122
1164
  if (mode === 'reviewed-native') {
1123
1165
  const { transaction } = (0, prepared_evm_1.snapshotPreparedEvmRequest)({
1124
1166
  account: {
@@ -1134,6 +1176,7 @@ class EthereumTransactions {
1134
1176
  },
1135
1177
  transaction: input,
1136
1178
  });
1179
+ assertApprovedNonce(Number(transaction.nonce));
1137
1180
  const minimum = this.gasOverrides.minGasLimit;
1138
1181
  if (transaction.data !== '0x' ||
1139
1182
  BigInt(transaction.value) === 0n ||
@@ -1152,7 +1195,7 @@ class EthereumTransactions {
1152
1195
  throw new Error('Reviewed native signer does not match the approved account');
1153
1196
  assertCurrent();
1154
1197
  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);
1198
+ return (0, evm_submission_1.submitSignedEvmTransaction)(signed.rawTransaction, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, expectedUnsigned);
1156
1199
  }
1157
1200
  // Detach supplied values before any RPC/device await. Autofill updates only
1158
1201
  // this invocation; caller mutations cannot replace already supplied fields.
@@ -1187,6 +1230,7 @@ class EthereumTransactions {
1187
1230
  supplied.data = (0, utils_1.hexlify)(supplied.data);
1188
1231
  if (supplied.accessList != null)
1189
1232
  supplied.accessList = (0, transaction_1.accessListify)(supplied.accessList);
1233
+ assertApprovedNonce(supplied.nonce);
1190
1234
  params = supplied;
1191
1235
  assertCurrent();
1192
1236
  // Capture the reviewed nonce before any fee/RPC await. Hardware and the
@@ -1308,7 +1352,7 @@ class EthereumTransactions {
1308
1352
  try {
1309
1353
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
1310
1354
  assertHardwareTransactionSender(signedTx, activeAccount.address);
1311
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, rawTx);
1355
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
1312
1356
  return finalTx;
1313
1357
  }
1314
1358
  catch (error) {
@@ -1398,7 +1442,7 @@ class EthereumTransactions {
1398
1442
  };
1399
1443
  const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
1400
1444
  assertHardwareTransactionSender(signedTx, activeAccount.address);
1401
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
1445
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
1402
1446
  return finalTx;
1403
1447
  }
1404
1448
  catch (error) {
@@ -1453,7 +1497,7 @@ class EthereumTransactions {
1453
1497
  throw new Error('Replacement context required');
1454
1498
  if (typeof options.validateOriginal !== 'function')
1455
1499
  throw new Error('Replacement original validator required');
1456
- const { assertCurrentContext, validateOriginal, beforeBroadcast } = options;
1500
+ const { assertCurrentContext, validateOriginal, beforeBroadcast, retainedCancellation, } = options;
1457
1501
  if (!previewOnly)
1458
1502
  this.assertHardwareSignerAvailable(options.targetAccount);
1459
1503
  isLegacy ?? (isLegacy = false);
@@ -1494,9 +1538,27 @@ class EthereumTransactions {
1494
1538
  const requestedHash = txHash.toLowerCase();
1495
1539
  const provider = this.web3Provider;
1496
1540
  assertSigningCurrent();
1541
+ let cancellation;
1542
+ if (retainedCancellation !== undefined) {
1543
+ if (typeof retainedCancellation !== 'string' ||
1544
+ retainedCancellation.length > 1024)
1545
+ throw new Error('Invalid retained cancellation');
1546
+ cancellation = transaction_1.Transaction.from(retainedCancellation);
1547
+ if (!cancellation.isSigned() ||
1548
+ cancellation.serialized !== retainedCancellation ||
1549
+ cancellation.hash !== requestedHash ||
1550
+ cancellation.chainId !== BigInt(activeNetwork.chainId) ||
1551
+ cancellation.from?.toLowerCase() !== expectedSender ||
1552
+ cancellation.to?.toLowerCase() !== expectedSender ||
1553
+ cancellation.value !== 0n ||
1554
+ cancellation.data !== '0x' ||
1555
+ cancellation.accessList?.length ||
1556
+ (cancellation.type !== 0 && cancellation.type !== 2))
1557
+ throw new Error('Invalid retained cancellation');
1558
+ }
1497
1559
  const submission = previewOnly
1498
1560
  ? undefined
1499
- : this.captureSubmission('speed-up', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, txHash);
1561
+ : this.captureSubmission('speed-up', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, { originalHash: txHash });
1500
1562
  const assertCurrent = submission?.assertCurrent ?? assertSigningCurrent;
1501
1563
  const validateLookup = (transaction) => {
1502
1564
  assertCurrent();
@@ -1504,6 +1566,10 @@ class EthereumTransactions {
1504
1566
  return null;
1505
1567
  const authenticated = authenticateReplacementOriginal(transaction, requestedHash, expectedSender, activeNetwork.chainId);
1506
1568
  const type = authenticated.type;
1569
+ if (cancellation &&
1570
+ (type !== cancellation.type ||
1571
+ (0, evm_fees_1.parseEvmFeeQuantity)(authenticated.gasLimit, 'retained cancellation gas limit') !== cancellation.gasLimit))
1572
+ throw new Error('Retained cancellation does not match original');
1507
1573
  if (type !== 1)
1508
1574
  isLegacy = type === 0;
1509
1575
  if (isLegacy &&
@@ -1532,9 +1598,20 @@ class EthereumTransactions {
1532
1598
  };
1533
1599
  }
1534
1600
  }
1535
- // Check if this might be a max send transaction by comparing total cost to balance
1536
1601
  const currentBalance = await provider.getBalance(activeAccount.address);
1537
1602
  assertCurrent();
1603
+ const sampledBlock = await provider.getBlock('latest');
1604
+ assertCurrent();
1605
+ const bounds = Object.freeze({
1606
+ chainId: BigInt(activeNetwork.chainId),
1607
+ ...(cancellation
1608
+ ? { retainedCancellationGasLimit: cancellation.gasLimit }
1609
+ : {}),
1610
+ sampledBlockGasLimit: (0, evm_fees_1.parseEvmFeeQuantity)(sampledBlock?.gasLimit, 'replacement block gas limit'),
1611
+ sampledBaseFeePerGas: sampledBlock?.baseFeePerGas == null
1612
+ ? null
1613
+ : (0, evm_fees_1.parseEvmFeeQuantity)(sampledBlock.baseFeePerGas, 'replacement base fee'),
1614
+ });
1538
1615
  // Ensure all transaction values are resolved from promises
1539
1616
  const gasLimit = await Promise.resolve(tx.gasLimit);
1540
1617
  const gasPrice = await Promise.resolve(tx.gasPrice || 0);
@@ -1543,15 +1620,6 @@ class EthereumTransactions {
1543
1620
  const txValue = await Promise.resolve(tx.value);
1544
1621
  const txData = await Promise.resolve(tx.data || '0x');
1545
1622
  assertCurrent();
1546
- // Check if this is a contract call (has data)
1547
- const isContractCall = txData && txData !== '0x' && txData.length > 2;
1548
- const originalGasCost = isLegacy
1549
- ? gasLimit.mul(gasPrice || 0)
1550
- : gasLimit.mul(maxFeePerGas || 0);
1551
- const originalTotalCost = txValue.add(originalGasCost);
1552
- // If original transaction used >95% of balance, it's likely a max send
1553
- const balanceThreshold = currentBalance.mul(95).div(100);
1554
- const isLikelyMaxSend = originalTotalCost.gt(balanceThreshold);
1555
1623
  const oldTxsGasValues = {
1556
1624
  maxFeePerGas: maxFeePerGas,
1557
1625
  maxPriorityFeePerGas: maxPriorityFeePerGas,
@@ -1565,26 +1633,33 @@ class EthereumTransactions {
1565
1633
  assertCurrent();
1566
1634
  }
1567
1635
  else if (!isLegacy && oldTxsGasValues.maxFeePerGas.isZero()) {
1568
- const feeData = await this.getFeeDataWithDynamicMaxPriorityFeePerGas(provider);
1636
+ const feeData = await this.getFeeDataWithDynamicMaxPriorityFeePerGas({
1637
+ getGasPrice: () => provider.getGasPrice(),
1638
+ getBlock: async () => sampledBlock,
1639
+ send: (method, params) => provider.send(method, params),
1640
+ });
1569
1641
  assertCurrent();
1570
1642
  oldTxsGasValues.maxFeePerGas = ethers_v6_1.BigNumber.from(feeData.maxFeePerGas || 0);
1571
1643
  oldTxsGasValues.maxPriorityFeePerGas = ethers_v6_1.BigNumber.from(feeData.maxPriorityFeePerGas || 0);
1572
1644
  }
1573
- const newGasValues = this.calculateNewGasValues(oldTxsGasValues, false, isLegacy);
1645
+ const newGasValues = this.calculateNewGasValues(oldTxsGasValues, false, isLegacy, bounds);
1574
1646
  if (!newGasValues.gasLimit.gt(0))
1575
1647
  throw new Error('Invalid replacement gas limit');
1648
+ assertTransactionGasLimit({
1649
+ gasLimit: newGasValues.gasLimit,
1650
+ chainId: activeNetwork.chainId,
1651
+ });
1576
1652
  if (!isLegacy &&
1577
1653
  newGasValues.maxPriorityFeePerGas.gt(newGasValues.maxFeePerGas))
1578
1654
  throw new Error('Invalid replacement fee');
1579
1655
  const maximumFee = newGasValues.gasLimit.mul(isLegacy ? newGasValues.gasPrice : newGasValues.maxFeePerGas);
1580
- if (isLikelyMaxSend && txValue.add(maximumFee).gt(currentBalance)) {
1581
- if (isContractCall) {
1582
- console.error('[SpeedUp] Cannot adjust value for contract call - rejecting speedup');
1656
+ if (txValue.add(maximumFee).gt(currentBalance)) {
1657
+ if (txData.length > 2) {
1583
1658
  return {
1584
1659
  isSpeedUp: false,
1585
1660
  error: true,
1586
1661
  code: 'CONTRACT_CALL_MAX_SEND',
1587
- message: 'Cannot speed up a likely max-send contract call; value cannot be adjusted to fit new gas',
1662
+ message: 'Add funds before speeding up this contract call; the original amount will not be changed',
1588
1663
  };
1589
1664
  }
1590
1665
  return {
@@ -1651,7 +1726,7 @@ class EthereumTransactions {
1651
1726
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
1652
1727
  assertSignedSender(signedTx);
1653
1728
  assertHardwareTransactionSender(signedTx, activeAccount.address);
1654
- const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, rawTx);
1729
+ const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
1655
1730
  return {
1656
1731
  isSpeedUp: true,
1657
1732
  transaction: transactionResponse,
@@ -1732,7 +1807,7 @@ class EthereumTransactions {
1732
1807
  const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
1733
1808
  assertSignedSender(signedTx);
1734
1809
  assertHardwareTransactionSender(signedTx, activeAccount.address);
1735
- const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
1810
+ const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
1736
1811
  return {
1737
1812
  isSpeedUp: true,
1738
1813
  transaction: transactionResponse,
@@ -1818,7 +1893,7 @@ class EthereumTransactions {
1818
1893
  }
1819
1894
  };
1820
1895
  this.sendSignedErc20Transaction = async (request, approval) => {
1821
- const { assertApproval, signal } = this.captureReviewedTokenApproval(approval, 'ERC20');
1896
+ const { assertApproval, signal, nonce } = this.captureReviewedTokenApproval(approval, 'ERC20');
1822
1897
  const { receiver, tokenAddress, tokenAmountUnits, saveTrezorTx, networkUrl, } = request;
1823
1898
  const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertSigningCurrent, } = this.captureEvmSigningContext();
1824
1899
  const activeAccountAddress = activeAccount.address;
@@ -1835,7 +1910,7 @@ class EthereumTransactions {
1835
1910
  const submission = this.captureSubmission('erc20', activeAccount, activeAccountType, activeAccountId, activeNetwork, () => {
1836
1911
  assertApproval();
1837
1912
  assertSigningCurrent();
1838
- });
1913
+ }, { walletNonce: { nonce, source: 'automatic' } });
1839
1914
  const assertCurrent = submission.assertCurrent;
1840
1915
  const sendERC20Token = async () => {
1841
1916
  assertCurrent();
@@ -1845,7 +1920,6 @@ class EthereumTransactions {
1845
1920
  let transferMethod;
1846
1921
  if (isLegacy) {
1847
1922
  const overrides = {
1848
- nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
1849
1923
  gasPrice,
1850
1924
  gasLimit,
1851
1925
  type: 0, // Explicitly set Type 0 for legacy token transfers
@@ -1855,7 +1929,6 @@ class EthereumTransactions {
1855
1929
  }
1856
1930
  else {
1857
1931
  const overrides = {
1858
- nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
1859
1932
  maxPriorityFeePerGas,
1860
1933
  maxFeePerGas,
1861
1934
  gasLimit,
@@ -1865,6 +1938,7 @@ class EthereumTransactions {
1865
1938
  }
1866
1939
  return await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, {
1867
1940
  ...transferMethod,
1941
+ nonce,
1868
1942
  chainId: activeNetwork.chainId,
1869
1943
  from: activeAccountAddress,
1870
1944
  value: '0x0',
@@ -1875,8 +1949,6 @@ class EthereumTransactions {
1875
1949
  }
1876
1950
  };
1877
1951
  const sendERC20TokenOnLedger = async () => {
1878
- assertCurrent();
1879
- const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
1880
1952
  assertCurrent();
1881
1953
  try {
1882
1954
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
@@ -1892,7 +1964,7 @@ class EthereumTransactions {
1892
1964
  gasLimit: gasLimit,
1893
1965
  gasPrice,
1894
1966
  data: txData,
1895
- nonce: transactionNonce,
1967
+ nonce,
1896
1968
  chainId: activeNetwork.chainId,
1897
1969
  type: 0,
1898
1970
  };
@@ -1905,7 +1977,7 @@ class EthereumTransactions {
1905
1977
  maxFeePerGas,
1906
1978
  maxPriorityFeePerGas,
1907
1979
  data: txData,
1908
- nonce: transactionNonce,
1980
+ nonce,
1909
1981
  chainId: activeNetwork.chainId,
1910
1982
  type: 2, // Need explicit type for hardware wallet serialization
1911
1983
  };
@@ -1927,8 +1999,8 @@ class EthereumTransactions {
1927
1999
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
1928
2000
  assertCurrent();
1929
2001
  assertHardwareTransactionSender(signedTx, activeAccount.address);
1930
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async (raw) => {
1931
- const response = await provider.sendTransaction(raw);
2002
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async ({ rawTransaction, admit }) => {
2003
+ const response = await admit(() => provider.sendTransaction(rawTransaction));
1932
2004
  saveTrezorTx && saveTrezorTx(response);
1933
2005
  return response;
1934
2006
  }, submission, undefined, rawTx);
@@ -1947,8 +2019,6 @@ class EthereumTransactions {
1947
2019
  }
1948
2020
  };
1949
2021
  const sendERC20TokenOnTrezor = async () => {
1950
- assertCurrent();
1951
- const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
1952
2022
  assertCurrent();
1953
2023
  try {
1954
2024
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
@@ -1963,7 +2033,7 @@ class EthereumTransactions {
1963
2033
  value: '0x0',
1964
2034
  gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
1965
2035
  gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
1966
- nonce: this.toBigNumber(transactionNonce)._hex,
2036
+ nonce: this.toBigNumber(nonce)._hex,
1967
2037
  chainId: activeNetwork.chainId,
1968
2038
  data: txData,
1969
2039
  };
@@ -1975,7 +2045,7 @@ class EthereumTransactions {
1975
2045
  gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
1976
2046
  maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
1977
2047
  maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
1978
- nonce: this.toBigNumber(transactionNonce)._hex,
2048
+ nonce: this.toBigNumber(nonce)._hex,
1979
2049
  chainId: activeNetwork.chainId,
1980
2050
  data: txData,
1981
2051
  };
@@ -2002,7 +2072,7 @@ class EthereumTransactions {
2002
2072
  gasLimit: gasLimit,
2003
2073
  gasPrice,
2004
2074
  data: txData,
2005
- nonce: transactionNonce,
2075
+ nonce,
2006
2076
  chainId: activeNetwork.chainId,
2007
2077
  type: 0,
2008
2078
  };
@@ -2015,7 +2085,7 @@ class EthereumTransactions {
2015
2085
  maxFeePerGas,
2016
2086
  maxPriorityFeePerGas,
2017
2087
  data: txData,
2018
- nonce: transactionNonce,
2088
+ nonce,
2019
2089
  chainId: activeNetwork.chainId,
2020
2090
  type: 2, // Need explicit type for hardware wallet serialization
2021
2091
  };
@@ -2023,8 +2093,8 @@ class EthereumTransactions {
2023
2093
  const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
2024
2094
  assertCurrent();
2025
2095
  assertHardwareTransactionSender(signedTx, activeAccount.address);
2026
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async (raw) => {
2027
- const response = await provider.sendTransaction(raw);
2096
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async ({ rawTransaction, admit }) => {
2097
+ const response = await admit(() => provider.sendTransaction(rawTransaction));
2028
2098
  saveTrezorTx && saveTrezorTx(response);
2029
2099
  return response;
2030
2100
  }, submission, undefined, expectedUnsignedTransaction);
@@ -2052,7 +2122,7 @@ class EthereumTransactions {
2052
2122
  }
2053
2123
  };
2054
2124
  this.sendSignedErc721Transaction = async (request, approval) => {
2055
- const { assertApproval, signal } = this.captureReviewedTokenApproval(approval, 'ERC721');
2125
+ const { assertApproval, signal, nonce } = this.captureReviewedTokenApproval(approval, 'ERC721');
2056
2126
  const { receiver, tokenAddress, tokenId, networkUrl } = request;
2057
2127
  const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertSigningCurrent, } = this.captureEvmSigningContext();
2058
2128
  if (networkUrl !== activeNetwork.url)
@@ -2069,7 +2139,7 @@ class EthereumTransactions {
2069
2139
  const submission = this.captureSubmission('erc721', activeAccount, activeAccountType, activeAccountId, activeNetwork, () => {
2070
2140
  assertApproval();
2071
2141
  assertSigningCurrent();
2072
- });
2142
+ }, { walletNonce: { nonce, source: 'automatic' } });
2073
2143
  const assertCurrent = submission.assertCurrent;
2074
2144
  const sendERC721Token = async () => {
2075
2145
  assertCurrent();
@@ -2079,7 +2149,6 @@ class EthereumTransactions {
2079
2149
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc21Abi)(), provider);
2080
2150
  if (isLegacy) {
2081
2151
  const overrides = {
2082
- nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
2083
2152
  gasPrice,
2084
2153
  gasLimit,
2085
2154
  type: 0, // Explicitly set Type 0 for legacy NFT transfers
@@ -2088,7 +2157,6 @@ class EthereumTransactions {
2088
2157
  }
2089
2158
  else {
2090
2159
  const overrides = {
2091
- nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
2092
2160
  maxPriorityFeePerGas,
2093
2161
  maxFeePerGas,
2094
2162
  gasLimit,
@@ -2097,6 +2165,7 @@ class EthereumTransactions {
2097
2165
  }
2098
2166
  return await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, {
2099
2167
  ...transferMethod,
2168
+ nonce,
2100
2169
  chainId: activeNetwork.chainId,
2101
2170
  from: activeAccountAddress,
2102
2171
  value: '0x0',
@@ -2107,8 +2176,6 @@ class EthereumTransactions {
2107
2176
  }
2108
2177
  };
2109
2178
  const sendERC721TokenOnLedger = async () => {
2110
- assertCurrent();
2111
- const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
2112
2179
  assertCurrent();
2113
2180
  try {
2114
2181
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc21Abi)(), provider);
@@ -2125,7 +2192,7 @@ class EthereumTransactions {
2125
2192
  gasLimit,
2126
2193
  gasPrice,
2127
2194
  data: txData,
2128
- nonce: transactionNonce,
2195
+ nonce,
2129
2196
  chainId: activeNetwork.chainId,
2130
2197
  type: 0,
2131
2198
  };
@@ -2138,7 +2205,7 @@ class EthereumTransactions {
2138
2205
  maxFeePerGas,
2139
2206
  maxPriorityFeePerGas,
2140
2207
  data: txData,
2141
- nonce: transactionNonce,
2208
+ nonce,
2142
2209
  chainId: activeNetwork.chainId,
2143
2210
  type: 2, // Need explicit type for hardware wallet serialization
2144
2211
  };
@@ -2160,7 +2227,7 @@ class EthereumTransactions {
2160
2227
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
2161
2228
  assertCurrent();
2162
2229
  assertHardwareTransactionSender(signedTx, activeAccount.address);
2163
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
2230
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
2164
2231
  return finalTx;
2165
2232
  }
2166
2233
  catch (error) {
@@ -2176,8 +2243,6 @@ class EthereumTransactions {
2176
2243
  }
2177
2244
  };
2178
2245
  const sendERC721TokenOnTrezor = async () => {
2179
- assertCurrent();
2180
- const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
2181
2246
  assertCurrent();
2182
2247
  try {
2183
2248
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc21Abi)(), provider);
@@ -2193,7 +2258,7 @@ class EthereumTransactions {
2193
2258
  value: '0x0',
2194
2259
  gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
2195
2260
  gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
2196
- nonce: this.toBigNumber(transactionNonce)._hex,
2261
+ nonce: this.toBigNumber(nonce)._hex,
2197
2262
  chainId: activeNetwork.chainId,
2198
2263
  data: txData,
2199
2264
  };
@@ -2206,7 +2271,7 @@ class EthereumTransactions {
2206
2271
  gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
2207
2272
  maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
2208
2273
  maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
2209
- nonce: this.toBigNumber(transactionNonce)._hex,
2274
+ nonce: this.toBigNumber(nonce)._hex,
2210
2275
  chainId: activeNetwork.chainId,
2211
2276
  data: txData,
2212
2277
  };
@@ -2235,7 +2300,7 @@ class EthereumTransactions {
2235
2300
  gasLimit,
2236
2301
  gasPrice,
2237
2302
  data: txData,
2238
- nonce: transactionNonce,
2303
+ nonce,
2239
2304
  chainId: activeNetwork.chainId,
2240
2305
  type: 0,
2241
2306
  };
@@ -2248,7 +2313,7 @@ class EthereumTransactions {
2248
2313
  maxFeePerGas,
2249
2314
  maxPriorityFeePerGas,
2250
2315
  data: txData,
2251
- nonce: transactionNonce,
2316
+ nonce,
2252
2317
  chainId: activeNetwork.chainId,
2253
2318
  type: 2, // Need explicit type for hardware wallet serialization
2254
2319
  };
@@ -2256,7 +2321,7 @@ class EthereumTransactions {
2256
2321
  const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
2257
2322
  assertCurrent();
2258
2323
  assertHardwareTransactionSender(signedTx, activeAccount.address);
2259
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
2324
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, expectedUnsignedTransaction);
2260
2325
  return finalTx;
2261
2326
  }
2262
2327
  catch (error) {
@@ -2283,7 +2348,7 @@ class EthereumTransactions {
2283
2348
  }
2284
2349
  };
2285
2350
  this.sendSignedErc1155Transaction = async (request, approval) => {
2286
- const { assertApproval, signal } = this.captureReviewedTokenApproval(approval, 'ERC1155');
2351
+ const { assertApproval, signal, nonce } = this.captureReviewedTokenApproval(approval, 'ERC1155');
2287
2352
  const { receiver, tokenAddress, tokenId, networkUrl } = request;
2288
2353
  const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertSigningCurrent, } = this.captureEvmSigningContext();
2289
2354
  if (networkUrl !== activeNetwork.url)
@@ -2303,7 +2368,7 @@ class EthereumTransactions {
2303
2368
  const submission = this.captureSubmission('erc1155', activeAccount, activeAccountType, activeAccountId, activeNetwork, () => {
2304
2369
  assertApproval();
2305
2370
  assertSigningCurrent();
2306
- });
2371
+ }, { walletNonce: { nonce, source: 'automatic' } });
2307
2372
  const assertCurrent = submission.assertCurrent;
2308
2373
  const sendERC1155Token = async () => {
2309
2374
  assertCurrent();
@@ -2315,7 +2380,6 @@ class EthereumTransactions {
2315
2380
  let overrides;
2316
2381
  if (isLegacy) {
2317
2382
  overrides = {
2318
- nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
2319
2383
  gasPrice,
2320
2384
  gasLimit,
2321
2385
  type: 0, // Explicitly set Type 0 for legacy ERC1155 transfers
@@ -2323,7 +2387,6 @@ class EthereumTransactions {
2323
2387
  }
2324
2388
  else {
2325
2389
  overrides = {
2326
- nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
2327
2390
  maxPriorityFeePerGas,
2328
2391
  maxFeePerGas,
2329
2392
  gasLimit,
@@ -2332,6 +2395,7 @@ class EthereumTransactions {
2332
2395
  transferMethod = await _contract.safeTransferFrom.populateTransaction(activeAccountAddress, receiver, normalizedTokenId, amount, '0x', normalizeEthersOverrides(overrides));
2333
2396
  return await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, {
2334
2397
  ...transferMethod,
2398
+ nonce,
2335
2399
  chainId: activeNetwork.chainId,
2336
2400
  from: activeAccountAddress,
2337
2401
  value: '0x0',
@@ -2342,8 +2406,6 @@ class EthereumTransactions {
2342
2406
  }
2343
2407
  };
2344
2408
  const sendERC1155TokenOnLedger = async () => {
2345
- assertCurrent();
2346
- const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
2347
2409
  assertCurrent();
2348
2410
  try {
2349
2411
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
@@ -2356,7 +2418,7 @@ class EthereumTransactions {
2356
2418
  gasLimit,
2357
2419
  gasPrice,
2358
2420
  data: txData,
2359
- nonce: transactionNonce,
2421
+ nonce,
2360
2422
  chainId: activeNetwork.chainId,
2361
2423
  type: 0,
2362
2424
  };
@@ -2369,7 +2431,7 @@ class EthereumTransactions {
2369
2431
  maxFeePerGas,
2370
2432
  maxPriorityFeePerGas,
2371
2433
  data: txData,
2372
- nonce: transactionNonce,
2434
+ nonce,
2373
2435
  chainId: activeNetwork.chainId,
2374
2436
  type: 2, // Need explicit type for hardware wallet serialization
2375
2437
  };
@@ -2391,7 +2453,7 @@ class EthereumTransactions {
2391
2453
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
2392
2454
  assertCurrent();
2393
2455
  assertHardwareTransactionSender(signedTx, activeAccount.address);
2394
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
2456
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
2395
2457
  return finalTx;
2396
2458
  }
2397
2459
  catch (error) {
@@ -2407,8 +2469,6 @@ class EthereumTransactions {
2407
2469
  }
2408
2470
  };
2409
2471
  const sendERC1155TokenOnTrezor = async () => {
2410
- assertCurrent();
2411
- const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
2412
2472
  assertCurrent();
2413
2473
  try {
2414
2474
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
@@ -2420,7 +2480,7 @@ class EthereumTransactions {
2420
2480
  value: '0x0',
2421
2481
  gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
2422
2482
  gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
2423
- nonce: this.toBigNumber(transactionNonce)._hex,
2483
+ nonce: this.toBigNumber(nonce)._hex,
2424
2484
  chainId: activeNetwork.chainId,
2425
2485
  data: txData,
2426
2486
  };
@@ -2432,7 +2492,7 @@ class EthereumTransactions {
2432
2492
  gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
2433
2493
  maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
2434
2494
  maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
2435
- nonce: this.toBigNumber(transactionNonce)._hex,
2495
+ nonce: this.toBigNumber(nonce)._hex,
2436
2496
  chainId: activeNetwork.chainId,
2437
2497
  data: txData,
2438
2498
  };
@@ -2459,7 +2519,7 @@ class EthereumTransactions {
2459
2519
  gasLimit,
2460
2520
  gasPrice,
2461
2521
  data: txData,
2462
- nonce: transactionNonce,
2522
+ nonce,
2463
2523
  chainId: activeNetwork.chainId,
2464
2524
  type: 0,
2465
2525
  };
@@ -2472,7 +2532,7 @@ class EthereumTransactions {
2472
2532
  maxFeePerGas,
2473
2533
  maxPriorityFeePerGas,
2474
2534
  data: txData,
2475
- nonce: transactionNonce,
2535
+ nonce,
2476
2536
  chainId: activeNetwork.chainId,
2477
2537
  type: 2, // Need explicit type for hardware wallet serialization
2478
2538
  };
@@ -2480,7 +2540,7 @@ class EthereumTransactions {
2480
2540
  const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
2481
2541
  assertCurrent();
2482
2542
  assertHardwareTransactionSender(signedTx, activeAccount.address);
2483
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
2543
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, expectedUnsignedTransaction);
2484
2544
  return finalTx;
2485
2545
  }
2486
2546
  catch (error) {