@sidhujag/sysweb3-keyring 2.1.7 → 2.1.8

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 (95) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/cjs/hardware-transaction-authority.js +15 -11
  3. package/cjs/hardware-transaction-authority.js.map +1 -1
  4. package/cjs/hardware-wallet-manager.js +74 -119
  5. package/cjs/hardware-wallet-manager.js.map +1 -1
  6. package/cjs/hardware-wallet-types.js +16 -0
  7. package/cjs/hardware-wallet-types.js.map +1 -0
  8. package/cjs/host-keyring.js +32 -0
  9. package/cjs/host-keyring.js.map +1 -0
  10. package/cjs/index.js +1 -0
  11. package/cjs/index.js.map +1 -1
  12. package/cjs/keyring-core.js +96 -61
  13. package/cjs/keyring-core.js.map +1 -1
  14. package/cjs/keyring-manager.js +15 -16
  15. package/cjs/keyring-manager.js.map +1 -1
  16. package/cjs/ledger/host-types.js.map +1 -1
  17. package/cjs/ledger/index.js +24 -4
  18. package/cjs/ledger/index.js.map +1 -1
  19. package/cjs/ledger-hardware.js +15 -0
  20. package/cjs/ledger-hardware.js.map +1 -0
  21. package/cjs/software.js +6 -25
  22. package/cjs/software.js.map +1 -1
  23. package/cjs/transactions/ethereum.js +317 -268
  24. package/cjs/transactions/ethereum.js.map +1 -1
  25. package/cjs/transactions/evm-submission.js +32 -4
  26. package/cjs/transactions/evm-submission.js.map +1 -1
  27. package/cjs/transactions/ledger-signing.js +44 -0
  28. package/cjs/transactions/ledger-signing.js.map +1 -0
  29. package/cjs/transactions/prepared-evm.js +24 -1
  30. package/cjs/transactions/prepared-evm.js.map +1 -1
  31. package/cjs/trezor/connect.js +238 -243
  32. package/cjs/trezor/connect.js.map +1 -1
  33. package/cjs/trezor/host-types.js +19 -0
  34. package/cjs/trezor/host-types.js.map +1 -0
  35. package/cjs/trezor/index.js +75 -23
  36. package/cjs/trezor/index.js.map +1 -1
  37. package/cjs/trezor/webextension-host.js +118 -0
  38. package/cjs/trezor/webextension-host.js.map +1 -0
  39. package/cjs/trezor-hardware.js +16 -0
  40. package/cjs/trezor-hardware.js.map +1 -0
  41. package/cjs/types.js.map +1 -1
  42. package/esm/hardware-transaction-authority.js +14 -11
  43. package/esm/hardware-transaction-authority.js.map +1 -1
  44. package/esm/hardware-wallet-manager.js +20 -32
  45. package/esm/hardware-wallet-manager.js.map +1 -1
  46. package/esm/hardware-wallet-types.js +13 -0
  47. package/esm/hardware-wallet-types.js.map +1 -0
  48. package/esm/keyring-core.js +98 -63
  49. package/esm/keyring-core.js.map +1 -1
  50. package/esm/ledger/dmk-host.js +2 -0
  51. package/esm/ledger/dmk-host.js.map +1 -1
  52. package/esm/ledger/dmk-session.js +73 -8
  53. package/esm/ledger/dmk-session.js.map +1 -1
  54. package/esm/ledger/host-types.js.map +1 -1
  55. package/esm/ledger/index.js +22 -2
  56. package/esm/ledger/index.js.map +1 -1
  57. package/esm/transactions/ethereum.js +318 -269
  58. package/esm/transactions/ethereum.js.map +1 -1
  59. package/esm/transactions/evm-submission.js +32 -4
  60. package/esm/transactions/evm-submission.js.map +1 -1
  61. package/esm/transactions/ledger-signing.js +41 -0
  62. package/esm/transactions/ledger-signing.js.map +1 -0
  63. package/esm/transactions/prepared-evm.js +23 -1
  64. package/esm/transactions/prepared-evm.js.map +1 -1
  65. package/esm/trezor/connect.js +236 -209
  66. package/esm/trezor/connect.js.map +1 -1
  67. package/esm/trezor/host-types.js +16 -0
  68. package/esm/trezor/host-types.js.map +1 -0
  69. package/esm/trezor/index.js +74 -22
  70. package/esm/trezor/index.js.map +1 -1
  71. package/esm/types.js.map +1 -1
  72. package/package.json +55 -1
  73. package/types/hardware-transaction-authority.d.ts +1 -0
  74. package/types/hardware-wallet-manager.d.ts +6 -22
  75. package/types/hardware-wallet-types.d.ts +21 -0
  76. package/types/host-keyring.d.ts +16 -0
  77. package/types/index.d.ts +1 -0
  78. package/types/keyring-core.d.ts +12 -4
  79. package/types/keyring-manager.d.ts +5 -3
  80. package/types/ledger/dmk-host.d.ts +2 -0
  81. package/types/ledger/dmk-session.d.ts +9 -0
  82. package/types/ledger/host-types.d.ts +2 -0
  83. package/types/ledger/index.d.ts +3 -1
  84. package/types/ledger-hardware.d.ts +5 -0
  85. package/types/software.d.ts +2 -16
  86. package/types/transactions/ethereum.d.ts +11 -1
  87. package/types/transactions/evm-submission.d.ts +7 -2
  88. package/types/transactions/ledger-signing.d.ts +13 -0
  89. package/types/transactions/prepared-evm.d.ts +12 -0
  90. package/types/trezor/connect.d.ts +11 -9
  91. package/types/trezor/host-types.d.ts +25 -0
  92. package/types/trezor/index.d.ts +7 -7
  93. package/types/trezor/webextension-host.d.ts +7 -0
  94. package/types/trezor-hardware.d.ts +6 -0
  95. package/types/types.d.ts +16 -4
@@ -1,6 +1,7 @@
1
1
  import { decrypt, signTypedData as signTypedDataUtil, getEncryptionPublicKey, recoverPersonalSignature, recoverTypedSignature, } from '@metamask/eth-sig-util';
2
2
  import { INetworkType } from '@sidhujag/sysweb3-network';
3
3
  import { createContractUsingAbi, getErc20Abi, getErc21Abi, getErc55Abi, } from '@sidhujag/sysweb3-utils';
4
+ import { getAddress } from 'ethers/address';
4
5
  import { accessListify, Transaction } from 'ethers/transaction';
5
6
  import { hexlify } from 'ethers/utils';
6
7
  import omit from 'lodash/omit';
@@ -9,9 +10,11 @@ import { assertEvmGasLimit, bufferedEvmGasLimit, EvmGasLimitError, getEvmFeeData
9
10
  import { CustomJsonRpcProvider, wrapTransactionResponse } from '../providers';
10
11
  import { KeyringAccountType, } from '../types';
11
12
  import { assertEvmCancellationEnvelope, authenticateRetainedCancellation, } from './evm-cancellation';
13
+ import { readEvmOwnData, serializePreparedEvmTransaction, snapshotPreparedEvmTransaction, } from './evm-envelope';
12
14
  import { deriveEvmAccountFromMnemonic, parsePersonalMessage as parseLocalPersonalMessage, privateKeyToAccount, normalizeGasLimitedTransaction, sendLocalEvmTransaction, signDigestHex, signPersonalMessage as signLocalPersonalMessage, } from './evm-local-signer';
13
15
  import { captureEvmSubmission, submitSignedEvmTransaction, submitSignedHardwareEvmTransaction, evmSubmissionFailure, copyEvmSubmissionFailure, } from './evm-submission';
14
- import { signPreparedEvmEnvelope, snapshotPreparedEvmRequest, } from './prepared-evm';
16
+ import { signLedgerEvmTransaction } from './ledger-signing';
17
+ import { signPreparedEvmEnvelope, snapshotPreparedEvmRequest, snapshotEvmSigningIdentity, preparedEvmGuard, } from './prepared-evm';
15
18
  import { getAddressDerivationPath } from '../utils/derivation-paths';
16
19
  const hardwareSignerUnavailable = (hardware) => Object.assign(new Error(`${hardware} signer is unavailable in this host`), {
17
20
  code: 'HARDWARE_SIGNER_UNAVAILABLE',
@@ -154,7 +157,7 @@ export class EthereumTransactions {
154
157
  }
155
158
  return () => assertSynchronousOriginalValidation(guard(), 'Signing context guard must be synchronous');
156
159
  };
157
- this.captureEvmSigningContext = (target) => {
160
+ this.captureEvmAccountContext = (target) => {
158
161
  this.assertHardwareSignerAvailable(target);
159
162
  const state = this.getState();
160
163
  const selectedType = state.activeAccountType;
@@ -168,7 +171,6 @@ export class EthereumTransactions {
168
171
  const account = { address: selectedAccount.address };
169
172
  const network = { ...state.activeNetwork };
170
173
  const assertSession = this.captureSessionGuard();
171
- const provider = this.web3Provider;
172
174
  const assertCurrent = () => {
173
175
  assertSession();
174
176
  const current = this.getState();
@@ -182,8 +184,7 @@ export class EthereumTransactions {
182
184
  [current.activeNetwork, this.getNetwork()].some((selected) => selected.kind !== network.kind ||
183
185
  selected.chainId !== network.chainId ||
184
186
  selected.slip44 !== network.slip44 ||
185
- selected.url !== network.url) ||
186
- this.web3Provider !== provider)
187
+ selected.url !== network.url))
187
188
  throw new Error('Wallet signing context changed');
188
189
  };
189
190
  assertCurrent();
@@ -192,10 +193,20 @@ export class EthereumTransactions {
192
193
  activeAccountType,
193
194
  activeAccountId,
194
195
  activeNetwork: network,
195
- provider,
196
196
  assertCurrent,
197
197
  };
198
198
  };
199
+ this.captureEvmSigningContext = (target) => {
200
+ const context = this.captureEvmAccountContext(target);
201
+ const provider = this.web3Provider;
202
+ const assertCurrent = () => {
203
+ context.assertCurrent();
204
+ if (this.web3Provider !== provider)
205
+ throw new Error('Wallet signing context changed');
206
+ };
207
+ assertCurrent();
208
+ return { ...context, provider, assertCurrent };
209
+ };
199
210
  this.captureSubmission = (operation, account, type, id, network, assertCurrent, details = {}) => {
200
211
  if (!this.captureSubmissionLifecycle)
201
212
  throw new TypeError('A durable EVM submission lifecycle is required');
@@ -296,10 +307,51 @@ export class EthereumTransactions {
296
307
  throw error;
297
308
  }
298
309
  };
299
- this.ethSign = async (params) => {
300
- const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
301
- const { accounts, activeAccountId, activeAccountType, activeNetwork } = this.getState();
302
- const activeAccount = accounts[activeAccountType][activeAccountId];
310
+ this.ethSign = async (params, input) => {
311
+ const selected = this.captureEvmAccountContext();
312
+ if (!Array.isArray(params) || params.length !== 2)
313
+ throw new Error('Invalid digest request');
314
+ const first = Object.getOwnPropertyDescriptor(params, '0');
315
+ const second = Object.getOwnPropertyDescriptor(params, '1');
316
+ if (!first ||
317
+ !second ||
318
+ !('value' in first) ||
319
+ !('value' in second) ||
320
+ typeof first.value !== 'string' ||
321
+ typeof second.value !== 'string')
322
+ throw new Error('Invalid digest request');
323
+ params = [first.value, second.value];
324
+ const options = readEvmOwnData(input, [
325
+ 'selectedAccount',
326
+ 'targetAccount',
327
+ 'assertCurrent',
328
+ ]);
329
+ const selectedAccount = snapshotEvmSigningIdentity(options.selectedAccount);
330
+ const targetAccount = snapshotEvmSigningIdentity(options.targetAccount);
331
+ const hostGuard = preparedEvmGuard({
332
+ assertCurrent: options.assertCurrent,
333
+ });
334
+ const target = {
335
+ id: targetAccount.id,
336
+ type: targetAccount.type,
337
+ };
338
+ const context = this.captureEvmAccountContext(target);
339
+ const assertCurrent = () => {
340
+ hostGuard();
341
+ selected.assertCurrent();
342
+ context.assertCurrent();
343
+ if (selected.activeAccountId !== selectedAccount.id ||
344
+ selected.activeAccountType !== selectedAccount.type ||
345
+ getAddress(selected.activeAccount.address) !==
346
+ selectedAccount.expectedAddress ||
347
+ getAddress(context.activeAccount.address) !==
348
+ targetAccount.expectedAddress ||
349
+ targetAccount.type === 'SmartAccount')
350
+ throw new Error('EVM digest signing context changed');
351
+ };
352
+ assertCurrent();
353
+ const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey(target);
354
+ const { activeAccountId, activeAccountType, activeNetwork, activeAccount } = context;
303
355
  // Validate that the derived address matches the active account to prevent race conditions
304
356
  if (address.toLowerCase() !== activeAccount.address.toLowerCase()) {
305
357
  throw {
@@ -368,14 +420,19 @@ export class EthereumTransactions {
368
420
  throw error;
369
421
  }
370
422
  };
423
+ let signature;
371
424
  switch (activeAccountType) {
372
425
  case KeyringAccountType.Trezor:
373
- return await signWithTrezor();
426
+ signature = await signWithTrezor();
427
+ break;
374
428
  case KeyringAccountType.Ledger:
375
- return await signWithLedger();
429
+ signature = await signWithLedger();
430
+ break;
376
431
  default:
377
- return sign();
432
+ signature = sign();
378
433
  }
434
+ assertCurrent();
435
+ return signature;
379
436
  };
380
437
  this.signPersonalMessage = async (params) => {
381
438
  const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
@@ -945,33 +1002,18 @@ export class EthereumTransactions {
945
1002
  const rawTx = serializeTransaction(txFormattedForEthers);
946
1003
  if (retained)
947
1004
  assertCancellationEnvelope(rawTx);
948
- const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
949
- rawTx: rawTx.replace('0x', ''),
1005
+ const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
1006
+ unsignedTransaction: rawTx,
950
1007
  accountIndex: activeAccountId,
951
1008
  expectedAddress: activeAccount.address,
952
1009
  }, await this.authorizeHardwareTransaction(provider, assertCurrent));
953
1010
  assertCurrent();
954
- const formattedSignature = {
955
- r: `0x${signature.r}`,
956
- s: `0x${signature.s}`,
957
- v: parseInt(signature.v, 16),
1011
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
1012
+ const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
1013
+ return {
1014
+ isCanceled: true,
1015
+ transaction: transactionResponse,
958
1016
  };
959
- if (signature) {
960
- const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
961
- assertCurrent();
962
- assertHardwareTransactionSender(signedTx, activeAccount.address);
963
- const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
964
- return {
965
- isCanceled: true,
966
- transaction: transactionResponse,
967
- };
968
- }
969
- else {
970
- return {
971
- isCanceled: false,
972
- error: true,
973
- };
974
- }
975
1017
  }
976
1018
  catch (error) {
977
1019
  return {
@@ -1102,12 +1144,21 @@ export class EthereumTransactions {
1102
1144
  typeof options !== 'object' ||
1103
1145
  Array.isArray(options) ||
1104
1146
  (options.mode !== 'reviewed-native' &&
1147
+ options.mode !== 'reviewed-generic' &&
1105
1148
  options.mode !== 'specialized') ||
1106
1149
  typeof options.assertCurrent !== 'function')
1107
1150
  throw new Error('Explicit transaction approval is required');
1108
1151
  // Capture once; later caller mutations cannot replace this invocation's
1109
1152
  // admission decision while chain checks or device approval are pending.
1110
1153
  const { assertCurrent: assertHostCurrent, mode, walletNonce } = options;
1154
+ const reviewId = mode === 'reviewed-generic' ? options.reviewId : undefined;
1155
+ if (mode === 'reviewed-generic' &&
1156
+ (typeof reviewId !== 'string' ||
1157
+ !/^[A-Za-z0-9_-]{1,128}$/.test(reviewId) ||
1158
+ walletNonce === undefined))
1159
+ throw new Error('An issued fee review and approved nonce are required');
1160
+ const selectedAccount = snapshotEvmSigningIdentity(options.selectedAccount);
1161
+ const targetAccount = snapshotEvmSigningIdentity(options.targetAccount);
1111
1162
  const nonceApproval = walletNonce === undefined ? undefined : { ...walletNonce };
1112
1163
  const assertApproved = () => {
1113
1164
  const result = assertHostCurrent();
@@ -1119,14 +1170,14 @@ export class EthereumTransactions {
1119
1170
  assertApproved();
1120
1171
  return await this.sendFormattedTransactionInternal(params, isLegacy, () => {
1121
1172
  broadcastAttempted = true;
1122
- }, assertApproved, mode, nonceApproval);
1173
+ }, assertApproved, mode, nonceApproval, selectedAccount, targetAccount, reviewId);
1123
1174
  }
1124
1175
  catch (error) {
1125
1176
  // A broadcast transport error may mean the node accepted the signed
1126
1177
  // transaction. Derive this marker from this invocation, never RPC data.
1127
1178
  const submissionError = Object.assign(new Error(error instanceof Error ? error.message : String(error)), error, {
1128
1179
  transactionNotBroadcast: !broadcastAttempted ||
1129
- evmSubmissionFailure(error).transactionNotBroadcast,
1180
+ evmSubmissionFailure(error)?.transactionNotBroadcast === true,
1130
1181
  });
1131
1182
  if (error instanceof Error) {
1132
1183
  submissionError.name = error.name;
@@ -1136,19 +1187,60 @@ export class EthereumTransactions {
1136
1187
  throw submissionError;
1137
1188
  }
1138
1189
  };
1139
- this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode, walletNonce) => {
1190
+ this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode, walletNonce, selectedIdentity, targetIdentity, reviewId) => {
1140
1191
  // Hardware approval may outlive the selected account, network or unlocked
1141
1192
  // session. Keep immutable identities and one provider for this invocation.
1142
- const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertKeyringCurrent, } = this.captureEvmSigningContext();
1193
+ const selectedContext = this.captureEvmAccountContext();
1194
+ const targetAccount = {
1195
+ id: targetIdentity.id,
1196
+ type: targetIdentity.type,
1197
+ };
1198
+ if (targetAccount.type === KeyringAccountType.SmartAccount)
1199
+ throw new Error('Invalid EVM payer');
1200
+ const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertKeyringCurrent, } = this.captureEvmSigningContext(targetAccount);
1143
1201
  const assertSigningCurrent = () => {
1144
1202
  assertHostCurrent();
1203
+ selectedContext.assertCurrent();
1145
1204
  assertKeyringCurrent();
1205
+ if (selectedContext.activeAccountId !== selectedIdentity.id ||
1206
+ selectedContext.activeAccountType !== selectedIdentity.type ||
1207
+ getAddress(selectedContext.activeAccount.address) !==
1208
+ selectedIdentity.expectedAddress ||
1209
+ getAddress(activeAccount.address) !== targetIdentity.expectedAddress)
1210
+ throw new Error('EVM payer approval context changed');
1146
1211
  };
1212
+ assertSigningCurrent();
1213
+ // Generic reviewed calls keep the exact prepared envelope, including
1214
+ // contract creation, calldata and access lists, through existing signers.
1215
+ const reviewed = mode === 'reviewed-generic'
1216
+ ? snapshotPreparedEvmTransaction(input)
1217
+ : undefined;
1218
+ if (reviewed) {
1219
+ if (reviewed.from !== targetIdentity.expectedAddress ||
1220
+ reviewed.chainId !== String(activeNetwork.chainId) ||
1221
+ reviewed.nonce !== String(walletNonce?.nonce) ||
1222
+ (isLegacy !== undefined && isLegacy !== (reviewed.type === 0)) ||
1223
+ (this.gasOverrides.minGasLimit &&
1224
+ BigInt(reviewed.gasLimit) < this.gasOverrides.minGasLimit.toBigInt()))
1225
+ throw new Error('Reviewed EVM transaction does not match its approval');
1226
+ input = reviewed;
1227
+ isLegacy = reviewed.type === 0;
1228
+ }
1147
1229
  const assertBeforeBroadcast = () => {
1148
1230
  assertCurrent();
1149
1231
  beforeBroadcast();
1150
1232
  };
1151
- const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, { walletNonce });
1233
+ const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, {
1234
+ walletNonce,
1235
+ ...(reviewed
1236
+ ? {
1237
+ review: {
1238
+ id: reviewId,
1239
+ unsignedHash: Transaction.from(serializePreparedEvmTransaction(reviewed)).unsignedHash,
1240
+ },
1241
+ }
1242
+ : {}),
1243
+ });
1152
1244
  const assertCurrent = submission.assertCurrent;
1153
1245
  const approvedNonce = submission.context.walletNonce?.nonce;
1154
1246
  const assertApprovedNonce = (nonce) => {
@@ -1157,6 +1249,7 @@ export class EthereumTransactions {
1157
1249
  };
1158
1250
  if (mode === 'reviewed-native') {
1159
1251
  const { transaction } = snapshotPreparedEvmRequest({
1252
+ selectedAccount: selectedIdentity,
1160
1253
  account: {
1161
1254
  id: activeAccountId,
1162
1255
  type: activeAccountType,
@@ -1187,7 +1280,7 @@ export class EthereumTransactions {
1187
1280
  assertCurrent();
1188
1281
  if (this.gasOverrides.minGasLimit !== minimum)
1189
1282
  throw new Error('Reviewed native gas policy changed');
1190
- const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
1283
+ const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey(targetAccount);
1191
1284
  if (address.toLowerCase() !== transaction.from.toLowerCase())
1192
1285
  throw new Error('Reviewed native signer does not match the approved account');
1193
1286
  assertCurrent();
@@ -1200,6 +1293,9 @@ export class EthereumTransactions {
1200
1293
  if (!params || typeof params !== 'object' || Array.isArray(params))
1201
1294
  throw new TypeError('Invalid EVM transaction request');
1202
1295
  const supplied = { ...params };
1296
+ if (supplied.from !== undefined &&
1297
+ getAddress(supplied.from) !== targetIdentity.expectedAddress)
1298
+ throw new Error('EVM transaction sender does not match the approved payer');
1203
1299
  // Validate original scalar spellings before conversion. Normalization's
1204
1300
  // permissive zero coercion must not bypass the bounded wire parser.
1205
1301
  for (const field of [
@@ -1252,7 +1348,8 @@ export class EthereumTransactions {
1252
1348
  const validateEip1559Fees = () => {
1253
1349
  const maximum = nonNegativeFeeQuantity(params.maxFeePerGas, 'maximum fee');
1254
1350
  const priority = nonNegativeFeeQuantity(params.maxPriorityFeePerGas, 'priority fee');
1255
- if (maximum.isZero() || priority.gt(maximum))
1351
+ if ((mode !== 'reviewed-generic' && maximum.isZero()) ||
1352
+ priority.gt(maximum))
1256
1353
  throw new Error('Invalid EIP-1559 fees');
1257
1354
  params.maxFeePerGas = maximum;
1258
1355
  params.maxPriorityFeePerGas = priority;
@@ -1263,7 +1360,9 @@ export class EthereumTransactions {
1263
1360
  validateEip1559Fees();
1264
1361
  // Only apply a minimum gas limit when a network-specific override is set.
1265
1362
  // Otherwise keep the user/dapp-approved gas limit unchanged.
1266
- if (params.gasLimit != null && this.gasOverrides.minGasLimit) {
1363
+ if (mode !== 'reviewed-generic' &&
1364
+ params.gasLimit != null &&
1365
+ this.gasOverrides.minGasLimit) {
1267
1366
  const minGasLimit = this.gasOverrides.minGasLimit;
1268
1367
  const currentGasLimit = BigNumber.from(params.gasLimit);
1269
1368
  if (currentGasLimit.lt(minGasLimit)) {
@@ -1334,31 +1433,15 @@ export class EthereumTransactions {
1334
1433
  type: 2, // Need explicit type for hardware wallet serialization
1335
1434
  };
1336
1435
  const rawTx = serializeTransaction(txFormattedForEthers);
1337
- const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
1338
- rawTx: rawTx.replace('0x', ''),
1436
+ const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
1437
+ unsignedTransaction: rawTx,
1339
1438
  accountIndex: activeAccountId,
1340
1439
  expectedAddress: activeAccount.address,
1341
1440
  }, await this.authorizeHardwareTransaction(provider, assertCurrent));
1342
1441
  assertCurrent();
1343
- const formattedSignature = {
1344
- r: `0x${signature.r}`,
1345
- s: `0x${signature.s}`,
1346
- v: parseInt(signature.v, 16),
1347
- };
1348
- if (signature) {
1349
- try {
1350
- const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
1351
- assertHardwareTransactionSender(signedTx, activeAccount.address);
1352
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
1353
- return finalTx;
1354
- }
1355
- catch (error) {
1356
- throw error;
1357
- }
1358
- }
1359
- else {
1360
- throw new Error(`Transaction Signature Failed. Error: ${signature}`);
1361
- }
1442
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
1443
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
1444
+ return finalTx;
1362
1445
  };
1363
1446
  const sendEVMTrezorTransaction = async () => {
1364
1447
  assertCurrent();
@@ -1452,7 +1535,7 @@ export class EthereumTransactions {
1452
1535
  };
1453
1536
  const sendEVMTransaction = async () => {
1454
1537
  assertCurrent();
1455
- const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
1538
+ const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey(targetAccount);
1456
1539
  // Validate that we have the correct private key for the active account to prevent race conditions
1457
1540
  // This is critical for transaction security during account switches
1458
1541
  if (address.toLowerCase() !== activeAccount.address.toLowerCase()) {
@@ -1709,32 +1792,18 @@ export class EthereumTransactions {
1709
1792
  };
1710
1793
  const rawTx = serializeTransaction(txFormattedForEthers);
1711
1794
  assertCurrent();
1712
- const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
1713
- rawTx: rawTx.replace('0x', ''),
1795
+ const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
1796
+ unsignedTransaction: rawTx,
1714
1797
  accountIndex: activeAccountId,
1715
1798
  expectedAddress: activeAccount.address,
1716
1799
  }, await this.authorizeHardwareTransaction(provider, assertCurrent));
1717
- const formattedSignature = {
1718
- r: `0x${signature.r}`,
1719
- s: `0x${signature.s}`,
1720
- v: parseInt(signature.v, 16),
1800
+ assertSignedSender(signedTx);
1801
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
1802
+ const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
1803
+ return {
1804
+ isSpeedUp: true,
1805
+ transaction: transactionResponse,
1721
1806
  };
1722
- if (signature) {
1723
- const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
1724
- assertSignedSender(signedTx);
1725
- assertHardwareTransactionSender(signedTx, activeAccount.address);
1726
- const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
1727
- return {
1728
- isSpeedUp: true,
1729
- transaction: transactionResponse,
1730
- };
1731
- }
1732
- else {
1733
- return {
1734
- isSpeedUp: false,
1735
- error: true,
1736
- };
1737
- }
1738
1807
  }
1739
1808
  catch (error) {
1740
1809
  console.error('[SpeedUp] Failed to send replacement transaction with Ledger:', error);
@@ -1947,73 +2016,51 @@ export class EthereumTransactions {
1947
2016
  };
1948
2017
  const sendERC20TokenOnLedger = async () => {
1949
2018
  assertCurrent();
1950
- try {
1951
- const _contract = new Contract(tokenAddress, getErc20Abi(), provider);
1952
- const txData = _contract.interface.encodeFunctionData('transfer', [
1953
- receiver,
1954
- exactTokenAmount,
1955
- ]);
1956
- let txFormattedForEthers;
1957
- if (isLegacy) {
1958
- txFormattedForEthers = {
1959
- to: tokenAddress,
1960
- value: '0x0',
1961
- gasLimit: gasLimit,
1962
- gasPrice,
1963
- data: txData,
1964
- nonce,
1965
- chainId: activeNetwork.chainId,
1966
- type: 0,
1967
- };
1968
- }
1969
- else {
1970
- txFormattedForEthers = {
1971
- to: tokenAddress,
1972
- value: '0x0',
1973
- gasLimit: gasLimit,
1974
- maxFeePerGas,
1975
- maxPriorityFeePerGas,
1976
- data: txData,
1977
- nonce,
1978
- chainId: activeNetwork.chainId,
1979
- type: 2, // Need explicit type for hardware wallet serialization
1980
- };
1981
- }
1982
- const rawTx = serializeTransaction(txFormattedForEthers);
1983
- const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
1984
- rawTx: rawTx.replace('0x', ''),
1985
- accountIndex: activeAccountId,
1986
- expectedAddress: activeAccount.address,
1987
- }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
1988
- assertCurrent();
1989
- const formattedSignature = {
1990
- r: `0x${signature.r}`,
1991
- s: `0x${signature.s}`,
1992
- v: parseInt(signature.v, 16),
2019
+ const _contract = new Contract(tokenAddress, getErc20Abi(), provider);
2020
+ const txData = _contract.interface.encodeFunctionData('transfer', [
2021
+ receiver,
2022
+ exactTokenAmount,
2023
+ ]);
2024
+ let txFormattedForEthers;
2025
+ if (isLegacy) {
2026
+ txFormattedForEthers = {
2027
+ to: tokenAddress,
2028
+ value: '0x0',
2029
+ gasLimit: gasLimit,
2030
+ gasPrice,
2031
+ data: txData,
2032
+ nonce,
2033
+ chainId: activeNetwork.chainId,
2034
+ type: 0,
1993
2035
  };
1994
- if (signature) {
1995
- try {
1996
- const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
1997
- assertCurrent();
1998
- assertHardwareTransactionSender(signedTx, activeAccount.address);
1999
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async ({ rawTransaction, admit }) => {
2000
- const response = await admit(() => provider.sendTransaction(rawTransaction));
2001
- saveTrezorTx && saveTrezorTx(response);
2002
- return response;
2003
- }, submission, undefined, rawTx);
2004
- return finalTx;
2005
- }
2006
- catch (error) {
2007
- throw error;
2008
- }
2009
- }
2010
- else {
2011
- throw new Error(`Transaction Signature Failed. Error: ${signature}`);
2012
- }
2013
2036
  }
2014
- catch (error) {
2015
- throw error;
2037
+ else {
2038
+ txFormattedForEthers = {
2039
+ to: tokenAddress,
2040
+ value: '0x0',
2041
+ gasLimit: gasLimit,
2042
+ maxFeePerGas,
2043
+ maxPriorityFeePerGas,
2044
+ data: txData,
2045
+ nonce,
2046
+ chainId: activeNetwork.chainId,
2047
+ type: 2, // Need explicit type for hardware wallet serialization
2048
+ };
2016
2049
  }
2050
+ const rawTx = serializeTransaction(txFormattedForEthers);
2051
+ const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
2052
+ unsignedTransaction: rawTx,
2053
+ accountIndex: activeAccountId,
2054
+ expectedAddress: activeAccount.address,
2055
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2056
+ assertCurrent();
2057
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
2058
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async ({ rawTransaction, admit }) => {
2059
+ const response = await admit(() => provider.sendTransaction(rawTransaction));
2060
+ saveTrezorTx && saveTrezorTx(response);
2061
+ return response;
2062
+ }, submission, undefined, rawTx);
2063
+ return finalTx;
2017
2064
  };
2018
2065
  const sendERC20TokenOnTrezor = async () => {
2019
2066
  assertCurrent();
@@ -2174,70 +2221,48 @@ export class EthereumTransactions {
2174
2221
  };
2175
2222
  const sendERC721TokenOnLedger = async () => {
2176
2223
  assertCurrent();
2177
- try {
2178
- const _contract = new Contract(tokenAddress, getErc21Abi(), provider);
2179
- const txData = _contract.interface.encodeFunctionData('transferFrom', [
2180
- activeAccountAddress,
2181
- receiver,
2182
- normalizedTokenId,
2183
- ]);
2184
- let txFormattedForEthers;
2185
- if (isLegacy) {
2186
- txFormattedForEthers = {
2187
- to: tokenAddress,
2188
- value: '0x0',
2189
- gasLimit,
2190
- gasPrice,
2191
- data: txData,
2192
- nonce,
2193
- chainId: activeNetwork.chainId,
2194
- type: 0,
2195
- };
2196
- }
2197
- else {
2198
- txFormattedForEthers = {
2199
- to: tokenAddress,
2200
- value: '0x0',
2201
- gasLimit,
2202
- maxFeePerGas,
2203
- maxPriorityFeePerGas,
2204
- data: txData,
2205
- nonce,
2206
- chainId: activeNetwork.chainId,
2207
- type: 2, // Need explicit type for hardware wallet serialization
2208
- };
2209
- }
2210
- const rawTx = serializeTransaction(txFormattedForEthers);
2211
- const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
2212
- rawTx: rawTx.replace('0x', ''),
2213
- accountIndex: activeAccountId,
2214
- expectedAddress: activeAccount.address,
2215
- }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2216
- assertCurrent();
2217
- const formattedSignature = {
2218
- r: `0x${signature.r}`,
2219
- s: `0x${signature.s}`,
2220
- v: parseInt(signature.v, 16),
2224
+ const _contract = new Contract(tokenAddress, getErc21Abi(), provider);
2225
+ const txData = _contract.interface.encodeFunctionData('transferFrom', [
2226
+ activeAccountAddress,
2227
+ receiver,
2228
+ normalizedTokenId,
2229
+ ]);
2230
+ let txFormattedForEthers;
2231
+ if (isLegacy) {
2232
+ txFormattedForEthers = {
2233
+ to: tokenAddress,
2234
+ value: '0x0',
2235
+ gasLimit,
2236
+ gasPrice,
2237
+ data: txData,
2238
+ nonce,
2239
+ chainId: activeNetwork.chainId,
2240
+ type: 0,
2221
2241
  };
2222
- if (signature) {
2223
- try {
2224
- const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
2225
- assertCurrent();
2226
- assertHardwareTransactionSender(signedTx, activeAccount.address);
2227
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
2228
- return finalTx;
2229
- }
2230
- catch (error) {
2231
- throw error;
2232
- }
2233
- }
2234
- else {
2235
- throw new Error(`Transaction Signature Failed. Error: ${signature}`);
2236
- }
2237
2242
  }
2238
- catch (error) {
2239
- throw error;
2243
+ else {
2244
+ txFormattedForEthers = {
2245
+ to: tokenAddress,
2246
+ value: '0x0',
2247
+ gasLimit,
2248
+ maxFeePerGas,
2249
+ maxPriorityFeePerGas,
2250
+ data: txData,
2251
+ nonce,
2252
+ chainId: activeNetwork.chainId,
2253
+ type: 2, // Need explicit type for hardware wallet serialization
2254
+ };
2240
2255
  }
2256
+ const rawTx = serializeTransaction(txFormattedForEthers);
2257
+ const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
2258
+ unsignedTransaction: rawTx,
2259
+ accountIndex: activeAccountId,
2260
+ expectedAddress: activeAccount.address,
2261
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2262
+ assertCurrent();
2263
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
2264
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
2265
+ return finalTx;
2241
2266
  };
2242
2267
  const sendERC721TokenOnTrezor = async () => {
2243
2268
  assertCurrent();
@@ -2404,66 +2429,44 @@ export class EthereumTransactions {
2404
2429
  };
2405
2430
  const sendERC1155TokenOnLedger = async () => {
2406
2431
  assertCurrent();
2407
- try {
2408
- const _contract = new Contract(tokenAddress, getErc55Abi(), provider);
2409
- const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, normalizedTokenId, amount, '0x']);
2410
- let txFormattedForEthers;
2411
- if (isLegacy) {
2412
- txFormattedForEthers = {
2413
- to: tokenAddress,
2414
- value: '0x0',
2415
- gasLimit,
2416
- gasPrice,
2417
- data: txData,
2418
- nonce,
2419
- chainId: activeNetwork.chainId,
2420
- type: 0,
2421
- };
2422
- }
2423
- else {
2424
- txFormattedForEthers = {
2425
- to: tokenAddress,
2426
- value: '0x0',
2427
- gasLimit,
2428
- maxFeePerGas,
2429
- maxPriorityFeePerGas,
2430
- data: txData,
2431
- nonce,
2432
- chainId: activeNetwork.chainId,
2433
- type: 2, // Need explicit type for hardware wallet serialization
2434
- };
2435
- }
2436
- const rawTx = serializeTransaction(txFormattedForEthers);
2437
- const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
2438
- rawTx: rawTx.replace('0x', ''),
2439
- accountIndex: activeAccountId,
2440
- expectedAddress: activeAccount.address,
2441
- }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2442
- assertCurrent();
2443
- const formattedSignature = {
2444
- r: `0x${signature.r}`,
2445
- s: `0x${signature.s}`,
2446
- v: parseInt(signature.v, 16),
2432
+ const _contract = new Contract(tokenAddress, getErc55Abi(), provider);
2433
+ const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, normalizedTokenId, amount, '0x']);
2434
+ let txFormattedForEthers;
2435
+ if (isLegacy) {
2436
+ txFormattedForEthers = {
2437
+ to: tokenAddress,
2438
+ value: '0x0',
2439
+ gasLimit,
2440
+ gasPrice,
2441
+ data: txData,
2442
+ nonce,
2443
+ chainId: activeNetwork.chainId,
2444
+ type: 0,
2447
2445
  };
2448
- if (signature) {
2449
- try {
2450
- const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
2451
- assertCurrent();
2452
- assertHardwareTransactionSender(signedTx, activeAccount.address);
2453
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
2454
- return finalTx;
2455
- }
2456
- catch (error) {
2457
- throw error;
2458
- }
2459
- }
2460
- else {
2461
- throw new Error(`Transaction Signature Failed. Error: ${signature}`);
2462
- }
2463
2446
  }
2464
- catch (error) {
2465
- throw error;
2447
+ else {
2448
+ txFormattedForEthers = {
2449
+ to: tokenAddress,
2450
+ value: '0x0',
2451
+ gasLimit,
2452
+ maxFeePerGas,
2453
+ maxPriorityFeePerGas,
2454
+ data: txData,
2455
+ nonce,
2456
+ chainId: activeNetwork.chainId,
2457
+ type: 2, // Need explicit type for hardware wallet serialization
2458
+ };
2466
2459
  }
2460
+ const rawTx = serializeTransaction(txFormattedForEthers);
2461
+ const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
2462
+ unsignedTransaction: rawTx,
2463
+ accountIndex: activeAccountId,
2464
+ expectedAddress: activeAccount.address,
2465
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2466
+ assertCurrent();
2467
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
2468
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
2469
+ return finalTx;
2467
2470
  };
2468
2471
  const sendERC1155TokenOnTrezor = async () => {
2469
2472
  assertCurrent();
@@ -2785,6 +2788,52 @@ export class EthereumTransactions {
2785
2788
  console.log('[EthereumTransactions] Deferring provider initialization:', error?.message || error);
2786
2789
  }
2787
2790
  }
2791
+ signPreparedEvmTransaction(input, options) {
2792
+ const selected = this.captureEvmAccountContext();
2793
+ const request = snapshotPreparedEvmRequest(input);
2794
+ const target = {
2795
+ id: request.account.id,
2796
+ type: request.account.type,
2797
+ };
2798
+ const context = this.captureEvmAccountContext(target);
2799
+ const hostGuard = preparedEvmGuard(options);
2800
+ const targetRecord = this.getState().accounts[target.type]?.[target.id];
2801
+ const encryptedKey = targetRecord?.xprv;
2802
+ const assertCurrent = () => {
2803
+ selected.assertCurrent();
2804
+ context.assertCurrent();
2805
+ const live = this.getState().accounts[target.type]?.[target.id];
2806
+ if (!live ||
2807
+ live.id !== target.id ||
2808
+ live.isSmartAccount ||
2809
+ live.isLedgerWallet ||
2810
+ live.isTrezorWallet ||
2811
+ typeof encryptedKey !== 'string' ||
2812
+ !encryptedKey ||
2813
+ live.xprv !== encryptedKey ||
2814
+ selected.activeAccountId !== request.selectedAccount.id ||
2815
+ selected.activeAccountType !== request.selectedAccount.type ||
2816
+ getAddress(selected.activeAccount.address) !==
2817
+ request.selectedAccount.expectedAddress ||
2818
+ getAddress(context.activeAccount.address) !==
2819
+ request.account.expectedAddress ||
2820
+ context.activeNetwork.kind !== 'ethereum' ||
2821
+ context.activeNetwork.slip44 !== 60 ||
2822
+ String(context.activeNetwork.chainId) !== request.network.chainId ||
2823
+ context.activeNetwork.url !== request.network.url)
2824
+ throw new Error('Prepared EVM signing context changed or is unsupported');
2825
+ };
2826
+ assertCurrent();
2827
+ hostGuard();
2828
+ assertCurrent();
2829
+ const key = this.getDecryptedPrivateKey(target);
2830
+ if (getAddress(key.address) !== request.account.expectedAddress)
2831
+ throw new Error('Prepared EVM signer address mismatch');
2832
+ const { signed } = signPreparedEvmEnvelope(request.transaction, key.decryptedPrivateKey);
2833
+ hostGuard();
2834
+ assertCurrent();
2835
+ return signed;
2836
+ }
2788
2837
  setWeb3Provider(network) {
2789
2838
  this.abortController.abort();
2790
2839
  this.abortController = new AbortController();