@sidhujag/sysweb3-keyring 2.1.6 → 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 (100) hide show
  1. package/CHANGELOG.md +12 -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 +321 -269
  24. package/cjs/transactions/ethereum.js.map +1 -1
  25. package/cjs/transactions/evm-envelope.js +186 -0
  26. package/cjs/transactions/evm-envelope.js.map +1 -0
  27. package/cjs/transactions/evm-submission.js +32 -4
  28. package/cjs/transactions/evm-submission.js.map +1 -1
  29. package/cjs/transactions/ledger-signing.js +44 -0
  30. package/cjs/transactions/ledger-signing.js.map +1 -0
  31. package/cjs/transactions/prepared-evm.js +44 -124
  32. package/cjs/transactions/prepared-evm.js.map +1 -1
  33. package/cjs/trezor/connect.js +238 -243
  34. package/cjs/trezor/connect.js.map +1 -1
  35. package/cjs/trezor/host-types.js +19 -0
  36. package/cjs/trezor/host-types.js.map +1 -0
  37. package/cjs/trezor/index.js +75 -23
  38. package/cjs/trezor/index.js.map +1 -1
  39. package/cjs/trezor/webextension-host.js +118 -0
  40. package/cjs/trezor/webextension-host.js.map +1 -0
  41. package/cjs/trezor-hardware.js +16 -0
  42. package/cjs/trezor-hardware.js.map +1 -0
  43. package/cjs/types.js.map +1 -1
  44. package/esm/hardware-transaction-authority.js +14 -11
  45. package/esm/hardware-transaction-authority.js.map +1 -1
  46. package/esm/hardware-wallet-manager.js +20 -32
  47. package/esm/hardware-wallet-manager.js.map +1 -1
  48. package/esm/hardware-wallet-types.js +13 -0
  49. package/esm/hardware-wallet-types.js.map +1 -0
  50. package/esm/keyring-core.js +98 -63
  51. package/esm/keyring-core.js.map +1 -1
  52. package/esm/ledger/dmk-host.js +2 -0
  53. package/esm/ledger/dmk-host.js.map +1 -1
  54. package/esm/ledger/dmk-session.js +73 -8
  55. package/esm/ledger/dmk-session.js.map +1 -1
  56. package/esm/ledger/host-types.js.map +1 -1
  57. package/esm/ledger/index.js +22 -2
  58. package/esm/ledger/index.js.map +1 -1
  59. package/esm/transactions/ethereum.js +322 -270
  60. package/esm/transactions/ethereum.js.map +1 -1
  61. package/esm/transactions/evm-envelope.js +179 -0
  62. package/esm/transactions/evm-envelope.js.map +1 -0
  63. package/esm/transactions/evm-submission.js +32 -4
  64. package/esm/transactions/evm-submission.js.map +1 -1
  65. package/esm/transactions/ledger-signing.js +41 -0
  66. package/esm/transactions/ledger-signing.js.map +1 -0
  67. package/esm/transactions/prepared-evm.js +43 -124
  68. package/esm/transactions/prepared-evm.js.map +1 -1
  69. package/esm/trezor/connect.js +236 -209
  70. package/esm/trezor/connect.js.map +1 -1
  71. package/esm/trezor/host-types.js +16 -0
  72. package/esm/trezor/host-types.js.map +1 -0
  73. package/esm/trezor/index.js +74 -22
  74. package/esm/trezor/index.js.map +1 -1
  75. package/esm/types.js.map +1 -1
  76. package/package.json +61 -1
  77. package/types/hardware-transaction-authority.d.ts +1 -0
  78. package/types/hardware-wallet-manager.d.ts +6 -22
  79. package/types/hardware-wallet-types.d.ts +21 -0
  80. package/types/host-keyring.d.ts +16 -0
  81. package/types/index.d.ts +1 -0
  82. package/types/keyring-core.d.ts +12 -4
  83. package/types/keyring-manager.d.ts +5 -3
  84. package/types/ledger/dmk-host.d.ts +2 -0
  85. package/types/ledger/dmk-session.d.ts +9 -0
  86. package/types/ledger/host-types.d.ts +2 -0
  87. package/types/ledger/index.d.ts +3 -1
  88. package/types/ledger-hardware.d.ts +5 -0
  89. package/types/software.d.ts +4 -17
  90. package/types/transactions/ethereum.d.ts +11 -1
  91. package/types/transactions/evm-envelope.d.ts +64 -0
  92. package/types/transactions/evm-submission.d.ts +7 -2
  93. package/types/transactions/ledger-signing.d.ts +13 -0
  94. package/types/transactions/prepared-evm.d.ts +14 -23
  95. package/types/trezor/connect.d.ts +11 -9
  96. package/types/trezor/host-types.d.ts +25 -0
  97. package/types/trezor/index.d.ts +7 -7
  98. package/types/trezor/webextension-host.d.ts +7 -0
  99. package/types/trezor-hardware.d.ts +6 -0
  100. package/types/types.d.ts +17 -5
@@ -7,6 +7,7 @@ exports.EthereumTransactions = void 0;
7
7
  const eth_sig_util_1 = require("@metamask/eth-sig-util");
8
8
  const sysweb3_network_1 = require("@sidhujag/sysweb3-network");
9
9
  const sysweb3_utils_1 = require("@sidhujag/sysweb3-utils");
10
+ const address_1 = require("ethers/address");
10
11
  const transaction_1 = require("ethers/transaction");
11
12
  const utils_1 = require("ethers/utils");
12
13
  const omit_1 = __importDefault(require("lodash/omit"));
@@ -15,8 +16,10 @@ const evm_fees_1 = require("../evm-fees");
15
16
  const providers_1 = require("../providers");
16
17
  const types_1 = require("../types");
17
18
  const evm_cancellation_1 = require("./evm-cancellation");
19
+ const evm_envelope_1 = require("./evm-envelope");
18
20
  const evm_local_signer_1 = require("./evm-local-signer");
19
21
  const evm_submission_1 = require("./evm-submission");
22
+ const ledger_signing_1 = require("./ledger-signing");
20
23
  const prepared_evm_1 = require("./prepared-evm");
21
24
  const derivation_paths_1 = require("../utils/derivation-paths");
22
25
  const hardwareSignerUnavailable = (hardware) => Object.assign(new Error(`${hardware} signer is unavailable in this host`), {
@@ -160,7 +163,7 @@ class EthereumTransactions {
160
163
  }
161
164
  return () => assertSynchronousOriginalValidation(guard(), 'Signing context guard must be synchronous');
162
165
  };
163
- this.captureEvmSigningContext = (target) => {
166
+ this.captureEvmAccountContext = (target) => {
164
167
  this.assertHardwareSignerAvailable(target);
165
168
  const state = this.getState();
166
169
  const selectedType = state.activeAccountType;
@@ -174,7 +177,6 @@ class EthereumTransactions {
174
177
  const account = { address: selectedAccount.address };
175
178
  const network = { ...state.activeNetwork };
176
179
  const assertSession = this.captureSessionGuard();
177
- const provider = this.web3Provider;
178
180
  const assertCurrent = () => {
179
181
  assertSession();
180
182
  const current = this.getState();
@@ -188,8 +190,7 @@ class EthereumTransactions {
188
190
  [current.activeNetwork, this.getNetwork()].some((selected) => selected.kind !== network.kind ||
189
191
  selected.chainId !== network.chainId ||
190
192
  selected.slip44 !== network.slip44 ||
191
- selected.url !== network.url) ||
192
- this.web3Provider !== provider)
193
+ selected.url !== network.url))
193
194
  throw new Error('Wallet signing context changed');
194
195
  };
195
196
  assertCurrent();
@@ -198,10 +199,20 @@ class EthereumTransactions {
198
199
  activeAccountType,
199
200
  activeAccountId,
200
201
  activeNetwork: network,
201
- provider,
202
202
  assertCurrent,
203
203
  };
204
204
  };
205
+ this.captureEvmSigningContext = (target) => {
206
+ const context = this.captureEvmAccountContext(target);
207
+ const provider = this.web3Provider;
208
+ const assertCurrent = () => {
209
+ context.assertCurrent();
210
+ if (this.web3Provider !== provider)
211
+ throw new Error('Wallet signing context changed');
212
+ };
213
+ assertCurrent();
214
+ return { ...context, provider, assertCurrent };
215
+ };
205
216
  this.captureSubmission = (operation, account, type, id, network, assertCurrent, details = {}) => {
206
217
  if (!this.captureSubmissionLifecycle)
207
218
  throw new TypeError('A durable EVM submission lifecycle is required');
@@ -302,10 +313,51 @@ class EthereumTransactions {
302
313
  throw error;
303
314
  }
304
315
  };
305
- this.ethSign = async (params) => {
306
- const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
307
- const { accounts, activeAccountId, activeAccountType, activeNetwork } = this.getState();
308
- const activeAccount = accounts[activeAccountType][activeAccountId];
316
+ this.ethSign = async (params, input) => {
317
+ const selected = this.captureEvmAccountContext();
318
+ if (!Array.isArray(params) || params.length !== 2)
319
+ throw new Error('Invalid digest request');
320
+ const first = Object.getOwnPropertyDescriptor(params, '0');
321
+ const second = Object.getOwnPropertyDescriptor(params, '1');
322
+ if (!first ||
323
+ !second ||
324
+ !('value' in first) ||
325
+ !('value' in second) ||
326
+ typeof first.value !== 'string' ||
327
+ typeof second.value !== 'string')
328
+ throw new Error('Invalid digest request');
329
+ params = [first.value, second.value];
330
+ const options = (0, evm_envelope_1.readEvmOwnData)(input, [
331
+ 'selectedAccount',
332
+ 'targetAccount',
333
+ 'assertCurrent',
334
+ ]);
335
+ const selectedAccount = (0, prepared_evm_1.snapshotEvmSigningIdentity)(options.selectedAccount);
336
+ const targetAccount = (0, prepared_evm_1.snapshotEvmSigningIdentity)(options.targetAccount);
337
+ const hostGuard = (0, prepared_evm_1.preparedEvmGuard)({
338
+ assertCurrent: options.assertCurrent,
339
+ });
340
+ const target = {
341
+ id: targetAccount.id,
342
+ type: targetAccount.type,
343
+ };
344
+ const context = this.captureEvmAccountContext(target);
345
+ const assertCurrent = () => {
346
+ hostGuard();
347
+ selected.assertCurrent();
348
+ context.assertCurrent();
349
+ if (selected.activeAccountId !== selectedAccount.id ||
350
+ selected.activeAccountType !== selectedAccount.type ||
351
+ (0, address_1.getAddress)(selected.activeAccount.address) !==
352
+ selectedAccount.expectedAddress ||
353
+ (0, address_1.getAddress)(context.activeAccount.address) !==
354
+ targetAccount.expectedAddress ||
355
+ targetAccount.type === 'SmartAccount')
356
+ throw new Error('EVM digest signing context changed');
357
+ };
358
+ assertCurrent();
359
+ const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey(target);
360
+ const { activeAccountId, activeAccountType, activeNetwork, activeAccount } = context;
309
361
  // Validate that the derived address matches the active account to prevent race conditions
310
362
  if (address.toLowerCase() !== activeAccount.address.toLowerCase()) {
311
363
  throw {
@@ -374,14 +426,19 @@ class EthereumTransactions {
374
426
  throw error;
375
427
  }
376
428
  };
429
+ let signature;
377
430
  switch (activeAccountType) {
378
431
  case types_1.KeyringAccountType.Trezor:
379
- return await signWithTrezor();
432
+ signature = await signWithTrezor();
433
+ break;
380
434
  case types_1.KeyringAccountType.Ledger:
381
- return await signWithLedger();
435
+ signature = await signWithLedger();
436
+ break;
382
437
  default:
383
- return sign();
438
+ signature = sign();
384
439
  }
440
+ assertCurrent();
441
+ return signature;
385
442
  };
386
443
  this.signPersonalMessage = async (params) => {
387
444
  const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
@@ -951,33 +1008,18 @@ class EthereumTransactions {
951
1008
  const rawTx = serializeTransaction(txFormattedForEthers);
952
1009
  if (retained)
953
1010
  assertCancellationEnvelope(rawTx);
954
- const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
955
- rawTx: rawTx.replace('0x', ''),
1011
+ const { rawTransaction: signedTx } = await (0, ledger_signing_1.signLedgerEvmTransaction)(this.requireLedgerSigner(), {
1012
+ unsignedTransaction: rawTx,
956
1013
  accountIndex: activeAccountId,
957
1014
  expectedAddress: activeAccount.address,
958
1015
  }, await this.authorizeHardwareTransaction(provider, assertCurrent));
959
1016
  assertCurrent();
960
- const formattedSignature = {
961
- r: `0x${signature.r}`,
962
- s: `0x${signature.s}`,
963
- v: parseInt(signature.v, 16),
1017
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
1018
+ const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
1019
+ return {
1020
+ isCanceled: true,
1021
+ transaction: transactionResponse,
964
1022
  };
965
- if (signature) {
966
- const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
967
- assertCurrent();
968
- assertHardwareTransactionSender(signedTx, activeAccount.address);
969
- const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
970
- return {
971
- isCanceled: true,
972
- transaction: transactionResponse,
973
- };
974
- }
975
- else {
976
- return {
977
- isCanceled: false,
978
- error: true,
979
- };
980
- }
981
1023
  }
982
1024
  catch (error) {
983
1025
  return {
@@ -1108,12 +1150,21 @@ class EthereumTransactions {
1108
1150
  typeof options !== 'object' ||
1109
1151
  Array.isArray(options) ||
1110
1152
  (options.mode !== 'reviewed-native' &&
1153
+ options.mode !== 'reviewed-generic' &&
1111
1154
  options.mode !== 'specialized') ||
1112
1155
  typeof options.assertCurrent !== 'function')
1113
1156
  throw new Error('Explicit transaction approval is required');
1114
1157
  // Capture once; later caller mutations cannot replace this invocation's
1115
1158
  // admission decision while chain checks or device approval are pending.
1116
1159
  const { assertCurrent: assertHostCurrent, mode, walletNonce } = options;
1160
+ const reviewId = mode === 'reviewed-generic' ? options.reviewId : undefined;
1161
+ if (mode === 'reviewed-generic' &&
1162
+ (typeof reviewId !== 'string' ||
1163
+ !/^[A-Za-z0-9_-]{1,128}$/.test(reviewId) ||
1164
+ walletNonce === undefined))
1165
+ throw new Error('An issued fee review and approved nonce are required');
1166
+ const selectedAccount = (0, prepared_evm_1.snapshotEvmSigningIdentity)(options.selectedAccount);
1167
+ const targetAccount = (0, prepared_evm_1.snapshotEvmSigningIdentity)(options.targetAccount);
1117
1168
  const nonceApproval = walletNonce === undefined ? undefined : { ...walletNonce };
1118
1169
  const assertApproved = () => {
1119
1170
  const result = assertHostCurrent();
@@ -1125,14 +1176,14 @@ class EthereumTransactions {
1125
1176
  assertApproved();
1126
1177
  return await this.sendFormattedTransactionInternal(params, isLegacy, () => {
1127
1178
  broadcastAttempted = true;
1128
- }, assertApproved, mode, nonceApproval);
1179
+ }, assertApproved, mode, nonceApproval, selectedAccount, targetAccount, reviewId);
1129
1180
  }
1130
1181
  catch (error) {
1131
1182
  // A broadcast transport error may mean the node accepted the signed
1132
1183
  // transaction. Derive this marker from this invocation, never RPC data.
1133
1184
  const submissionError = Object.assign(new Error(error instanceof Error ? error.message : String(error)), error, {
1134
1185
  transactionNotBroadcast: !broadcastAttempted ||
1135
- (0, evm_submission_1.evmSubmissionFailure)(error).transactionNotBroadcast,
1186
+ (0, evm_submission_1.evmSubmissionFailure)(error)?.transactionNotBroadcast === true,
1136
1187
  });
1137
1188
  if (error instanceof Error) {
1138
1189
  submissionError.name = error.name;
@@ -1142,19 +1193,60 @@ class EthereumTransactions {
1142
1193
  throw submissionError;
1143
1194
  }
1144
1195
  };
1145
- this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode, walletNonce) => {
1196
+ this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode, walletNonce, selectedIdentity, targetIdentity, reviewId) => {
1146
1197
  // Hardware approval may outlive the selected account, network or unlocked
1147
1198
  // session. Keep immutable identities and one provider for this invocation.
1148
- const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertKeyringCurrent, } = this.captureEvmSigningContext();
1199
+ const selectedContext = this.captureEvmAccountContext();
1200
+ const targetAccount = {
1201
+ id: targetIdentity.id,
1202
+ type: targetIdentity.type,
1203
+ };
1204
+ if (targetAccount.type === types_1.KeyringAccountType.SmartAccount)
1205
+ throw new Error('Invalid EVM payer');
1206
+ const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertKeyringCurrent, } = this.captureEvmSigningContext(targetAccount);
1149
1207
  const assertSigningCurrent = () => {
1150
1208
  assertHostCurrent();
1209
+ selectedContext.assertCurrent();
1151
1210
  assertKeyringCurrent();
1211
+ if (selectedContext.activeAccountId !== selectedIdentity.id ||
1212
+ selectedContext.activeAccountType !== selectedIdentity.type ||
1213
+ (0, address_1.getAddress)(selectedContext.activeAccount.address) !==
1214
+ selectedIdentity.expectedAddress ||
1215
+ (0, address_1.getAddress)(activeAccount.address) !== targetIdentity.expectedAddress)
1216
+ throw new Error('EVM payer approval context changed');
1152
1217
  };
1218
+ assertSigningCurrent();
1219
+ // Generic reviewed calls keep the exact prepared envelope, including
1220
+ // contract creation, calldata and access lists, through existing signers.
1221
+ const reviewed = mode === 'reviewed-generic'
1222
+ ? (0, evm_envelope_1.snapshotPreparedEvmTransaction)(input)
1223
+ : undefined;
1224
+ if (reviewed) {
1225
+ if (reviewed.from !== targetIdentity.expectedAddress ||
1226
+ reviewed.chainId !== String(activeNetwork.chainId) ||
1227
+ reviewed.nonce !== String(walletNonce?.nonce) ||
1228
+ (isLegacy !== undefined && isLegacy !== (reviewed.type === 0)) ||
1229
+ (this.gasOverrides.minGasLimit &&
1230
+ BigInt(reviewed.gasLimit) < this.gasOverrides.minGasLimit.toBigInt()))
1231
+ throw new Error('Reviewed EVM transaction does not match its approval');
1232
+ input = reviewed;
1233
+ isLegacy = reviewed.type === 0;
1234
+ }
1153
1235
  const assertBeforeBroadcast = () => {
1154
1236
  assertCurrent();
1155
1237
  beforeBroadcast();
1156
1238
  };
1157
- const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, { walletNonce });
1239
+ const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, {
1240
+ walletNonce,
1241
+ ...(reviewed
1242
+ ? {
1243
+ review: {
1244
+ id: reviewId,
1245
+ unsignedHash: transaction_1.Transaction.from((0, evm_envelope_1.serializePreparedEvmTransaction)(reviewed)).unsignedHash,
1246
+ },
1247
+ }
1248
+ : {}),
1249
+ });
1158
1250
  const assertCurrent = submission.assertCurrent;
1159
1251
  const approvedNonce = submission.context.walletNonce?.nonce;
1160
1252
  const assertApprovedNonce = (nonce) => {
@@ -1163,6 +1255,7 @@ class EthereumTransactions {
1163
1255
  };
1164
1256
  if (mode === 'reviewed-native') {
1165
1257
  const { transaction } = (0, prepared_evm_1.snapshotPreparedEvmRequest)({
1258
+ selectedAccount: selectedIdentity,
1166
1259
  account: {
1167
1260
  id: activeAccountId,
1168
1261
  type: activeAccountType,
@@ -1178,7 +1271,10 @@ class EthereumTransactions {
1178
1271
  });
1179
1272
  assertApprovedNonce(Number(transaction.nonce));
1180
1273
  const minimum = this.gasOverrides.minGasLimit;
1181
- if (transaction.data !== '0x' ||
1274
+ if (transaction.to === null ||
1275
+ /^0x0{40}$/i.test(transaction.to) ||
1276
+ (transaction.type === 2 && transaction.accessList.length !== 0) ||
1277
+ transaction.data !== '0x' ||
1182
1278
  BigInt(transaction.value) === 0n ||
1183
1279
  BigInt(transaction.gasLimit) < 21000n ||
1184
1280
  (transaction.type === 2 && BigInt(transaction.maxFeePerGas) === 0n) ||
@@ -1190,7 +1286,7 @@ class EthereumTransactions {
1190
1286
  assertCurrent();
1191
1287
  if (this.gasOverrides.minGasLimit !== minimum)
1192
1288
  throw new Error('Reviewed native gas policy changed');
1193
- const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
1289
+ const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey(targetAccount);
1194
1290
  if (address.toLowerCase() !== transaction.from.toLowerCase())
1195
1291
  throw new Error('Reviewed native signer does not match the approved account');
1196
1292
  assertCurrent();
@@ -1203,6 +1299,9 @@ class EthereumTransactions {
1203
1299
  if (!params || typeof params !== 'object' || Array.isArray(params))
1204
1300
  throw new TypeError('Invalid EVM transaction request');
1205
1301
  const supplied = { ...params };
1302
+ if (supplied.from !== undefined &&
1303
+ (0, address_1.getAddress)(supplied.from) !== targetIdentity.expectedAddress)
1304
+ throw new Error('EVM transaction sender does not match the approved payer');
1206
1305
  // Validate original scalar spellings before conversion. Normalization's
1207
1306
  // permissive zero coercion must not bypass the bounded wire parser.
1208
1307
  for (const field of [
@@ -1255,7 +1354,8 @@ class EthereumTransactions {
1255
1354
  const validateEip1559Fees = () => {
1256
1355
  const maximum = nonNegativeFeeQuantity(params.maxFeePerGas, 'maximum fee');
1257
1356
  const priority = nonNegativeFeeQuantity(params.maxPriorityFeePerGas, 'priority fee');
1258
- if (maximum.isZero() || priority.gt(maximum))
1357
+ if ((mode !== 'reviewed-generic' && maximum.isZero()) ||
1358
+ priority.gt(maximum))
1259
1359
  throw new Error('Invalid EIP-1559 fees');
1260
1360
  params.maxFeePerGas = maximum;
1261
1361
  params.maxPriorityFeePerGas = priority;
@@ -1266,7 +1366,9 @@ class EthereumTransactions {
1266
1366
  validateEip1559Fees();
1267
1367
  // Only apply a minimum gas limit when a network-specific override is set.
1268
1368
  // Otherwise keep the user/dapp-approved gas limit unchanged.
1269
- if (params.gasLimit != null && this.gasOverrides.minGasLimit) {
1369
+ if (mode !== 'reviewed-generic' &&
1370
+ params.gasLimit != null &&
1371
+ this.gasOverrides.minGasLimit) {
1270
1372
  const minGasLimit = this.gasOverrides.minGasLimit;
1271
1373
  const currentGasLimit = ethers_v6_1.BigNumber.from(params.gasLimit);
1272
1374
  if (currentGasLimit.lt(minGasLimit)) {
@@ -1337,31 +1439,15 @@ class EthereumTransactions {
1337
1439
  type: 2, // Need explicit type for hardware wallet serialization
1338
1440
  };
1339
1441
  const rawTx = serializeTransaction(txFormattedForEthers);
1340
- const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
1341
- rawTx: rawTx.replace('0x', ''),
1442
+ const { rawTransaction: signedTx } = await (0, ledger_signing_1.signLedgerEvmTransaction)(this.requireLedgerSigner(), {
1443
+ unsignedTransaction: rawTx,
1342
1444
  accountIndex: activeAccountId,
1343
1445
  expectedAddress: activeAccount.address,
1344
1446
  }, await this.authorizeHardwareTransaction(provider, assertCurrent));
1345
1447
  assertCurrent();
1346
- const formattedSignature = {
1347
- r: `0x${signature.r}`,
1348
- s: `0x${signature.s}`,
1349
- v: parseInt(signature.v, 16),
1350
- };
1351
- if (signature) {
1352
- try {
1353
- const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
1354
- assertHardwareTransactionSender(signedTx, activeAccount.address);
1355
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
1356
- return finalTx;
1357
- }
1358
- catch (error) {
1359
- throw error;
1360
- }
1361
- }
1362
- else {
1363
- throw new Error(`Transaction Signature Failed. Error: ${signature}`);
1364
- }
1448
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
1449
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
1450
+ return finalTx;
1365
1451
  };
1366
1452
  const sendEVMTrezorTransaction = async () => {
1367
1453
  assertCurrent();
@@ -1455,7 +1541,7 @@ class EthereumTransactions {
1455
1541
  };
1456
1542
  const sendEVMTransaction = async () => {
1457
1543
  assertCurrent();
1458
- const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
1544
+ const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey(targetAccount);
1459
1545
  // Validate that we have the correct private key for the active account to prevent race conditions
1460
1546
  // This is critical for transaction security during account switches
1461
1547
  if (address.toLowerCase() !== activeAccount.address.toLowerCase()) {
@@ -1712,32 +1798,18 @@ class EthereumTransactions {
1712
1798
  };
1713
1799
  const rawTx = serializeTransaction(txFormattedForEthers);
1714
1800
  assertCurrent();
1715
- const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
1716
- rawTx: rawTx.replace('0x', ''),
1801
+ const { rawTransaction: signedTx } = await (0, ledger_signing_1.signLedgerEvmTransaction)(this.requireLedgerSigner(), {
1802
+ unsignedTransaction: rawTx,
1717
1803
  accountIndex: activeAccountId,
1718
1804
  expectedAddress: activeAccount.address,
1719
1805
  }, await this.authorizeHardwareTransaction(provider, assertCurrent));
1720
- const formattedSignature = {
1721
- r: `0x${signature.r}`,
1722
- s: `0x${signature.s}`,
1723
- v: parseInt(signature.v, 16),
1806
+ assertSignedSender(signedTx);
1807
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
1808
+ const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
1809
+ return {
1810
+ isSpeedUp: true,
1811
+ transaction: transactionResponse,
1724
1812
  };
1725
- if (signature) {
1726
- const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
1727
- assertSignedSender(signedTx);
1728
- assertHardwareTransactionSender(signedTx, activeAccount.address);
1729
- const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
1730
- return {
1731
- isSpeedUp: true,
1732
- transaction: transactionResponse,
1733
- };
1734
- }
1735
- else {
1736
- return {
1737
- isSpeedUp: false,
1738
- error: true,
1739
- };
1740
- }
1741
1813
  }
1742
1814
  catch (error) {
1743
1815
  console.error('[SpeedUp] Failed to send replacement transaction with Ledger:', error);
@@ -1950,73 +2022,51 @@ class EthereumTransactions {
1950
2022
  };
1951
2023
  const sendERC20TokenOnLedger = async () => {
1952
2024
  assertCurrent();
1953
- try {
1954
- const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
1955
- const txData = _contract.interface.encodeFunctionData('transfer', [
1956
- receiver,
1957
- exactTokenAmount,
1958
- ]);
1959
- let txFormattedForEthers;
1960
- if (isLegacy) {
1961
- txFormattedForEthers = {
1962
- to: tokenAddress,
1963
- value: '0x0',
1964
- gasLimit: gasLimit,
1965
- gasPrice,
1966
- data: txData,
1967
- nonce,
1968
- chainId: activeNetwork.chainId,
1969
- type: 0,
1970
- };
1971
- }
1972
- else {
1973
- txFormattedForEthers = {
1974
- to: tokenAddress,
1975
- value: '0x0',
1976
- gasLimit: gasLimit,
1977
- maxFeePerGas,
1978
- maxPriorityFeePerGas,
1979
- data: txData,
1980
- nonce,
1981
- chainId: activeNetwork.chainId,
1982
- type: 2, // Need explicit type for hardware wallet serialization
1983
- };
1984
- }
1985
- const rawTx = serializeTransaction(txFormattedForEthers);
1986
- const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
1987
- rawTx: rawTx.replace('0x', ''),
1988
- accountIndex: activeAccountId,
1989
- expectedAddress: activeAccount.address,
1990
- }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
1991
- assertCurrent();
1992
- const formattedSignature = {
1993
- r: `0x${signature.r}`,
1994
- s: `0x${signature.s}`,
1995
- v: parseInt(signature.v, 16),
2025
+ const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
2026
+ const txData = _contract.interface.encodeFunctionData('transfer', [
2027
+ receiver,
2028
+ exactTokenAmount,
2029
+ ]);
2030
+ let txFormattedForEthers;
2031
+ if (isLegacy) {
2032
+ txFormattedForEthers = {
2033
+ to: tokenAddress,
2034
+ value: '0x0',
2035
+ gasLimit: gasLimit,
2036
+ gasPrice,
2037
+ data: txData,
2038
+ nonce,
2039
+ chainId: activeNetwork.chainId,
2040
+ type: 0,
1996
2041
  };
1997
- if (signature) {
1998
- try {
1999
- const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
2000
- assertCurrent();
2001
- assertHardwareTransactionSender(signedTx, activeAccount.address);
2002
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async ({ rawTransaction, admit }) => {
2003
- const response = await admit(() => provider.sendTransaction(rawTransaction));
2004
- saveTrezorTx && saveTrezorTx(response);
2005
- return response;
2006
- }, submission, undefined, rawTx);
2007
- return finalTx;
2008
- }
2009
- catch (error) {
2010
- throw error;
2011
- }
2012
- }
2013
- else {
2014
- throw new Error(`Transaction Signature Failed. Error: ${signature}`);
2015
- }
2016
2042
  }
2017
- catch (error) {
2018
- throw error;
2043
+ else {
2044
+ txFormattedForEthers = {
2045
+ to: tokenAddress,
2046
+ value: '0x0',
2047
+ gasLimit: gasLimit,
2048
+ maxFeePerGas,
2049
+ maxPriorityFeePerGas,
2050
+ data: txData,
2051
+ nonce,
2052
+ chainId: activeNetwork.chainId,
2053
+ type: 2, // Need explicit type for hardware wallet serialization
2054
+ };
2019
2055
  }
2056
+ const rawTx = serializeTransaction(txFormattedForEthers);
2057
+ const { rawTransaction: signedTx } = await (0, ledger_signing_1.signLedgerEvmTransaction)(this.requireLedgerSigner(), {
2058
+ unsignedTransaction: rawTx,
2059
+ accountIndex: activeAccountId,
2060
+ expectedAddress: activeAccount.address,
2061
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2062
+ assertCurrent();
2063
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
2064
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async ({ rawTransaction, admit }) => {
2065
+ const response = await admit(() => provider.sendTransaction(rawTransaction));
2066
+ saveTrezorTx && saveTrezorTx(response);
2067
+ return response;
2068
+ }, submission, undefined, rawTx);
2069
+ return finalTx;
2020
2070
  };
2021
2071
  const sendERC20TokenOnTrezor = async () => {
2022
2072
  assertCurrent();
@@ -2177,70 +2227,48 @@ class EthereumTransactions {
2177
2227
  };
2178
2228
  const sendERC721TokenOnLedger = async () => {
2179
2229
  assertCurrent();
2180
- try {
2181
- const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc21Abi)(), provider);
2182
- const txData = _contract.interface.encodeFunctionData('transferFrom', [
2183
- activeAccountAddress,
2184
- receiver,
2185
- normalizedTokenId,
2186
- ]);
2187
- let txFormattedForEthers;
2188
- if (isLegacy) {
2189
- txFormattedForEthers = {
2190
- to: tokenAddress,
2191
- value: '0x0',
2192
- gasLimit,
2193
- gasPrice,
2194
- data: txData,
2195
- nonce,
2196
- chainId: activeNetwork.chainId,
2197
- type: 0,
2198
- };
2199
- }
2200
- else {
2201
- txFormattedForEthers = {
2202
- to: tokenAddress,
2203
- value: '0x0',
2204
- gasLimit,
2205
- maxFeePerGas,
2206
- maxPriorityFeePerGas,
2207
- data: txData,
2208
- nonce,
2209
- chainId: activeNetwork.chainId,
2210
- type: 2, // Need explicit type for hardware wallet serialization
2211
- };
2212
- }
2213
- const rawTx = serializeTransaction(txFormattedForEthers);
2214
- const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
2215
- rawTx: rawTx.replace('0x', ''),
2216
- accountIndex: activeAccountId,
2217
- expectedAddress: activeAccount.address,
2218
- }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2219
- assertCurrent();
2220
- const formattedSignature = {
2221
- r: `0x${signature.r}`,
2222
- s: `0x${signature.s}`,
2223
- v: parseInt(signature.v, 16),
2230
+ const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc21Abi)(), provider);
2231
+ const txData = _contract.interface.encodeFunctionData('transferFrom', [
2232
+ activeAccountAddress,
2233
+ receiver,
2234
+ normalizedTokenId,
2235
+ ]);
2236
+ let txFormattedForEthers;
2237
+ if (isLegacy) {
2238
+ txFormattedForEthers = {
2239
+ to: tokenAddress,
2240
+ value: '0x0',
2241
+ gasLimit,
2242
+ gasPrice,
2243
+ data: txData,
2244
+ nonce,
2245
+ chainId: activeNetwork.chainId,
2246
+ type: 0,
2224
2247
  };
2225
- if (signature) {
2226
- try {
2227
- const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
2228
- assertCurrent();
2229
- assertHardwareTransactionSender(signedTx, activeAccount.address);
2230
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
2231
- return finalTx;
2232
- }
2233
- catch (error) {
2234
- throw error;
2235
- }
2236
- }
2237
- else {
2238
- throw new Error(`Transaction Signature Failed. Error: ${signature}`);
2239
- }
2240
2248
  }
2241
- catch (error) {
2242
- throw error;
2249
+ else {
2250
+ txFormattedForEthers = {
2251
+ to: tokenAddress,
2252
+ value: '0x0',
2253
+ gasLimit,
2254
+ maxFeePerGas,
2255
+ maxPriorityFeePerGas,
2256
+ data: txData,
2257
+ nonce,
2258
+ chainId: activeNetwork.chainId,
2259
+ type: 2, // Need explicit type for hardware wallet serialization
2260
+ };
2243
2261
  }
2262
+ const rawTx = serializeTransaction(txFormattedForEthers);
2263
+ const { rawTransaction: signedTx } = await (0, ledger_signing_1.signLedgerEvmTransaction)(this.requireLedgerSigner(), {
2264
+ unsignedTransaction: rawTx,
2265
+ accountIndex: activeAccountId,
2266
+ expectedAddress: activeAccount.address,
2267
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2268
+ assertCurrent();
2269
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
2270
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
2271
+ return finalTx;
2244
2272
  };
2245
2273
  const sendERC721TokenOnTrezor = async () => {
2246
2274
  assertCurrent();
@@ -2407,66 +2435,44 @@ class EthereumTransactions {
2407
2435
  };
2408
2436
  const sendERC1155TokenOnLedger = async () => {
2409
2437
  assertCurrent();
2410
- try {
2411
- const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
2412
- const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, normalizedTokenId, amount, '0x']);
2413
- let txFormattedForEthers;
2414
- if (isLegacy) {
2415
- txFormattedForEthers = {
2416
- to: tokenAddress,
2417
- value: '0x0',
2418
- gasLimit,
2419
- gasPrice,
2420
- data: txData,
2421
- nonce,
2422
- chainId: activeNetwork.chainId,
2423
- type: 0,
2424
- };
2425
- }
2426
- else {
2427
- txFormattedForEthers = {
2428
- to: tokenAddress,
2429
- value: '0x0',
2430
- gasLimit,
2431
- maxFeePerGas,
2432
- maxPriorityFeePerGas,
2433
- data: txData,
2434
- nonce,
2435
- chainId: activeNetwork.chainId,
2436
- type: 2, // Need explicit type for hardware wallet serialization
2437
- };
2438
- }
2439
- const rawTx = serializeTransaction(txFormattedForEthers);
2440
- const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
2441
- rawTx: rawTx.replace('0x', ''),
2442
- accountIndex: activeAccountId,
2443
- expectedAddress: activeAccount.address,
2444
- }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2445
- assertCurrent();
2446
- const formattedSignature = {
2447
- r: `0x${signature.r}`,
2448
- s: `0x${signature.s}`,
2449
- v: parseInt(signature.v, 16),
2438
+ const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
2439
+ const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, normalizedTokenId, amount, '0x']);
2440
+ let txFormattedForEthers;
2441
+ if (isLegacy) {
2442
+ txFormattedForEthers = {
2443
+ to: tokenAddress,
2444
+ value: '0x0',
2445
+ gasLimit,
2446
+ gasPrice,
2447
+ data: txData,
2448
+ nonce,
2449
+ chainId: activeNetwork.chainId,
2450
+ type: 0,
2450
2451
  };
2451
- if (signature) {
2452
- try {
2453
- const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
2454
- assertCurrent();
2455
- assertHardwareTransactionSender(signedTx, activeAccount.address);
2456
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
2457
- return finalTx;
2458
- }
2459
- catch (error) {
2460
- throw error;
2461
- }
2462
- }
2463
- else {
2464
- throw new Error(`Transaction Signature Failed. Error: ${signature}`);
2465
- }
2466
2452
  }
2467
- catch (error) {
2468
- throw error;
2453
+ else {
2454
+ txFormattedForEthers = {
2455
+ to: tokenAddress,
2456
+ value: '0x0',
2457
+ gasLimit,
2458
+ maxFeePerGas,
2459
+ maxPriorityFeePerGas,
2460
+ data: txData,
2461
+ nonce,
2462
+ chainId: activeNetwork.chainId,
2463
+ type: 2, // Need explicit type for hardware wallet serialization
2464
+ };
2469
2465
  }
2466
+ const rawTx = serializeTransaction(txFormattedForEthers);
2467
+ const { rawTransaction: signedTx } = await (0, ledger_signing_1.signLedgerEvmTransaction)(this.requireLedgerSigner(), {
2468
+ unsignedTransaction: rawTx,
2469
+ accountIndex: activeAccountId,
2470
+ expectedAddress: activeAccount.address,
2471
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2472
+ assertCurrent();
2473
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
2474
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
2475
+ return finalTx;
2470
2476
  };
2471
2477
  const sendERC1155TokenOnTrezor = async () => {
2472
2478
  assertCurrent();
@@ -2788,6 +2794,52 @@ class EthereumTransactions {
2788
2794
  console.log('[EthereumTransactions] Deferring provider initialization:', error?.message || error);
2789
2795
  }
2790
2796
  }
2797
+ signPreparedEvmTransaction(input, options) {
2798
+ const selected = this.captureEvmAccountContext();
2799
+ const request = (0, prepared_evm_1.snapshotPreparedEvmRequest)(input);
2800
+ const target = {
2801
+ id: request.account.id,
2802
+ type: request.account.type,
2803
+ };
2804
+ const context = this.captureEvmAccountContext(target);
2805
+ const hostGuard = (0, prepared_evm_1.preparedEvmGuard)(options);
2806
+ const targetRecord = this.getState().accounts[target.type]?.[target.id];
2807
+ const encryptedKey = targetRecord?.xprv;
2808
+ const assertCurrent = () => {
2809
+ selected.assertCurrent();
2810
+ context.assertCurrent();
2811
+ const live = this.getState().accounts[target.type]?.[target.id];
2812
+ if (!live ||
2813
+ live.id !== target.id ||
2814
+ live.isSmartAccount ||
2815
+ live.isLedgerWallet ||
2816
+ live.isTrezorWallet ||
2817
+ typeof encryptedKey !== 'string' ||
2818
+ !encryptedKey ||
2819
+ live.xprv !== encryptedKey ||
2820
+ selected.activeAccountId !== request.selectedAccount.id ||
2821
+ selected.activeAccountType !== request.selectedAccount.type ||
2822
+ (0, address_1.getAddress)(selected.activeAccount.address) !==
2823
+ request.selectedAccount.expectedAddress ||
2824
+ (0, address_1.getAddress)(context.activeAccount.address) !==
2825
+ request.account.expectedAddress ||
2826
+ context.activeNetwork.kind !== 'ethereum' ||
2827
+ context.activeNetwork.slip44 !== 60 ||
2828
+ String(context.activeNetwork.chainId) !== request.network.chainId ||
2829
+ context.activeNetwork.url !== request.network.url)
2830
+ throw new Error('Prepared EVM signing context changed or is unsupported');
2831
+ };
2832
+ assertCurrent();
2833
+ hostGuard();
2834
+ assertCurrent();
2835
+ const key = this.getDecryptedPrivateKey(target);
2836
+ if ((0, address_1.getAddress)(key.address) !== request.account.expectedAddress)
2837
+ throw new Error('Prepared EVM signer address mismatch');
2838
+ const { signed } = (0, prepared_evm_1.signPreparedEvmEnvelope)(request.transaction, key.decryptedPrivateKey);
2839
+ hostGuard();
2840
+ assertCurrent();
2841
+ return signed;
2842
+ }
2791
2843
  setWeb3Provider(network) {
2792
2844
  this.abortController.abort();
2793
2845
  this.abortController = new AbortController();