@sidhujag/sysweb3-keyring 2.1.3 → 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 (126) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +47 -47
  3. package/cjs/hardware-transaction-authority.js +36 -0
  4. package/cjs/hardware-transaction-authority.js.map +1 -0
  5. package/cjs/index.js +5 -5
  6. package/cjs/index.js.map +1 -1
  7. package/cjs/keyring-core.js +171 -220
  8. package/cjs/keyring-core.js.map +1 -1
  9. package/cjs/keyring-host.js +113 -0
  10. package/cjs/keyring-host.js.map +1 -0
  11. package/cjs/keyring-manager.js +15 -151
  12. package/cjs/keyring-manager.js.map +1 -1
  13. package/cjs/ledger/host-types.js.map +1 -1
  14. package/cjs/ledger/index.js +44 -19
  15. package/cjs/ledger/index.js.map +1 -1
  16. package/cjs/providers.js +3 -1
  17. package/cjs/providers.js.map +1 -1
  18. package/cjs/software.js +10 -56
  19. package/cjs/software.js.map +1 -1
  20. package/cjs/transactions/ethereum.js +306 -229
  21. package/cjs/transactions/ethereum.js.map +1 -1
  22. package/cjs/transactions/evm-local-signer.js +15 -0
  23. package/cjs/transactions/evm-local-signer.js.map +1 -1
  24. package/cjs/transactions/evm-submission.js +61 -65
  25. package/cjs/transactions/evm-submission.js.map +1 -1
  26. package/cjs/transactions/hardware-psbt.js +11 -8
  27. package/cjs/transactions/hardware-psbt.js.map +1 -1
  28. package/cjs/transactions/index.js +10 -2
  29. package/cjs/transactions/index.js.map +1 -1
  30. package/cjs/transactions/prepared-evm.js +10 -7
  31. package/cjs/transactions/prepared-evm.js.map +1 -1
  32. package/cjs/transactions/syscoin-core.js +282 -202
  33. package/cjs/transactions/syscoin-core.js.map +1 -1
  34. package/cjs/trezor/connect.js +19 -2
  35. package/cjs/trezor/connect.js.map +1 -1
  36. package/cjs/trezor/index.js +38 -16
  37. package/cjs/trezor/index.js.map +1 -1
  38. package/cjs/types.js.map +1 -1
  39. package/cjs/utils/owned-blockbook-account.js +45 -0
  40. package/cjs/utils/owned-blockbook-account.js.map +1 -0
  41. package/esm/current-vault-refusal.js +3 -0
  42. package/esm/current-vault-refusal.js.map +1 -0
  43. package/esm/evm-fees.js +242 -0
  44. package/esm/evm-fees.js.map +1 -0
  45. package/esm/hardware-transaction-authority.js +33 -0
  46. package/esm/hardware-transaction-authority.js.map +1 -0
  47. package/esm/initial-state.js +102 -0
  48. package/esm/initial-state.js.map +1 -0
  49. package/esm/keyring-core.js +2781 -0
  50. package/esm/keyring-core.js.map +1 -0
  51. package/esm/keyring-host.js +108 -0
  52. package/esm/keyring-host.js.map +1 -0
  53. package/esm/ledger/dmk-session.js +1 -0
  54. package/esm/ledger/dmk-session.js.map +1 -1
  55. package/esm/ledger/dmk-utxo.js +9 -3
  56. package/esm/ledger/dmk-utxo.js.map +1 -1
  57. package/esm/ledger/host-types.js.map +1 -1
  58. package/esm/ledger/index.js +44 -19
  59. package/esm/ledger/index.js.map +1 -1
  60. package/esm/network-utils.js +70 -0
  61. package/esm/network-utils.js.map +1 -0
  62. package/esm/providers.js +3 -1
  63. package/esm/providers.js.map +1 -1
  64. package/esm/software-hd-account.js +20 -0
  65. package/esm/software-hd-account.js.map +1 -0
  66. package/esm/storage-core.js +258 -0
  67. package/esm/storage-core.js.map +1 -0
  68. package/esm/transactions/ethereum.js +2753 -0
  69. package/esm/transactions/ethereum.js.map +1 -0
  70. package/esm/transactions/evm-cancellation.js +40 -0
  71. package/esm/transactions/evm-cancellation.js.map +1 -0
  72. package/esm/transactions/evm-local-signer.js +202 -0
  73. package/esm/transactions/evm-local-signer.js.map +1 -0
  74. package/esm/transactions/evm-submission.js +187 -0
  75. package/esm/transactions/evm-submission.js.map +1 -0
  76. package/esm/transactions/prepared-evm.js +194 -0
  77. package/esm/transactions/prepared-evm.js.map +1 -0
  78. package/esm/transactions/syscoin-core.js +691 -0
  79. package/esm/transactions/syscoin-core.js.map +1 -0
  80. package/esm/trezor/connect.js +19 -2
  81. package/esm/trezor/connect.js.map +1 -1
  82. package/esm/trezor/index.js +38 -16
  83. package/esm/trezor/index.js.map +1 -1
  84. package/esm/types.js.map +1 -1
  85. package/esm/utils/excluded-utxos.js +53 -0
  86. package/esm/utils/excluded-utxos.js.map +1 -0
  87. package/esm/utils/owned-blockbook-account.js +9 -0
  88. package/esm/utils/owned-blockbook-account.js.map +1 -0
  89. package/esm/utils/psbt.js +32 -0
  90. package/esm/utils/psbt.js.map +1 -0
  91. package/esm/utils/watch-descriptor.js +138 -0
  92. package/esm/utils/watch-descriptor.js.map +1 -0
  93. package/package.json +5 -11
  94. package/types/hardware-transaction-authority.d.ts +5 -0
  95. package/types/index.d.ts +3 -3
  96. package/types/keyring-core.d.ts +22 -51
  97. package/types/keyring-host.d.ts +46 -0
  98. package/types/keyring-manager.d.ts +13 -18
  99. package/types/ledger/host-types.d.ts +4 -1
  100. package/types/ledger/index.d.ts +1 -1
  101. package/types/ledger/types.d.ts +3 -2
  102. package/types/providers.d.ts +1 -1
  103. package/types/software.d.ts +6 -19
  104. package/types/transactions/ethereum.d.ts +14 -11
  105. package/types/transactions/evm-local-signer.d.ts +2 -2
  106. package/types/transactions/evm-submission.d.ts +11 -4
  107. package/types/transactions/hardware-psbt.d.ts +1 -1
  108. package/types/transactions/index.d.ts +3 -2
  109. package/types/transactions/prepared-evm.d.ts +4 -1
  110. package/types/transactions/syscoin-core.d.ts +48 -21
  111. package/types/trezor/connect.d.ts +6 -3
  112. package/types/trezor/index.d.ts +4 -3
  113. package/types/types.d.ts +95 -63
  114. package/types/utils/owned-blockbook-account.d.ts +3 -0
  115. package/cjs/hardware-wallet-manager-singleton.js +0 -34
  116. package/cjs/hardware-wallet-manager-singleton.js.map +0 -1
  117. package/cjs/storage.js +0 -20
  118. package/cjs/storage.js.map +0 -1
  119. package/cjs/transactions/syscoin.js +0 -16
  120. package/cjs/transactions/syscoin.js.map +0 -1
  121. package/cjs/utils/blockbook-cache.js +0 -76
  122. package/cjs/utils/blockbook-cache.js.map +0 -1
  123. package/types/hardware-wallet-manager-singleton.d.ts +0 -19
  124. package/types/storage.d.ts +0 -5
  125. package/types/transactions/syscoin.d.ts +0 -9
  126. package/types/utils/blockbook-cache.d.ts +0 -6
@@ -17,6 +17,7 @@ const types_1 = require("../types");
17
17
  const evm_cancellation_1 = require("./evm-cancellation");
18
18
  const evm_local_signer_1 = require("./evm-local-signer");
19
19
  const evm_submission_1 = require("./evm-submission");
20
+ const prepared_evm_1 = require("./prepared-evm");
20
21
  const derivation_paths_1 = require("../utils/derivation-paths");
21
22
  const hardwareSignerUnavailable = (hardware) => Object.assign(new Error(`${hardware} signer is unavailable in this host`), {
22
23
  code: 'HARDWARE_SIGNER_UNAVAILABLE',
@@ -116,14 +117,16 @@ const authenticateReplacementOriginal = (transaction, requestedHash, expectedSen
116
117
  });
117
118
  };
118
119
  const assertSynchronousOriginalValidation = (result, message = 'Original transaction validator must be synchronous') => {
120
+ if (result === undefined)
121
+ return;
119
122
  if (result !== null &&
120
123
  (typeof result === 'object' || typeof result === 'function') &&
121
124
  typeof result.then === 'function') {
122
125
  // A void callback type also accepts async functions. Decline their pending
123
126
  // decision and consume a later rejection without allowing signing to start.
124
127
  void Promise.resolve(result).catch(() => undefined);
125
- throw new Error(message);
126
128
  }
129
+ throw new Error(message);
127
130
  };
128
131
  class EthereumTransactions {
129
132
  constructor(getNetwork, getDecryptedPrivateKey, getState, ledgerSigner, trezorSigner, captureSigningContext, captureSubmissionLifecycle) {
@@ -149,6 +152,14 @@ class EthereumTransactions {
149
152
  if (type === types_1.KeyringAccountType.Trezor || account?.isTrezorWallet)
150
153
  this.requireTrezorSigner();
151
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
+ };
152
163
  this.captureEvmSigningContext = (target) => {
153
164
  this.assertHardwareSignerAvailable(target);
154
165
  const state = this.getState();
@@ -162,10 +173,10 @@ class EthereumTransactions {
162
173
  throw new Error('Active account not found');
163
174
  const account = { address: selectedAccount.address };
164
175
  const network = { ...state.activeNetwork };
165
- const assertSession = this.captureSigningContext?.();
176
+ const assertSession = this.captureSessionGuard();
166
177
  const provider = this.web3Provider;
167
178
  const assertCurrent = () => {
168
- assertSession?.();
179
+ assertSession();
169
180
  const current = this.getState();
170
181
  const currentAccount = current.accounts[activeAccountType]?.[activeAccountId];
171
182
  if (current.activeAccountId !== selectedId ||
@@ -191,17 +202,30 @@ class EthereumTransactions {
191
202
  assertCurrent,
192
203
  };
193
204
  };
194
- this.captureSubmission = (operation, account, type, id, network, assertCurrent, originalHash) => (0, evm_submission_1.captureEvmSubmission)({
195
- operation,
196
- account: { address: account.address, type, id },
197
- network: {
198
- kind: network.kind,
199
- chainId: network.chainId,
200
- slip44: network.slip44,
201
- url: network.url,
202
- },
203
- ...(originalHash !== undefined ? { originalHash } : {}),
204
- }, 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));
205
229
  this.signTypedData = async (addr, typedData, version) => {
206
230
  const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
207
231
  const { activeAccountType, accounts, activeAccountId } = this.getState();
@@ -555,35 +579,45 @@ class EthereumTransactions {
555
579
  throw new Error(`Invalid numeric field "${fieldName}" for EVM tx: ${String(value)}`);
556
580
  }
557
581
  };
558
- this.resolveEvmFeeParams = async ({ gasPrice, isLegacy = false, maxFeePerGas, maxPriorityFeePerGas, }, provider = this.web3Provider) => {
559
- if (isLegacy) {
560
- return {
561
- gasPrice: gasPrice || (await provider.getGasPrice()),
562
- isLegacy: true,
563
- maxFeePerGas: undefined,
564
- maxPriorityFeePerGas: undefined,
565
- };
566
- }
567
- if (!maxFeePerGas || maxPriorityFeePerGas == null) {
568
- const feeData = await provider.getFeeData();
569
- maxFeePerGas = maxFeePerGas || feeData.maxFeePerGas || feeData.gasPrice;
570
- maxPriorityFeePerGas =
571
- maxPriorityFeePerGas ?? feeData.maxPriorityFeePerGas;
572
- if (!maxFeePerGas || maxPriorityFeePerGas == null) {
573
- return {
574
- gasPrice: feeData.gasPrice || (await provider.getGasPrice()),
575
- isLegacy: true,
576
- maxFeePerGas: undefined,
577
- maxPriorityFeePerGas: undefined,
578
- };
579
- }
580
- }
581
- return {
582
- gasPrice: undefined,
583
- isLegacy: false,
584
- maxFeePerGas,
585
- 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`);
586
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 };
587
621
  };
588
622
  this.getData = ({ contractAddress, receivingAddress, value, }) => {
589
623
  const abi = (0, sysweb3_utils_1.getErc20Abi)();
@@ -674,7 +708,8 @@ class EthereumTransactions {
674
708
  !/^(0|[1-9][0-9]{0,77})$/.test(approvedMaximumFee))) {
675
709
  throw new Error('Cancellation fee approval required');
676
710
  }
677
- const assertHostContext = options.assertCurrentContext;
711
+ const { assertCurrentContext, validateOriginal } = options;
712
+ const assertHostContext = () => assertSynchronousOriginalValidation(assertCurrentContext(), 'Transaction context guard must be synchronous');
678
713
  assertHostContext();
679
714
  const state = this.getState();
680
715
  const selectedType = state.activeAccountType;
@@ -697,9 +732,9 @@ class EthereumTransactions {
697
732
  if (!previewOnly)
698
733
  this.assertHardwareSignerAvailable(targetAccount);
699
734
  const provider = this.web3Provider;
700
- const assertSession = this.captureSigningContext?.();
735
+ const assertSession = this.captureSessionGuard();
701
736
  const assertKeyringContext = () => {
702
- assertSession?.();
737
+ assertSession();
703
738
  const current = this.getState();
704
739
  if (current.activeAccountType !== selectedType ||
705
740
  current.activeAccountId !== selectedId ||
@@ -713,14 +748,15 @@ class EthereumTransactions {
713
748
  throw new Error('Wallet signing context changed');
714
749
  }
715
750
  };
716
- const assertCurrent = () => {
751
+ const assertSigningCurrent = () => {
717
752
  assertHostContext();
718
753
  assertKeyringContext();
719
754
  };
720
- assertCurrent();
755
+ assertSigningCurrent();
721
756
  const submission = previewOnly
722
757
  ? undefined
723
- : 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;
724
760
  const currentEip1559Fees = async () => {
725
761
  const feeData = await this.getFeeDataWithDynamicMaxPriorityFeePerGas(provider);
726
762
  assertCurrent();
@@ -750,7 +786,7 @@ class EthereumTransactions {
750
786
  maxPriorityFeePerGas: tx.maxPriorityFeePerGas.toBigInt(),
751
787
  }
752
788
  : undefined;
753
- assertSynchronousOriginalValidation(options.validateOriginal(tx));
789
+ assertSynchronousOriginalValidation(validateOriginal(tx));
754
790
  assertCurrent();
755
791
  // If transaction not found, create a minimal tx object with current gas prices
756
792
  // This handles cases where tx with 0 gas never made it to the mempool
@@ -817,7 +853,7 @@ class EthereumTransactions {
817
853
  }
818
854
  const maximumFee = ethers_v6_1.BigNumber.from(feePlan.maximumFee);
819
855
  assertCurrent();
820
- if (previewOnly)
856
+ if (!submission)
821
857
  return {
822
858
  isCanceled: false,
823
859
  error: false,
@@ -880,7 +916,7 @@ class EthereumTransactions {
880
916
  rawTx: rawTx.replace('0x', ''),
881
917
  accountIndex: activeAccountId,
882
918
  expectedAddress: activeAccount.address,
883
- });
919
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent));
884
920
  assertCurrent();
885
921
  const formattedSignature = {
886
922
  r: `0x${signature.r}`,
@@ -891,7 +927,7 @@ class EthereumTransactions {
891
927
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
892
928
  assertCurrent();
893
929
  assertHardwareTransactionSender(signedTx, activeAccount.address);
894
- 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);
895
931
  return {
896
932
  isCanceled: true,
897
933
  transaction: transactionResponse,
@@ -953,13 +989,13 @@ class EthereumTransactions {
953
989
  tx: txFormattedForTrezor,
954
990
  index: activeAccountId.toString(),
955
991
  slip44: activeNetwork.slip44,
956
- });
992
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent));
957
993
  assertCurrent();
958
994
  if (signature.success) {
959
995
  const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
960
996
  assertCurrent();
961
997
  assertHardwareTransactionSender(signedTx, activeAccount.address);
962
- 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);
963
999
  return {
964
1000
  isCanceled: true,
965
1001
  transaction: transactionResponse,
@@ -1028,21 +1064,31 @@ class EthereumTransactions {
1028
1064
  }
1029
1065
  };
1030
1066
  //TODO: This function needs to be refactored
1031
- this.sendFormattedTransaction = async (params, isLegacy, options) => {
1067
+ this.sendFormattedTransaction = async (...args) => {
1068
+ const [params, isLegacy, options] = args;
1032
1069
  let broadcastAttempted = false;
1033
1070
  try {
1034
- if (options !== undefined &&
1035
- (!options || typeof options !== 'object' || Array.isArray(options)))
1036
- 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');
1037
1078
  // Capture once; later caller mutations cannot replace this invocation's
1038
1079
  // admission decision while chain checks or device approval are pending.
1039
- const assertHostCurrent = options?.assertCurrent;
1040
- if (assertHostCurrent !== undefined &&
1041
- typeof assertHostCurrent !== 'function')
1042
- 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();
1043
1089
  return await this.sendFormattedTransactionInternal(params, isLegacy, () => {
1044
1090
  broadcastAttempted = true;
1045
- }, assertHostCurrent);
1091
+ }, assertApproved, mode);
1046
1092
  }
1047
1093
  catch (error) {
1048
1094
  // A broadcast transport error may mean the node accepted the signed
@@ -1059,26 +1105,58 @@ class EthereumTransactions {
1059
1105
  throw submissionError;
1060
1106
  }
1061
1107
  };
1062
- this.sendFormattedTransactionInternal = async (params, isLegacy, beforeBroadcast, assertHostCurrent) => {
1108
+ this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode) => {
1063
1109
  // Hardware approval may outlive the selected account, network or unlocked
1064
1110
  // session. Keep immutable identities and one provider for this invocation.
1065
1111
  const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertKeyringCurrent, } = this.captureEvmSigningContext();
1066
- const assertCurrent = () => {
1067
- const result = assertHostCurrent?.();
1068
- if (result !== undefined) {
1069
- assertSynchronousOriginalValidation(result, 'Transaction context guard must be synchronous');
1070
- throw new Error('Transaction context guard must return undefined');
1071
- }
1112
+ const assertSigningCurrent = () => {
1113
+ assertHostCurrent();
1072
1114
  assertKeyringCurrent();
1073
1115
  };
1074
1116
  const assertBeforeBroadcast = () => {
1075
1117
  assertCurrent();
1076
1118
  beforeBroadcast();
1077
1119
  };
1078
- assertCurrent();
1079
- 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
+ }
1080
1157
  // Detach supplied values before any RPC/device await. Autofill updates only
1081
1158
  // this invocation; caller mutations cannot replace already supplied fields.
1159
+ let params = input;
1082
1160
  if (!params || typeof params !== 'object' || Array.isArray(params))
1083
1161
  throw new TypeError('Invalid EVM transaction request');
1084
1162
  const supplied = { ...params };
@@ -1219,7 +1297,7 @@ class EthereumTransactions {
1219
1297
  rawTx: rawTx.replace('0x', ''),
1220
1298
  accountIndex: activeAccountId,
1221
1299
  expectedAddress: activeAccount.address,
1222
- });
1300
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent));
1223
1301
  assertCurrent();
1224
1302
  const formattedSignature = {
1225
1303
  r: `0x${signature.r}`,
@@ -1230,7 +1308,7 @@ class EthereumTransactions {
1230
1308
  try {
1231
1309
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
1232
1310
  assertHardwareTransactionSender(signedTx, activeAccount.address);
1233
- 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);
1234
1312
  return finalTx;
1235
1313
  }
1236
1314
  catch (error) {
@@ -1301,7 +1379,7 @@ class EthereumTransactions {
1301
1379
  tx: txFormattedForTrezor,
1302
1380
  coin: activeNetwork.currency,
1303
1381
  slip44: activeNetwork.slip44,
1304
- });
1382
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent));
1305
1383
  assertCurrent();
1306
1384
  if (signature.success) {
1307
1385
  try {
@@ -1320,7 +1398,7 @@ class EthereumTransactions {
1320
1398
  };
1321
1399
  const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
1322
1400
  assertHardwareTransactionSender(signedTx, activeAccount.address);
1323
- 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);
1324
1402
  return finalTx;
1325
1403
  }
1326
1404
  catch (error) {
@@ -1364,35 +1442,37 @@ class EthereumTransactions {
1364
1442
  return await sendEVMTransaction();
1365
1443
  }
1366
1444
  };
1367
- this.sendTransactionWithEditedFee = async (txHash, isLegacy = false, options = {}) => {
1368
- const { assertCurrentContext, validateOriginal, beforeBroadcast, approvedMaximumFee, } = options;
1369
- const previewOnly = options.previewOnly === true;
1370
- if (!previewOnly)
1371
- this.assertHardwareSignerAvailable(options.targetAccount);
1445
+ this.sendTransactionWithEditedFee = async (txHash, isLegacy, options) => {
1446
+ const previewOnly = options?.previewOnly === true;
1447
+ const approvedMaximumFee = options?.approvedMaximumFee;
1372
1448
  if (!previewOnly &&
1373
1449
  (typeof approvedMaximumFee !== 'string' ||
1374
- !/^\d{1,78}$/.test(approvedMaximumFee))) {
1450
+ !/^(0|[1-9][0-9]{0,77})$/.test(approvedMaximumFee)))
1375
1451
  throw new Error('Replacement fee approval required');
1376
- }
1377
- let signing;
1378
- const assertCurrent = () => {
1379
- const result = assertCurrentContext?.();
1380
- assertSynchronousOriginalValidation(result, 'Transaction context guard must be synchronous');
1381
- if (result !== undefined)
1382
- throw new Error('Transaction context guard must return undefined');
1383
- 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?.();
1384
1464
  };
1385
1465
  const assertBeforeBroadcast = () => {
1386
- assertCurrent();
1387
- beforeBroadcast?.();
1388
- assertCurrent();
1466
+ assertSigningCurrent();
1467
+ assertSynchronousOriginalValidation(beforeBroadcast?.(), 'Transaction broadcast guard must be synchronous');
1468
+ assertSigningCurrent();
1389
1469
  };
1390
1470
  const assertSignedSender = (signedTx) => {
1391
1471
  if (transaction_1.Transaction.from(signedTx).from?.toLowerCase() !== expectedSender) {
1392
1472
  throw new Error('Replacement signature does not match account');
1393
1473
  }
1394
1474
  };
1395
- assertCurrent();
1475
+ assertSigningCurrent();
1396
1476
  const state = this.getState();
1397
1477
  const activeAccountType = options.targetAccount?.type ?? state.activeAccountType;
1398
1478
  const activeAccountId = options.targetAccount?.id ?? state.activeAccountId;
@@ -1408,14 +1488,16 @@ class EthereumTransactions {
1408
1488
  throw new Error('Replacement signing account is unavailable');
1409
1489
  }
1410
1490
  if (!previewOnly)
1411
- signing = this.captureEvmSigningContext(targetAccount);
1491
+ assertCapturedSignerCurrent =
1492
+ this.captureEvmSigningContext(targetAccount).assertCurrent;
1412
1493
  const expectedSender = activeAccount.address.toLowerCase();
1413
1494
  const requestedHash = txHash.toLowerCase();
1414
1495
  const provider = this.web3Provider;
1415
- assertCurrent();
1496
+ assertSigningCurrent();
1416
1497
  const submission = previewOnly
1417
1498
  ? undefined
1418
- : 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;
1419
1501
  const validateLookup = (transaction) => {
1420
1502
  assertCurrent();
1421
1503
  if (!transaction)
@@ -1429,7 +1511,7 @@ class EthereumTransactions {
1429
1511
  activeAccountType === types_1.KeyringAccountType.Trezor) {
1430
1512
  throw new Error('Trezor does not support type 1 replacement signing');
1431
1513
  }
1432
- assertSynchronousOriginalValidation(validateOriginal?.(authenticated));
1514
+ assertSynchronousOriginalValidation(validateOriginal(authenticated));
1433
1515
  assertCurrent();
1434
1516
  return authenticated;
1435
1517
  };
@@ -1528,7 +1610,7 @@ class EthereumTransactions {
1528
1610
  maxPriorityFeePerGas: newGasValues.maxPriorityFeePerGas,
1529
1611
  }),
1530
1612
  };
1531
- if (previewOnly)
1613
+ if (!submission)
1532
1614
  return { isSpeedUp: false, maximumFee: maximumFee.toString() };
1533
1615
  if (maximumFee.gt(approvedMaximumFee))
1534
1616
  throw new Error('Replacement fee exceeds approval');
@@ -1559,7 +1641,7 @@ class EthereumTransactions {
1559
1641
  rawTx: rawTx.replace('0x', ''),
1560
1642
  accountIndex: activeAccountId,
1561
1643
  expectedAddress: activeAccount.address,
1562
- });
1644
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent));
1563
1645
  const formattedSignature = {
1564
1646
  r: `0x${signature.r}`,
1565
1647
  s: `0x${signature.s}`,
@@ -1569,7 +1651,7 @@ class EthereumTransactions {
1569
1651
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
1570
1652
  assertSignedSender(signedTx);
1571
1653
  assertHardwareTransactionSender(signedTx, activeAccount.address);
1572
- 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);
1573
1655
  return {
1574
1656
  isSpeedUp: true,
1575
1657
  transaction: transactionResponse,
@@ -1645,12 +1727,12 @@ class EthereumTransactions {
1645
1727
  tx: txFormattedForTrezor,
1646
1728
  index: activeAccountId.toString(),
1647
1729
  slip44: activeNetwork.slip44,
1648
- });
1730
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent));
1649
1731
  if (signature.success) {
1650
1732
  const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
1651
1733
  assertSignedSender(signedTx);
1652
1734
  assertHardwareTransactionSender(signedTx, activeAccount.address);
1653
- 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);
1654
1736
  return {
1655
1737
  isSpeedUp: true,
1656
1738
  transaction: transactionResponse,
@@ -1735,44 +1817,51 @@ class EthereumTransactions {
1735
1817
  return await speedUpWithPrivateKey();
1736
1818
  }
1737
1819
  };
1738
- this.sendSignedErc20Transaction = async ({ receiver, tokenAddress, tokenAmount, isLegacy = false, maxPriorityFeePerGas, maxFeePerGas, gasPrice, decimals, gasLimit, saveTrezorTx, }) => {
1739
- 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();
1740
1824
  const activeAccountAddress = activeAccount.address;
1741
- const submission = this.captureSubmission('erc20', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertCurrent);
1742
- ({ gasPrice, isLegacy, maxFeePerGas, maxPriorityFeePerGas } =
1743
- await this.resolveEvmFeeParams({
1744
- gasPrice,
1745
- isLegacy,
1746
- maxFeePerGas,
1747
- maxPriorityFeePerGas,
1748
- }, provider));
1749
- 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;
1750
1840
  const sendERC20Token = async () => {
1751
1841
  assertCurrent();
1752
1842
  const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
1753
1843
  try {
1754
1844
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
1755
- // Preserve zero-decimal tokens: use provided decimals when defined (including 0).
1756
- const resolvedDecimals = decimals === undefined || decimals === null ? 18 : Number(decimals);
1757
- const calculatedTokenAmount = (0, ethers_v6_1.parseUnits)(tokenAmount, resolvedDecimals).toBigInt();
1758
1845
  let transferMethod;
1759
1846
  if (isLegacy) {
1760
1847
  const overrides = {
1761
1848
  nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
1762
1849
  gasPrice,
1763
- ...(gasLimit && { gasLimit }),
1850
+ gasLimit,
1764
1851
  type: 0, // Explicitly set Type 0 for legacy token transfers
1765
1852
  };
1766
- transferMethod = await _contract.transfer.populateTransaction(receiver, calculatedTokenAmount, normalizeEthersOverrides(overrides));
1853
+ assertCurrent();
1854
+ transferMethod = await _contract.transfer.populateTransaction(receiver, exactTokenAmount, normalizeEthersOverrides(overrides));
1767
1855
  }
1768
1856
  else {
1769
1857
  const overrides = {
1770
1858
  nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
1771
1859
  maxPriorityFeePerGas,
1772
1860
  maxFeePerGas,
1773
- ...(gasLimit && { gasLimit }),
1861
+ gasLimit,
1774
1862
  };
1775
- transferMethod = await _contract.transfer.populateTransaction(receiver, calculatedTokenAmount, normalizeEthersOverrides(overrides));
1863
+ assertCurrent();
1864
+ transferMethod = await _contract.transfer.populateTransaction(receiver, exactTokenAmount, normalizeEthersOverrides(overrides));
1776
1865
  }
1777
1866
  return await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, {
1778
1867
  ...transferMethod,
@@ -1791,20 +1880,16 @@ class EthereumTransactions {
1791
1880
  assertCurrent();
1792
1881
  try {
1793
1882
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
1794
- const resolvedDecimals = decimals === undefined || decimals === null ? 18 : Number(decimals);
1795
- const calculatedTokenAmount = (0, ethers_v6_1.parseUnits)(tokenAmount, resolvedDecimals).toBigInt();
1796
1883
  const txData = _contract.interface.encodeFunctionData('transfer', [
1797
1884
  receiver,
1798
- calculatedTokenAmount,
1885
+ exactTokenAmount,
1799
1886
  ]);
1800
- // Use fallback gas limit if not provided (for auto-estimation)
1801
- const effectiveGasLimit = gasLimit || this.toBigNumber('100000'); // ERC20 fallback
1802
1887
  let txFormattedForEthers;
1803
1888
  if (isLegacy) {
1804
1889
  txFormattedForEthers = {
1805
1890
  to: tokenAddress,
1806
1891
  value: '0x0',
1807
- gasLimit: effectiveGasLimit,
1892
+ gasLimit: gasLimit,
1808
1893
  gasPrice,
1809
1894
  data: txData,
1810
1895
  nonce: transactionNonce,
@@ -1816,7 +1901,7 @@ class EthereumTransactions {
1816
1901
  txFormattedForEthers = {
1817
1902
  to: tokenAddress,
1818
1903
  value: '0x0',
1819
- gasLimit: effectiveGasLimit,
1904
+ gasLimit: gasLimit,
1820
1905
  maxFeePerGas,
1821
1906
  maxPriorityFeePerGas,
1822
1907
  data: txData,
@@ -1830,7 +1915,7 @@ class EthereumTransactions {
1830
1915
  rawTx: rawTx.replace('0x', ''),
1831
1916
  accountIndex: activeAccountId,
1832
1917
  expectedAddress: activeAccount.address,
1833
- });
1918
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
1834
1919
  assertCurrent();
1835
1920
  const formattedSignature = {
1836
1921
  r: `0x${signature.r}`,
@@ -1842,7 +1927,7 @@ class EthereumTransactions {
1842
1927
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
1843
1928
  assertCurrent();
1844
1929
  assertHardwareTransactionSender(signedTx, activeAccount.address);
1845
- const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, async (raw) => {
1930
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async (raw) => {
1846
1931
  const response = await provider.sendTransaction(raw);
1847
1932
  saveTrezorTx && saveTrezorTx(response);
1848
1933
  return response;
@@ -1867,20 +1952,16 @@ class EthereumTransactions {
1867
1952
  assertCurrent();
1868
1953
  try {
1869
1954
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
1870
- const resolvedDecimals = decimals === undefined || decimals === null ? 18 : Number(decimals);
1871
- const calculatedTokenAmount = (0, ethers_v6_1.parseUnits)(tokenAmount, resolvedDecimals).toBigInt();
1872
1955
  const txData = _contract.interface.encodeFunctionData('transfer', [
1873
1956
  receiver,
1874
- calculatedTokenAmount,
1957
+ exactTokenAmount,
1875
1958
  ]);
1876
- // Use fallback gas limit if not provided (for auto-estimation)
1877
- const effectiveGasLimit = gasLimit || this.toBigNumber('100000'); // ERC20 fallback
1878
1959
  let txToBeSignedByTrezor;
1879
1960
  if (isLegacy) {
1880
1961
  txToBeSignedByTrezor = {
1881
1962
  to: tokenAddress,
1882
1963
  value: '0x0',
1883
- gasLimit: this.toHex0x(effectiveGasLimit, 'gasLimit'),
1964
+ gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
1884
1965
  gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
1885
1966
  nonce: this.toBigNumber(transactionNonce)._hex,
1886
1967
  chainId: activeNetwork.chainId,
@@ -1891,7 +1972,7 @@ class EthereumTransactions {
1891
1972
  txToBeSignedByTrezor = {
1892
1973
  to: tokenAddress,
1893
1974
  value: '0x0',
1894
- gasLimit: this.toHex0x(effectiveGasLimit, 'gasLimit'),
1975
+ gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
1895
1976
  maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
1896
1977
  maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
1897
1978
  nonce: this.toBigNumber(transactionNonce)._hex,
@@ -1909,7 +1990,7 @@ class EthereumTransactions {
1909
1990
  tx: txToBeSignedByTrezor,
1910
1991
  coin: activeNetwork.currency,
1911
1992
  slip44: activeNetwork.slip44,
1912
- });
1993
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
1913
1994
  assertCurrent();
1914
1995
  if (signature.success) {
1915
1996
  try {
@@ -1918,7 +1999,7 @@ class EthereumTransactions {
1918
1999
  txFormattedForEthers = {
1919
2000
  to: tokenAddress,
1920
2001
  value: '0x0',
1921
- gasLimit: effectiveGasLimit,
2002
+ gasLimit: gasLimit,
1922
2003
  gasPrice,
1923
2004
  data: txData,
1924
2005
  nonce: transactionNonce,
@@ -1930,7 +2011,7 @@ class EthereumTransactions {
1930
2011
  txFormattedForEthers = {
1931
2012
  to: tokenAddress,
1932
2013
  value: '0x0',
1933
- gasLimit: effectiveGasLimit,
2014
+ gasLimit: gasLimit,
1934
2015
  maxFeePerGas,
1935
2016
  maxPriorityFeePerGas,
1936
2017
  data: txData,
@@ -1942,7 +2023,7 @@ class EthereumTransactions {
1942
2023
  const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
1943
2024
  assertCurrent();
1944
2025
  assertHardwareTransactionSender(signedTx, activeAccount.address);
1945
- const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, async (raw) => {
2026
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async (raw) => {
1946
2027
  const response = await provider.sendTransaction(raw);
1947
2028
  saveTrezorTx && saveTrezorTx(response);
1948
2029
  return response;
@@ -1970,22 +2051,26 @@ class EthereumTransactions {
1970
2051
  return (await sendERC20Token());
1971
2052
  }
1972
2053
  };
1973
- this.sendSignedErc721Transaction = async ({ receiver, tokenAddress, tokenId, isLegacy, maxPriorityFeePerGas, maxFeePerGas, gasPrice, gasLimit, }) => {
1974
- 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');
1975
2068
  const activeAccountAddress = activeAccount.address;
1976
- const submission = this.captureSubmission('erc721', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertCurrent);
1977
- if (tokenId === undefined || tokenId === null) {
1978
- throw new Error('ERC721 tokenId is required');
1979
- }
1980
- ({ gasPrice, isLegacy, maxFeePerGas, maxPriorityFeePerGas } =
1981
- await this.resolveEvmFeeParams({
1982
- gasPrice,
1983
- isLegacy,
1984
- maxFeePerGas,
1985
- maxPriorityFeePerGas,
1986
- }, provider));
1987
- assertCurrent();
1988
- 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;
1989
2074
  const sendERC721Token = async () => {
1990
2075
  assertCurrent();
1991
2076
  const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
@@ -1996,7 +2081,7 @@ class EthereumTransactions {
1996
2081
  const overrides = {
1997
2082
  nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
1998
2083
  gasPrice,
1999
- ...(gasLimit && { gasLimit }),
2084
+ gasLimit,
2000
2085
  type: 0, // Explicitly set Type 0 for legacy NFT transfers
2001
2086
  };
2002
2087
  transferMethod = await _contract.transferFrom.populateTransaction(activeAccountAddress, receiver, normalizedTokenId, normalizeEthersOverrides(overrides));
@@ -2006,7 +2091,7 @@ class EthereumTransactions {
2006
2091
  nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
2007
2092
  maxPriorityFeePerGas,
2008
2093
  maxFeePerGas,
2009
- ...(gasLimit && { gasLimit }),
2094
+ gasLimit,
2010
2095
  };
2011
2096
  transferMethod = await _contract.transferFrom.populateTransaction(activeAccountAddress, receiver, normalizedTokenId, normalizeEthersOverrides(overrides));
2012
2097
  }
@@ -2032,14 +2117,12 @@ class EthereumTransactions {
2032
2117
  receiver,
2033
2118
  normalizedTokenId,
2034
2119
  ]);
2035
- // Use fallback gas limit if not provided (for auto-estimation)
2036
- const effectiveGasLimit = gasLimit || this.toBigNumber('150000'); // ERC721 fallback
2037
2120
  let txFormattedForEthers;
2038
2121
  if (isLegacy) {
2039
2122
  txFormattedForEthers = {
2040
2123
  to: tokenAddress,
2041
2124
  value: '0x0',
2042
- gasLimit: effectiveGasLimit,
2125
+ gasLimit,
2043
2126
  gasPrice,
2044
2127
  data: txData,
2045
2128
  nonce: transactionNonce,
@@ -2051,7 +2134,7 @@ class EthereumTransactions {
2051
2134
  txFormattedForEthers = {
2052
2135
  to: tokenAddress,
2053
2136
  value: '0x0',
2054
- gasLimit: effectiveGasLimit,
2137
+ gasLimit,
2055
2138
  maxFeePerGas,
2056
2139
  maxPriorityFeePerGas,
2057
2140
  data: txData,
@@ -2065,7 +2148,7 @@ class EthereumTransactions {
2065
2148
  rawTx: rawTx.replace('0x', ''),
2066
2149
  accountIndex: activeAccountId,
2067
2150
  expectedAddress: activeAccount.address,
2068
- });
2151
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2069
2152
  assertCurrent();
2070
2153
  const formattedSignature = {
2071
2154
  r: `0x${signature.r}`,
@@ -2077,7 +2160,7 @@ class EthereumTransactions {
2077
2160
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
2078
2161
  assertCurrent();
2079
2162
  assertHardwareTransactionSender(signedTx, activeAccount.address);
2080
- 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);
2081
2164
  return finalTx;
2082
2165
  }
2083
2166
  catch (error) {
@@ -2103,14 +2186,12 @@ class EthereumTransactions {
2103
2186
  receiver,
2104
2187
  normalizedTokenId,
2105
2188
  ]);
2106
- // Use fallback gas limit if not provided (for auto-estimation)
2107
- const effectiveGasLimit = gasLimit || this.toBigNumber('150000'); // ERC721 fallback
2108
2189
  let txToBeSignedByTrezor;
2109
2190
  if (isLegacy) {
2110
2191
  txToBeSignedByTrezor = {
2111
2192
  to: tokenAddress,
2112
2193
  value: '0x0',
2113
- gasLimit: this.toHex0x(effectiveGasLimit, 'gasLimit'),
2194
+ gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
2114
2195
  gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
2115
2196
  nonce: this.toBigNumber(transactionNonce)._hex,
2116
2197
  chainId: activeNetwork.chainId,
@@ -2122,7 +2203,7 @@ class EthereumTransactions {
2122
2203
  txToBeSignedByTrezor = {
2123
2204
  to: tokenAddress,
2124
2205
  value: '0x0',
2125
- gasLimit: this.toHex0x(effectiveGasLimit, 'gasLimit'),
2206
+ gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
2126
2207
  maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
2127
2208
  maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
2128
2209
  nonce: this.toBigNumber(transactionNonce)._hex,
@@ -2142,7 +2223,7 @@ class EthereumTransactions {
2142
2223
  tx: txToBeSignedByTrezor,
2143
2224
  coin: trezorCoin,
2144
2225
  slip44: activeNetwork.slip44,
2145
- });
2226
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2146
2227
  assertCurrent();
2147
2228
  if (signature.success) {
2148
2229
  try {
@@ -2151,7 +2232,7 @@ class EthereumTransactions {
2151
2232
  txFormattedForEthers = {
2152
2233
  to: tokenAddress,
2153
2234
  value: '0x0',
2154
- gasLimit: effectiveGasLimit,
2235
+ gasLimit,
2155
2236
  gasPrice,
2156
2237
  data: txData,
2157
2238
  nonce: transactionNonce,
@@ -2163,7 +2244,7 @@ class EthereumTransactions {
2163
2244
  txFormattedForEthers = {
2164
2245
  to: tokenAddress,
2165
2246
  value: '0x0',
2166
- gasLimit: effectiveGasLimit,
2247
+ gasLimit,
2167
2248
  maxFeePerGas,
2168
2249
  maxPriorityFeePerGas,
2169
2250
  data: txData,
@@ -2175,7 +2256,7 @@ class EthereumTransactions {
2175
2256
  const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
2176
2257
  assertCurrent();
2177
2258
  assertHardwareTransactionSender(signedTx, activeAccount.address);
2178
- 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);
2179
2260
  return finalTx;
2180
2261
  }
2181
2262
  catch (error) {
@@ -2201,18 +2282,29 @@ class EthereumTransactions {
2201
2282
  return (await sendERC721Token());
2202
2283
  }
2203
2284
  };
2204
- this.sendSignedErc1155Transaction = async ({ receiver, tokenAddress, tokenId, tokenAmount, isLegacy, maxPriorityFeePerGas, maxFeePerGas, gasPrice, gasLimit, }) => {
2205
- 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');
2206
2302
  const activeAccountAddress = activeAccount.address;
2207
- const submission = this.captureSubmission('erc1155', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertCurrent);
2208
- ({ gasPrice, isLegacy, maxFeePerGas, maxPriorityFeePerGas } =
2209
- await this.resolveEvmFeeParams({
2210
- gasPrice,
2211
- isLegacy,
2212
- maxFeePerGas,
2213
- maxPriorityFeePerGas,
2214
- }, provider));
2215
- assertCurrent();
2303
+ const submission = this.captureSubmission('erc1155', activeAccount, activeAccountType, activeAccountId, activeNetwork, () => {
2304
+ assertApproval();
2305
+ assertSigningCurrent();
2306
+ });
2307
+ const assertCurrent = submission.assertCurrent;
2216
2308
  const sendERC1155Token = async () => {
2217
2309
  assertCurrent();
2218
2310
  const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
@@ -2220,13 +2312,12 @@ class EthereumTransactions {
2220
2312
  try {
2221
2313
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
2222
2314
  // Use BigNumber to avoid JS number overflow/precision loss
2223
- const amount = ethers_v6_1.BigNumber.from(tokenAmount ?? '1');
2224
2315
  let overrides;
2225
2316
  if (isLegacy) {
2226
2317
  overrides = {
2227
2318
  nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
2228
2319
  gasPrice,
2229
- ...(gasLimit && { gasLimit }),
2320
+ gasLimit,
2230
2321
  type: 0, // Explicitly set Type 0 for legacy ERC1155 transfers
2231
2322
  };
2232
2323
  }
@@ -2235,10 +2326,10 @@ class EthereumTransactions {
2235
2326
  nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
2236
2327
  maxPriorityFeePerGas,
2237
2328
  maxFeePerGas,
2238
- ...(gasLimit && { gasLimit }),
2329
+ gasLimit,
2239
2330
  };
2240
2331
  }
2241
- 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));
2242
2333
  return await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, {
2243
2334
  ...transferMethod,
2244
2335
  chainId: activeNetwork.chainId,
@@ -2256,16 +2347,13 @@ class EthereumTransactions {
2256
2347
  assertCurrent();
2257
2348
  try {
2258
2349
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
2259
- const amount = ethers_v6_1.BigNumber.from(tokenAmount ?? '1');
2260
- const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, tokenId, amount.toBigInt(), '0x']);
2261
- // Use fallback gas limit if not provided (for auto-estimation)
2262
- const effectiveGasLimit = gasLimit || this.toBigNumber('200000'); // ERC1155 fallback
2350
+ const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, normalizedTokenId, amount, '0x']);
2263
2351
  let txFormattedForEthers;
2264
2352
  if (isLegacy) {
2265
2353
  txFormattedForEthers = {
2266
2354
  to: tokenAddress,
2267
2355
  value: '0x0',
2268
- gasLimit: effectiveGasLimit,
2356
+ gasLimit,
2269
2357
  gasPrice,
2270
2358
  data: txData,
2271
2359
  nonce: transactionNonce,
@@ -2277,7 +2365,7 @@ class EthereumTransactions {
2277
2365
  txFormattedForEthers = {
2278
2366
  to: tokenAddress,
2279
2367
  value: '0x0',
2280
- gasLimit: effectiveGasLimit,
2368
+ gasLimit,
2281
2369
  maxFeePerGas,
2282
2370
  maxPriorityFeePerGas,
2283
2371
  data: txData,
@@ -2291,7 +2379,7 @@ class EthereumTransactions {
2291
2379
  rawTx: rawTx.replace('0x', ''),
2292
2380
  accountIndex: activeAccountId,
2293
2381
  expectedAddress: activeAccount.address,
2294
- });
2382
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2295
2383
  assertCurrent();
2296
2384
  const formattedSignature = {
2297
2385
  r: `0x${signature.r}`,
@@ -2303,7 +2391,7 @@ class EthereumTransactions {
2303
2391
  const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
2304
2392
  assertCurrent();
2305
2393
  assertHardwareTransactionSender(signedTx, activeAccount.address);
2306
- 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);
2307
2395
  return finalTx;
2308
2396
  }
2309
2397
  catch (error) {
@@ -2324,16 +2412,13 @@ class EthereumTransactions {
2324
2412
  assertCurrent();
2325
2413
  try {
2326
2414
  const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
2327
- const amount = ethers_v6_1.BigNumber.from(tokenAmount ?? '1');
2328
- const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, tokenId, amount.toBigInt(), '0x']);
2329
- // Use fallback gas limit if not provided (for auto-estimation)
2330
- const effectiveGasLimit = gasLimit || this.toBigNumber('200000'); // ERC1155 fallback
2415
+ const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, normalizedTokenId, amount, '0x']);
2331
2416
  let txToBeSignedByTrezor;
2332
2417
  if (isLegacy) {
2333
2418
  txToBeSignedByTrezor = {
2334
2419
  to: tokenAddress,
2335
2420
  value: '0x0',
2336
- gasLimit: this.toHex0x(effectiveGasLimit, 'gasLimit'),
2421
+ gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
2337
2422
  gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
2338
2423
  nonce: this.toBigNumber(transactionNonce)._hex,
2339
2424
  chainId: activeNetwork.chainId,
@@ -2344,7 +2429,7 @@ class EthereumTransactions {
2344
2429
  txToBeSignedByTrezor = {
2345
2430
  to: tokenAddress,
2346
2431
  value: '0x0',
2347
- gasLimit: this.toHex0x(effectiveGasLimit, 'gasLimit'),
2432
+ gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
2348
2433
  maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
2349
2434
  maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
2350
2435
  nonce: this.toBigNumber(transactionNonce)._hex,
@@ -2362,7 +2447,7 @@ class EthereumTransactions {
2362
2447
  tx: txToBeSignedByTrezor,
2363
2448
  coin: activeNetwork.currency,
2364
2449
  slip44: activeNetwork.slip44,
2365
- });
2450
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2366
2451
  assertCurrent();
2367
2452
  if (signature.success) {
2368
2453
  try {
@@ -2371,7 +2456,7 @@ class EthereumTransactions {
2371
2456
  txFormattedForEthers = {
2372
2457
  to: tokenAddress,
2373
2458
  value: '0x0',
2374
- gasLimit: effectiveGasLimit,
2459
+ gasLimit,
2375
2460
  gasPrice,
2376
2461
  data: txData,
2377
2462
  nonce: transactionNonce,
@@ -2383,7 +2468,7 @@ class EthereumTransactions {
2383
2468
  txFormattedForEthers = {
2384
2469
  to: tokenAddress,
2385
2470
  value: '0x0',
2386
- gasLimit: effectiveGasLimit,
2471
+ gasLimit,
2387
2472
  maxFeePerGas,
2388
2473
  maxPriorityFeePerGas,
2389
2474
  data: txData,
@@ -2395,7 +2480,7 @@ class EthereumTransactions {
2395
2480
  const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
2396
2481
  assertCurrent();
2397
2482
  assertHardwareTransactionSender(signedTx, activeAccount.address);
2398
- 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);
2399
2484
  return finalTx;
2400
2485
  }
2401
2486
  catch (error) {
@@ -2595,6 +2680,11 @@ class EthereumTransactions {
2595
2680
  maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
2596
2681
  };
2597
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');
2598
2688
  this.getNetwork = getNetwork;
2599
2689
  this.getDecryptedPrivateKey = (targetAccount) => {
2600
2690
  this.assertHardwareSignerAvailable(targetAccount);
@@ -2621,11 +2711,13 @@ class EthereumTransactions {
2621
2711
  // RPC/network than `activeNetwork`, nodes will reject broadcasts with chain-id mismatch errors.
2622
2712
  try {
2623
2713
  const currentNetwork = this.getNetwork();
2624
- const currentUrl = currentNetwork?.url;
2625
- const currentKey = `${currentNetwork?.chainId ?? 'unknown'}|${currentUrl ?? ''}`;
2626
- const needsInit = !this._web3Provider ||
2627
- !this._web3ProviderKey ||
2628
- (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;
2629
2721
  if (needsInit) {
2630
2722
  this.setWeb3Provider(currentNetwork);
2631
2723
  }
@@ -2636,31 +2728,16 @@ class EthereumTransactions {
2636
2728
  console.log('[EthereumTransactions] Deferring provider initialization:', error?.message || error);
2637
2729
  }
2638
2730
  }
2639
- // Helper method to detect UTXO networks
2640
- isUtxoNetwork(network) {
2641
- // Generic UTXO network detection patterns:
2642
- // 1. URL contains blockbook or trezor (most reliable)
2643
- // 2. Network kind is explicitly set to 'syscoin'
2644
- const hasBlockbookUrl = !!(network.url?.includes('blockbook') || network.url?.includes('trezor'));
2645
- const hasUtxoKind = network.kind === sysweb3_network_1.INetworkType.Syscoin;
2646
- return hasBlockbookUrl || hasUtxoKind;
2647
- }
2648
2731
  setWeb3Provider(network) {
2649
2732
  this.abortController.abort();
2650
2733
  this.abortController = new AbortController();
2651
- // Check if network is a UTXO network to avoid creating web3 providers for blockbook URLs
2652
- const isUtxoNetwork = this.isUtxoNetwork(network);
2653
- if (isUtxoNetwork) {
2654
- // For UTXO networks, don't create web3 providers at all since they won't be used
2655
- console.log('[EthereumTransactions] setWeb3Provider: Skipping web3Provider creation for UTXO network:', network.url);
2656
- // Clear any existing providers for UTXO networks
2734
+ if (network?.kind !== sysweb3_network_1.INetworkType.Ethereum) {
2657
2735
  this._web3Provider = undefined;
2658
2736
  this._web3ProviderKey = undefined;
2737
+ return;
2659
2738
  }
2660
- else {
2661
- this._web3Provider = new providers_1.CustomJsonRpcProvider(this.abortController.signal, network.url, network.chainId);
2662
- this._web3ProviderKey = `${network.chainId}|${network.url ?? ''}`;
2663
- }
2739
+ this._web3Provider = new providers_1.CustomJsonRpcProvider(this.abortController.signal, network.url, network.chainId);
2740
+ this._web3ProviderKey = `${network.chainId}|${network.url ?? ''}`;
2664
2741
  }
2665
2742
  // Method to configure gas overrides for networks with validation issues
2666
2743
  setGasOverrides(overrides) {