@sidhujag/sysweb3-keyring 2.1.7 → 2.1.9

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 (105) 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 +371 -494
  24. package/cjs/transactions/ethereum.js.map +1 -1
  25. package/cjs/transactions/evm-submission-failure.js +73 -0
  26. package/cjs/transactions/evm-submission-failure.js.map +1 -0
  27. package/cjs/transactions/evm-submission.js +35 -61
  28. package/cjs/transactions/evm-submission.js.map +1 -1
  29. package/cjs/transactions/ledger-signing.js +52 -0
  30. package/cjs/transactions/ledger-signing.js.map +1 -0
  31. package/cjs/transactions/prepared-evm.js +24 -1
  32. package/cjs/transactions/prepared-evm.js.map +1 -1
  33. package/cjs/transactions/trezor-signing.js +90 -0
  34. package/cjs/transactions/trezor-signing.js.map +1 -0
  35. package/cjs/trezor/connect.js +238 -243
  36. package/cjs/trezor/connect.js.map +1 -1
  37. package/cjs/trezor/host-types.js +19 -0
  38. package/cjs/trezor/host-types.js.map +1 -0
  39. package/cjs/trezor/index.js +75 -23
  40. package/cjs/trezor/index.js.map +1 -1
  41. package/cjs/trezor/webextension-host.js +118 -0
  42. package/cjs/trezor/webextension-host.js.map +1 -0
  43. package/cjs/trezor-hardware.js +16 -0
  44. package/cjs/trezor-hardware.js.map +1 -0
  45. package/cjs/types.js.map +1 -1
  46. package/esm/hardware-transaction-authority.js +14 -11
  47. package/esm/hardware-transaction-authority.js.map +1 -1
  48. package/esm/hardware-wallet-manager.js +20 -32
  49. package/esm/hardware-wallet-manager.js.map +1 -1
  50. package/esm/hardware-wallet-types.js +13 -0
  51. package/esm/hardware-wallet-types.js.map +1 -0
  52. package/esm/keyring-core.js +98 -63
  53. package/esm/keyring-core.js.map +1 -1
  54. package/esm/ledger/dmk-host.js +2 -0
  55. package/esm/ledger/dmk-host.js.map +1 -1
  56. package/esm/ledger/dmk-session.js +73 -8
  57. package/esm/ledger/dmk-session.js.map +1 -1
  58. package/esm/ledger/host-types.js.map +1 -1
  59. package/esm/ledger/index.js +22 -2
  60. package/esm/ledger/index.js.map +1 -1
  61. package/esm/transactions/ethereum.js +372 -495
  62. package/esm/transactions/ethereum.js.map +1 -1
  63. package/esm/transactions/evm-submission-failure.js +68 -0
  64. package/esm/transactions/evm-submission-failure.js.map +1 -0
  65. package/esm/transactions/evm-submission.js +32 -59
  66. package/esm/transactions/evm-submission.js.map +1 -1
  67. package/esm/transactions/ledger-signing.js +49 -0
  68. package/esm/transactions/ledger-signing.js.map +1 -0
  69. package/esm/transactions/prepared-evm.js +23 -1
  70. package/esm/transactions/prepared-evm.js.map +1 -1
  71. package/esm/transactions/trezor-signing.js +87 -0
  72. package/esm/transactions/trezor-signing.js.map +1 -0
  73. package/esm/trezor/connect.js +236 -209
  74. package/esm/trezor/connect.js.map +1 -1
  75. package/esm/trezor/host-types.js +16 -0
  76. package/esm/trezor/host-types.js.map +1 -0
  77. package/esm/trezor/index.js +74 -22
  78. package/esm/trezor/index.js.map +1 -1
  79. package/esm/types.js.map +1 -1
  80. package/package.json +61 -1
  81. package/types/hardware-transaction-authority.d.ts +1 -0
  82. package/types/hardware-wallet-manager.d.ts +6 -22
  83. package/types/hardware-wallet-types.d.ts +21 -0
  84. package/types/host-keyring.d.ts +16 -0
  85. package/types/index.d.ts +1 -0
  86. package/types/keyring-core.d.ts +12 -4
  87. package/types/keyring-manager.d.ts +5 -3
  88. package/types/ledger/dmk-host.d.ts +2 -0
  89. package/types/ledger/dmk-session.d.ts +9 -0
  90. package/types/ledger/host-types.d.ts +2 -0
  91. package/types/ledger/index.d.ts +3 -1
  92. package/types/ledger-hardware.d.ts +5 -0
  93. package/types/software.d.ts +2 -16
  94. package/types/transactions/ethereum.d.ts +11 -1
  95. package/types/transactions/evm-submission-failure.d.ts +11 -0
  96. package/types/transactions/evm-submission.d.ts +6 -8
  97. package/types/transactions/ledger-signing.d.ts +13 -0
  98. package/types/transactions/prepared-evm.d.ts +12 -0
  99. package/types/transactions/trezor-signing.d.ts +13 -0
  100. package/types/trezor/connect.d.ts +11 -9
  101. package/types/trezor/host-types.d.ts +25 -0
  102. package/types/trezor/index.d.ts +7 -7
  103. package/types/trezor/webextension-host.d.ts +7 -0
  104. package/types/trezor-hardware.d.ts +6 -0
  105. 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,12 @@ 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';
18
+ import { signTrezorEvmTransaction } from './trezor-signing';
15
19
  import { getAddressDerivationPath } from '../utils/derivation-paths';
16
20
  const hardwareSignerUnavailable = (hardware) => Object.assign(new Error(`${hardware} signer is unavailable in this host`), {
17
21
  code: 'HARDWARE_SIGNER_UNAVAILABLE',
@@ -29,26 +33,6 @@ const stripHexPrefix = (value) => {
29
33
  ? value.slice(2)
30
34
  : value;
31
35
  };
32
- // Connect returns hexadecimal signature fields; normalize their encoding
33
- // without mutating the response or reparsing an already numeric recovery v.
34
- const trezorSignature = (signature) => {
35
- const { r, s, v } = signature;
36
- if (![r, s].every((value) => /^(?:0x)?[0-9a-f]{64}$/i.test(value))) {
37
- throw new Error('Invalid Trezor signature scalar encoding');
38
- }
39
- if (typeof v === 'string' && !/^(?:0x)?[0-9a-f]+$/i.test(v)) {
40
- throw new Error('Invalid Trezor signature recovery value');
41
- }
42
- const recovery = typeof v === 'number' ? v : Number.parseInt(v, 16);
43
- if (!Number.isSafeInteger(recovery) || recovery < 0) {
44
- throw new Error('Invalid Trezor signature recovery value');
45
- }
46
- return {
47
- r: `0x${stripHexPrefix(r)}`,
48
- s: `0x${stripHexPrefix(s)}`,
49
- v: recovery,
50
- };
51
- };
52
36
  const normalizeEthersOverrideValue = (value) => normalizeTxValue(value);
53
37
  // The same protocol bound applies to software and external-device envelopes.
54
38
  // Keep this at serialization as well as before handing a request to a device.
@@ -154,7 +138,7 @@ export class EthereumTransactions {
154
138
  }
155
139
  return () => assertSynchronousOriginalValidation(guard(), 'Signing context guard must be synchronous');
156
140
  };
157
- this.captureEvmSigningContext = (target) => {
141
+ this.captureEvmAccountContext = (target) => {
158
142
  this.assertHardwareSignerAvailable(target);
159
143
  const state = this.getState();
160
144
  const selectedType = state.activeAccountType;
@@ -168,7 +152,6 @@ export class EthereumTransactions {
168
152
  const account = { address: selectedAccount.address };
169
153
  const network = { ...state.activeNetwork };
170
154
  const assertSession = this.captureSessionGuard();
171
- const provider = this.web3Provider;
172
155
  const assertCurrent = () => {
173
156
  assertSession();
174
157
  const current = this.getState();
@@ -182,8 +165,7 @@ export class EthereumTransactions {
182
165
  [current.activeNetwork, this.getNetwork()].some((selected) => selected.kind !== network.kind ||
183
166
  selected.chainId !== network.chainId ||
184
167
  selected.slip44 !== network.slip44 ||
185
- selected.url !== network.url) ||
186
- this.web3Provider !== provider)
168
+ selected.url !== network.url))
187
169
  throw new Error('Wallet signing context changed');
188
170
  };
189
171
  assertCurrent();
@@ -192,10 +174,20 @@ export class EthereumTransactions {
192
174
  activeAccountType,
193
175
  activeAccountId,
194
176
  activeNetwork: network,
195
- provider,
196
177
  assertCurrent,
197
178
  };
198
179
  };
180
+ this.captureEvmSigningContext = (target) => {
181
+ const context = this.captureEvmAccountContext(target);
182
+ const provider = this.web3Provider;
183
+ const assertCurrent = () => {
184
+ context.assertCurrent();
185
+ if (this.web3Provider !== provider)
186
+ throw new Error('Wallet signing context changed');
187
+ };
188
+ assertCurrent();
189
+ return { ...context, provider, assertCurrent };
190
+ };
199
191
  this.captureSubmission = (operation, account, type, id, network, assertCurrent, details = {}) => {
200
192
  if (!this.captureSubmissionLifecycle)
201
193
  throw new TypeError('A durable EVM submission lifecycle is required');
@@ -296,10 +288,51 @@ export class EthereumTransactions {
296
288
  throw error;
297
289
  }
298
290
  };
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];
291
+ this.ethSign = async (params, input) => {
292
+ const selected = this.captureEvmAccountContext();
293
+ if (!Array.isArray(params) || params.length !== 2)
294
+ throw new Error('Invalid digest request');
295
+ const first = Object.getOwnPropertyDescriptor(params, '0');
296
+ const second = Object.getOwnPropertyDescriptor(params, '1');
297
+ if (!first ||
298
+ !second ||
299
+ !('value' in first) ||
300
+ !('value' in second) ||
301
+ typeof first.value !== 'string' ||
302
+ typeof second.value !== 'string')
303
+ throw new Error('Invalid digest request');
304
+ params = [first.value, second.value];
305
+ const options = readEvmOwnData(input, [
306
+ 'selectedAccount',
307
+ 'targetAccount',
308
+ 'assertCurrent',
309
+ ]);
310
+ const selectedAccount = snapshotEvmSigningIdentity(options.selectedAccount);
311
+ const targetAccount = snapshotEvmSigningIdentity(options.targetAccount);
312
+ const hostGuard = preparedEvmGuard({
313
+ assertCurrent: options.assertCurrent,
314
+ });
315
+ const target = {
316
+ id: targetAccount.id,
317
+ type: targetAccount.type,
318
+ };
319
+ const context = this.captureEvmAccountContext(target);
320
+ const assertCurrent = () => {
321
+ hostGuard();
322
+ selected.assertCurrent();
323
+ context.assertCurrent();
324
+ if (selected.activeAccountId !== selectedAccount.id ||
325
+ selected.activeAccountType !== selectedAccount.type ||
326
+ getAddress(selected.activeAccount.address) !==
327
+ selectedAccount.expectedAddress ||
328
+ getAddress(context.activeAccount.address) !==
329
+ targetAccount.expectedAddress ||
330
+ targetAccount.type === 'SmartAccount')
331
+ throw new Error('EVM digest signing context changed');
332
+ };
333
+ assertCurrent();
334
+ const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey(target);
335
+ const { activeAccountId, activeAccountType, activeNetwork, activeAccount } = context;
303
336
  // Validate that the derived address matches the active account to prevent race conditions
304
337
  if (address.toLowerCase() !== activeAccount.address.toLowerCase()) {
305
338
  throw {
@@ -368,14 +401,19 @@ export class EthereumTransactions {
368
401
  throw error;
369
402
  }
370
403
  };
404
+ let signature;
371
405
  switch (activeAccountType) {
372
406
  case KeyringAccountType.Trezor:
373
- return await signWithTrezor();
407
+ signature = await signWithTrezor();
408
+ break;
374
409
  case KeyringAccountType.Ledger:
375
- return await signWithLedger();
410
+ signature = await signWithLedger();
411
+ break;
376
412
  default:
377
- return sign();
413
+ signature = sign();
378
414
  }
415
+ assertCurrent();
416
+ return signature;
379
417
  };
380
418
  this.signPersonalMessage = async (params) => {
381
419
  const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
@@ -945,33 +983,18 @@ export class EthereumTransactions {
945
983
  const rawTx = serializeTransaction(txFormattedForEthers);
946
984
  if (retained)
947
985
  assertCancellationEnvelope(rawTx);
948
- const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
949
- rawTx: rawTx.replace('0x', ''),
986
+ const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
987
+ unsignedTransaction: rawTx,
950
988
  accountIndex: activeAccountId,
951
989
  expectedAddress: activeAccount.address,
952
990
  }, await this.authorizeHardwareTransaction(provider, assertCurrent));
953
991
  assertCurrent();
954
- const formattedSignature = {
955
- r: `0x${signature.r}`,
956
- s: `0x${signature.s}`,
957
- v: parseInt(signature.v, 16),
992
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
993
+ const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
994
+ return {
995
+ isCanceled: true,
996
+ transaction: transactionResponse,
958
997
  };
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
998
  }
976
999
  catch (error) {
977
1000
  return {
@@ -1019,29 +1042,20 @@ export class EthereumTransactions {
1019
1042
  const expectedUnsignedTransaction = serializeTransaction(txFormattedForTrezor);
1020
1043
  if (retained)
1021
1044
  assertCancellationEnvelope(expectedUnsignedTransaction);
1022
- const signature = await this.requireTrezorSigner().signEthTransaction({
1045
+ const { rawTransaction: signedTx } = await signTrezorEvmTransaction(this.requireTrezorSigner(), {
1046
+ accountIndex: activeAccountId,
1023
1047
  coin: trezorCoin,
1024
- tx: txFormattedForTrezor,
1025
- index: activeAccountId.toString(),
1048
+ expectedAddress: activeAccount.address,
1026
1049
  slip44: activeNetwork.slip44,
1050
+ unsignedTransaction: expectedUnsignedTransaction,
1027
1051
  }, await this.authorizeHardwareTransaction(provider, assertCurrent));
1028
1052
  assertCurrent();
1029
- if (signature.success) {
1030
- const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
1031
- assertCurrent();
1032
- assertHardwareTransactionSender(signedTx, activeAccount.address);
1033
- const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, expectedUnsignedTransaction);
1034
- return {
1035
- isCanceled: true,
1036
- transaction: transactionResponse,
1037
- };
1038
- }
1039
- else {
1040
- return {
1041
- isCanceled: false,
1042
- error: true,
1043
- };
1044
- }
1053
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
1054
+ const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, expectedUnsignedTransaction);
1055
+ return {
1056
+ isCanceled: true,
1057
+ transaction: transactionResponse,
1058
+ };
1045
1059
  }
1046
1060
  catch (error) {
1047
1061
  return {
@@ -1102,12 +1116,21 @@ export class EthereumTransactions {
1102
1116
  typeof options !== 'object' ||
1103
1117
  Array.isArray(options) ||
1104
1118
  (options.mode !== 'reviewed-native' &&
1119
+ options.mode !== 'reviewed-generic' &&
1105
1120
  options.mode !== 'specialized') ||
1106
1121
  typeof options.assertCurrent !== 'function')
1107
1122
  throw new Error('Explicit transaction approval is required');
1108
1123
  // Capture once; later caller mutations cannot replace this invocation's
1109
1124
  // admission decision while chain checks or device approval are pending.
1110
1125
  const { assertCurrent: assertHostCurrent, mode, walletNonce } = options;
1126
+ const reviewId = mode === 'reviewed-generic' ? options.reviewId : undefined;
1127
+ if (mode === 'reviewed-generic' &&
1128
+ (typeof reviewId !== 'string' ||
1129
+ !/^[A-Za-z0-9_-]{1,128}$/.test(reviewId) ||
1130
+ walletNonce === undefined))
1131
+ throw new Error('An issued fee review and approved nonce are required');
1132
+ const selectedAccount = snapshotEvmSigningIdentity(options.selectedAccount);
1133
+ const targetAccount = snapshotEvmSigningIdentity(options.targetAccount);
1111
1134
  const nonceApproval = walletNonce === undefined ? undefined : { ...walletNonce };
1112
1135
  const assertApproved = () => {
1113
1136
  const result = assertHostCurrent();
@@ -1119,14 +1142,14 @@ export class EthereumTransactions {
1119
1142
  assertApproved();
1120
1143
  return await this.sendFormattedTransactionInternal(params, isLegacy, () => {
1121
1144
  broadcastAttempted = true;
1122
- }, assertApproved, mode, nonceApproval);
1145
+ }, assertApproved, mode, nonceApproval, selectedAccount, targetAccount, reviewId);
1123
1146
  }
1124
1147
  catch (error) {
1125
1148
  // A broadcast transport error may mean the node accepted the signed
1126
1149
  // transaction. Derive this marker from this invocation, never RPC data.
1127
1150
  const submissionError = Object.assign(new Error(error instanceof Error ? error.message : String(error)), error, {
1128
1151
  transactionNotBroadcast: !broadcastAttempted ||
1129
- evmSubmissionFailure(error).transactionNotBroadcast,
1152
+ evmSubmissionFailure(error)?.transactionNotBroadcast === true,
1130
1153
  });
1131
1154
  if (error instanceof Error) {
1132
1155
  submissionError.name = error.name;
@@ -1136,19 +1159,60 @@ export class EthereumTransactions {
1136
1159
  throw submissionError;
1137
1160
  }
1138
1161
  };
1139
- this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode, walletNonce) => {
1162
+ this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode, walletNonce, selectedIdentity, targetIdentity, reviewId) => {
1140
1163
  // Hardware approval may outlive the selected account, network or unlocked
1141
1164
  // session. Keep immutable identities and one provider for this invocation.
1142
- const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertKeyringCurrent, } = this.captureEvmSigningContext();
1165
+ const selectedContext = this.captureEvmAccountContext();
1166
+ const targetAccount = {
1167
+ id: targetIdentity.id,
1168
+ type: targetIdentity.type,
1169
+ };
1170
+ if (targetAccount.type === KeyringAccountType.SmartAccount)
1171
+ throw new Error('Invalid EVM payer');
1172
+ const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertKeyringCurrent, } = this.captureEvmSigningContext(targetAccount);
1143
1173
  const assertSigningCurrent = () => {
1144
1174
  assertHostCurrent();
1175
+ selectedContext.assertCurrent();
1145
1176
  assertKeyringCurrent();
1177
+ if (selectedContext.activeAccountId !== selectedIdentity.id ||
1178
+ selectedContext.activeAccountType !== selectedIdentity.type ||
1179
+ getAddress(selectedContext.activeAccount.address) !==
1180
+ selectedIdentity.expectedAddress ||
1181
+ getAddress(activeAccount.address) !== targetIdentity.expectedAddress)
1182
+ throw new Error('EVM payer approval context changed');
1146
1183
  };
1184
+ assertSigningCurrent();
1185
+ // Generic reviewed calls keep the exact prepared envelope, including
1186
+ // contract creation, calldata and access lists, through existing signers.
1187
+ const reviewed = mode === 'reviewed-generic'
1188
+ ? snapshotPreparedEvmTransaction(input)
1189
+ : undefined;
1190
+ if (reviewed) {
1191
+ if (reviewed.from !== targetIdentity.expectedAddress ||
1192
+ reviewed.chainId !== String(activeNetwork.chainId) ||
1193
+ reviewed.nonce !== String(walletNonce?.nonce) ||
1194
+ (isLegacy !== undefined && isLegacy !== (reviewed.type === 0)) ||
1195
+ (this.gasOverrides.minGasLimit &&
1196
+ BigInt(reviewed.gasLimit) < this.gasOverrides.minGasLimit.toBigInt()))
1197
+ throw new Error('Reviewed EVM transaction does not match its approval');
1198
+ input = reviewed;
1199
+ isLegacy = reviewed.type === 0;
1200
+ }
1147
1201
  const assertBeforeBroadcast = () => {
1148
1202
  assertCurrent();
1149
1203
  beforeBroadcast();
1150
1204
  };
1151
- const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, { walletNonce });
1205
+ const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, {
1206
+ walletNonce,
1207
+ ...(reviewed
1208
+ ? {
1209
+ review: {
1210
+ id: reviewId,
1211
+ unsignedHash: Transaction.from(serializePreparedEvmTransaction(reviewed)).unsignedHash,
1212
+ },
1213
+ }
1214
+ : {}),
1215
+ });
1152
1216
  const assertCurrent = submission.assertCurrent;
1153
1217
  const approvedNonce = submission.context.walletNonce?.nonce;
1154
1218
  const assertApprovedNonce = (nonce) => {
@@ -1157,6 +1221,7 @@ export class EthereumTransactions {
1157
1221
  };
1158
1222
  if (mode === 'reviewed-native') {
1159
1223
  const { transaction } = snapshotPreparedEvmRequest({
1224
+ selectedAccount: selectedIdentity,
1160
1225
  account: {
1161
1226
  id: activeAccountId,
1162
1227
  type: activeAccountType,
@@ -1187,7 +1252,7 @@ export class EthereumTransactions {
1187
1252
  assertCurrent();
1188
1253
  if (this.gasOverrides.minGasLimit !== minimum)
1189
1254
  throw new Error('Reviewed native gas policy changed');
1190
- const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
1255
+ const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey(targetAccount);
1191
1256
  if (address.toLowerCase() !== transaction.from.toLowerCase())
1192
1257
  throw new Error('Reviewed native signer does not match the approved account');
1193
1258
  assertCurrent();
@@ -1200,6 +1265,9 @@ export class EthereumTransactions {
1200
1265
  if (!params || typeof params !== 'object' || Array.isArray(params))
1201
1266
  throw new TypeError('Invalid EVM transaction request');
1202
1267
  const supplied = { ...params };
1268
+ if (supplied.from !== undefined &&
1269
+ getAddress(supplied.from) !== targetIdentity.expectedAddress)
1270
+ throw new Error('EVM transaction sender does not match the approved payer');
1203
1271
  // Validate original scalar spellings before conversion. Normalization's
1204
1272
  // permissive zero coercion must not bypass the bounded wire parser.
1205
1273
  for (const field of [
@@ -1252,7 +1320,8 @@ export class EthereumTransactions {
1252
1320
  const validateEip1559Fees = () => {
1253
1321
  const maximum = nonNegativeFeeQuantity(params.maxFeePerGas, 'maximum fee');
1254
1322
  const priority = nonNegativeFeeQuantity(params.maxPriorityFeePerGas, 'priority fee');
1255
- if (maximum.isZero() || priority.gt(maximum))
1323
+ if ((mode !== 'reviewed-generic' && maximum.isZero()) ||
1324
+ priority.gt(maximum))
1256
1325
  throw new Error('Invalid EIP-1559 fees');
1257
1326
  params.maxFeePerGas = maximum;
1258
1327
  params.maxPriorityFeePerGas = priority;
@@ -1263,7 +1332,9 @@ export class EthereumTransactions {
1263
1332
  validateEip1559Fees();
1264
1333
  // Only apply a minimum gas limit when a network-specific override is set.
1265
1334
  // Otherwise keep the user/dapp-approved gas limit unchanged.
1266
- if (params.gasLimit != null && this.gasOverrides.minGasLimit) {
1335
+ if (mode !== 'reviewed-generic' &&
1336
+ params.gasLimit != null &&
1337
+ this.gasOverrides.minGasLimit) {
1267
1338
  const minGasLimit = this.gasOverrides.minGasLimit;
1268
1339
  const currentGasLimit = BigNumber.from(params.gasLimit);
1269
1340
  if (currentGasLimit.lt(minGasLimit)) {
@@ -1334,31 +1405,15 @@ export class EthereumTransactions {
1334
1405
  type: 2, // Need explicit type for hardware wallet serialization
1335
1406
  };
1336
1407
  const rawTx = serializeTransaction(txFormattedForEthers);
1337
- const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
1338
- rawTx: rawTx.replace('0x', ''),
1408
+ const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
1409
+ unsignedTransaction: rawTx,
1339
1410
  accountIndex: activeAccountId,
1340
1411
  expectedAddress: activeAccount.address,
1341
1412
  }, await this.authorizeHardwareTransaction(provider, assertCurrent));
1342
1413
  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
- }
1414
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
1415
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
1416
+ return finalTx;
1362
1417
  };
1363
1418
  const sendEVMTrezorTransaction = async () => {
1364
1419
  assertCurrent();
@@ -1415,44 +1470,21 @@ export class EthereumTransactions {
1415
1470
  ...txFormattedForTrezor,
1416
1471
  type: isLegacy ? 0 : 2,
1417
1472
  });
1418
- const signature = await this.requireTrezorSigner().signEthTransaction({
1419
- index: `${activeAccountId}`,
1420
- tx: txFormattedForTrezor,
1473
+ const { rawTransaction: signedTx } = await signTrezorEvmTransaction(this.requireTrezorSigner(), {
1474
+ accountIndex: activeAccountId,
1421
1475
  coin: activeNetwork.currency,
1476
+ expectedAddress: activeAccount.address,
1422
1477
  slip44: activeNetwork.slip44,
1478
+ unsignedTransaction: expectedUnsignedTransaction,
1423
1479
  }, await this.authorizeHardwareTransaction(provider, assertCurrent));
1424
1480
  assertCurrent();
1425
- if (signature.success) {
1426
- try {
1427
- const txFormattedForEthers = isLegacy
1428
- ? {
1429
- ...formatParams,
1430
- nonce: transactionNonce,
1431
- chainId: activeNetwork.chainId,
1432
- type: 0, // Force Type 0 for legacy
1433
- }
1434
- : {
1435
- ...formatParams,
1436
- nonce: transactionNonce,
1437
- chainId: activeNetwork.chainId,
1438
- type: 2, // Need explicit type for hardware wallet serialization
1439
- };
1440
- const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
1441
- assertHardwareTransactionSender(signedTx, activeAccount.address);
1442
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
1443
- return finalTx;
1444
- }
1445
- catch (error) {
1446
- throw error;
1447
- }
1448
- }
1449
- else {
1450
- throw new Error(`Transaction Signature Failed. Error: ${signature}`);
1451
- }
1481
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
1482
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
1483
+ return finalTx;
1452
1484
  };
1453
1485
  const sendEVMTransaction = async () => {
1454
1486
  assertCurrent();
1455
- const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
1487
+ const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey(targetAccount);
1456
1488
  // Validate that we have the correct private key for the active account to prevent race conditions
1457
1489
  // This is critical for transaction security during account switches
1458
1490
  if (address.toLowerCase() !== activeAccount.address.toLowerCase()) {
@@ -1709,32 +1741,18 @@ export class EthereumTransactions {
1709
1741
  };
1710
1742
  const rawTx = serializeTransaction(txFormattedForEthers);
1711
1743
  assertCurrent();
1712
- const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
1713
- rawTx: rawTx.replace('0x', ''),
1744
+ const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
1745
+ unsignedTransaction: rawTx,
1714
1746
  accountIndex: activeAccountId,
1715
1747
  expectedAddress: activeAccount.address,
1716
1748
  }, await this.authorizeHardwareTransaction(provider, assertCurrent));
1717
- const formattedSignature = {
1718
- r: `0x${signature.r}`,
1719
- s: `0x${signature.s}`,
1720
- v: parseInt(signature.v, 16),
1749
+ assertSignedSender(signedTx);
1750
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
1751
+ const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
1752
+ return {
1753
+ isSpeedUp: true,
1754
+ transaction: transactionResponse,
1721
1755
  };
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
1756
  }
1739
1757
  catch (error) {
1740
1758
  console.error('[SpeedUp] Failed to send replacement transaction with Ledger:', error);
@@ -1794,28 +1812,20 @@ export class EthereumTransactions {
1794
1812
  assertCurrent();
1795
1813
  assertTransactionGasLimit(txFormattedForTrezor);
1796
1814
  const expectedUnsignedTransaction = serializeTransaction(txFormattedForTrezor);
1797
- const signature = await this.requireTrezorSigner().signEthTransaction({
1815
+ const { rawTransaction: signedTx } = await signTrezorEvmTransaction(this.requireTrezorSigner(), {
1816
+ accountIndex: activeAccountId,
1798
1817
  coin: trezorCoin,
1799
- tx: txFormattedForTrezor,
1800
- index: activeAccountId.toString(),
1818
+ expectedAddress: activeAccount.address,
1801
1819
  slip44: activeNetwork.slip44,
1820
+ unsignedTransaction: expectedUnsignedTransaction,
1802
1821
  }, await this.authorizeHardwareTransaction(provider, assertCurrent));
1803
- if (signature.success) {
1804
- const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
1805
- assertSignedSender(signedTx);
1806
- assertHardwareTransactionSender(signedTx, activeAccount.address);
1807
- const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
1808
- return {
1809
- isSpeedUp: true,
1810
- transaction: transactionResponse,
1811
- };
1812
- }
1813
- else {
1814
- return {
1815
- isSpeedUp: false,
1816
- error: true,
1817
- };
1818
- }
1822
+ assertSignedSender(signedTx);
1823
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
1824
+ const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
1825
+ return {
1826
+ isSpeedUp: true,
1827
+ transaction: transactionResponse,
1828
+ };
1819
1829
  }
1820
1830
  catch (error) {
1821
1831
  console.error('[SpeedUp] Failed to send replacement transaction with Trezor:', error);
@@ -1947,73 +1957,51 @@ export class EthereumTransactions {
1947
1957
  };
1948
1958
  const sendERC20TokenOnLedger = async () => {
1949
1959
  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),
1960
+ const _contract = new Contract(tokenAddress, getErc20Abi(), provider);
1961
+ const txData = _contract.interface.encodeFunctionData('transfer', [
1962
+ receiver,
1963
+ exactTokenAmount,
1964
+ ]);
1965
+ let txFormattedForEthers;
1966
+ if (isLegacy) {
1967
+ txFormattedForEthers = {
1968
+ to: tokenAddress,
1969
+ value: '0x0',
1970
+ gasLimit: gasLimit,
1971
+ gasPrice,
1972
+ data: txData,
1973
+ nonce,
1974
+ chainId: activeNetwork.chainId,
1975
+ type: 0,
1993
1976
  };
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
1977
  }
2014
- catch (error) {
2015
- throw error;
1978
+ else {
1979
+ txFormattedForEthers = {
1980
+ to: tokenAddress,
1981
+ value: '0x0',
1982
+ gasLimit: gasLimit,
1983
+ maxFeePerGas,
1984
+ maxPriorityFeePerGas,
1985
+ data: txData,
1986
+ nonce,
1987
+ chainId: activeNetwork.chainId,
1988
+ type: 2, // Need explicit type for hardware wallet serialization
1989
+ };
2016
1990
  }
1991
+ const rawTx = serializeTransaction(txFormattedForEthers);
1992
+ const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
1993
+ unsignedTransaction: rawTx,
1994
+ accountIndex: activeAccountId,
1995
+ expectedAddress: activeAccount.address,
1996
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
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;
2017
2005
  };
2018
2006
  const sendERC20TokenOnTrezor = async () => {
2019
2007
  assertCurrent();
@@ -2052,58 +2040,21 @@ export class EthereumTransactions {
2052
2040
  ...txToBeSignedByTrezor,
2053
2041
  type: isLegacy ? 0 : 2,
2054
2042
  });
2055
- const signature = await this.requireTrezorSigner().signEthTransaction({
2056
- index: `${activeAccountId}`,
2057
- tx: txToBeSignedByTrezor,
2043
+ const { rawTransaction: signedTx } = await signTrezorEvmTransaction(this.requireTrezorSigner(), {
2044
+ accountIndex: activeAccountId,
2058
2045
  coin: activeNetwork.currency,
2046
+ expectedAddress: activeAccount.address,
2059
2047
  slip44: activeNetwork.slip44,
2048
+ unsignedTransaction: expectedUnsignedTransaction,
2060
2049
  }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2061
2050
  assertCurrent();
2062
- if (signature.success) {
2063
- try {
2064
- let txFormattedForEthers;
2065
- if (isLegacy) {
2066
- txFormattedForEthers = {
2067
- to: tokenAddress,
2068
- value: '0x0',
2069
- gasLimit: gasLimit,
2070
- gasPrice,
2071
- data: txData,
2072
- nonce,
2073
- chainId: activeNetwork.chainId,
2074
- type: 0,
2075
- };
2076
- }
2077
- else {
2078
- txFormattedForEthers = {
2079
- to: tokenAddress,
2080
- value: '0x0',
2081
- gasLimit: gasLimit,
2082
- maxFeePerGas,
2083
- maxPriorityFeePerGas,
2084
- data: txData,
2085
- nonce,
2086
- chainId: activeNetwork.chainId,
2087
- type: 2, // Need explicit type for hardware wallet serialization
2088
- };
2089
- }
2090
- const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
2091
- assertCurrent();
2092
- assertHardwareTransactionSender(signedTx, activeAccount.address);
2093
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async ({ rawTransaction, admit }) => {
2094
- const response = await admit(() => provider.sendTransaction(rawTransaction));
2095
- saveTrezorTx && saveTrezorTx(response);
2096
- return response;
2097
- }, submission, undefined, expectedUnsignedTransaction);
2098
- return finalTx;
2099
- }
2100
- catch (error) {
2101
- throw error;
2102
- }
2103
- }
2104
- else {
2105
- throw new Error(`Transaction Signature Failed. Error: ${signature}`);
2106
- }
2051
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
2052
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async ({ rawTransaction, admit }) => {
2053
+ const response = await admit(() => provider.sendTransaction(rawTransaction));
2054
+ saveTrezorTx && saveTrezorTx(response);
2055
+ return response;
2056
+ }, submission, undefined, expectedUnsignedTransaction);
2057
+ return finalTx;
2107
2058
  }
2108
2059
  catch (error) {
2109
2060
  throw error;
@@ -2174,70 +2125,48 @@ export class EthereumTransactions {
2174
2125
  };
2175
2126
  const sendERC721TokenOnLedger = async () => {
2176
2127
  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),
2128
+ const _contract = new Contract(tokenAddress, getErc21Abi(), provider);
2129
+ const txData = _contract.interface.encodeFunctionData('transferFrom', [
2130
+ activeAccountAddress,
2131
+ receiver,
2132
+ normalizedTokenId,
2133
+ ]);
2134
+ let txFormattedForEthers;
2135
+ if (isLegacy) {
2136
+ txFormattedForEthers = {
2137
+ to: tokenAddress,
2138
+ value: '0x0',
2139
+ gasLimit,
2140
+ gasPrice,
2141
+ data: txData,
2142
+ nonce,
2143
+ chainId: activeNetwork.chainId,
2144
+ type: 0,
2221
2145
  };
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
2146
  }
2238
- catch (error) {
2239
- throw error;
2147
+ else {
2148
+ txFormattedForEthers = {
2149
+ to: tokenAddress,
2150
+ value: '0x0',
2151
+ gasLimit,
2152
+ maxFeePerGas,
2153
+ maxPriorityFeePerGas,
2154
+ data: txData,
2155
+ nonce,
2156
+ chainId: activeNetwork.chainId,
2157
+ type: 2, // Need explicit type for hardware wallet serialization
2158
+ };
2240
2159
  }
2160
+ const rawTx = serializeTransaction(txFormattedForEthers);
2161
+ const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
2162
+ unsignedTransaction: rawTx,
2163
+ accountIndex: activeAccountId,
2164
+ expectedAddress: activeAccount.address,
2165
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2166
+ assertCurrent();
2167
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
2168
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
2169
+ return finalTx;
2241
2170
  };
2242
2171
  const sendERC721TokenOnTrezor = async () => {
2243
2172
  assertCurrent();
@@ -2280,55 +2209,17 @@ export class EthereumTransactions {
2280
2209
  ...txToBeSignedByTrezor,
2281
2210
  type: isLegacy ? 0 : 2,
2282
2211
  });
2283
- const signature = await this.requireTrezorSigner().signEthTransaction({
2284
- index: `${activeAccountId}`,
2285
- tx: txToBeSignedByTrezor,
2212
+ const { rawTransaction: signedTx } = await signTrezorEvmTransaction(this.requireTrezorSigner(), {
2213
+ accountIndex: activeAccountId,
2286
2214
  coin: trezorCoin,
2215
+ expectedAddress: activeAccount.address,
2287
2216
  slip44: activeNetwork.slip44,
2217
+ unsignedTransaction: expectedUnsignedTransaction,
2288
2218
  }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2289
2219
  assertCurrent();
2290
- if (signature.success) {
2291
- try {
2292
- let txFormattedForEthers;
2293
- if (isLegacy) {
2294
- txFormattedForEthers = {
2295
- to: tokenAddress,
2296
- value: '0x0',
2297
- gasLimit,
2298
- gasPrice,
2299
- data: txData,
2300
- nonce,
2301
- chainId: activeNetwork.chainId,
2302
- type: 0,
2303
- };
2304
- }
2305
- else {
2306
- txFormattedForEthers = {
2307
- to: tokenAddress,
2308
- value: '0x0',
2309
- gasLimit,
2310
- maxFeePerGas,
2311
- maxPriorityFeePerGas,
2312
- data: txData,
2313
- nonce,
2314
- chainId: activeNetwork.chainId,
2315
- type: 2, // Need explicit type for hardware wallet serialization
2316
- };
2317
- }
2318
- const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
2319
- assertCurrent();
2320
- assertHardwareTransactionSender(signedTx, activeAccount.address);
2321
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, expectedUnsignedTransaction);
2322
- return finalTx;
2323
- }
2324
- catch (error) {
2325
- console.log({ error });
2326
- throw error;
2327
- }
2328
- }
2329
- else {
2330
- throw new Error(`Transaction Signature Failed. Error: ${signature}`);
2331
- }
2220
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
2221
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, expectedUnsignedTransaction);
2222
+ return finalTx;
2332
2223
  }
2333
2224
  catch (error) {
2334
2225
  console.log({ errorDois: error });
@@ -2404,66 +2295,44 @@ export class EthereumTransactions {
2404
2295
  };
2405
2296
  const sendERC1155TokenOnLedger = async () => {
2406
2297
  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),
2298
+ const _contract = new Contract(tokenAddress, getErc55Abi(), provider);
2299
+ const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, normalizedTokenId, amount, '0x']);
2300
+ let txFormattedForEthers;
2301
+ if (isLegacy) {
2302
+ txFormattedForEthers = {
2303
+ to: tokenAddress,
2304
+ value: '0x0',
2305
+ gasLimit,
2306
+ gasPrice,
2307
+ data: txData,
2308
+ nonce,
2309
+ chainId: activeNetwork.chainId,
2310
+ type: 0,
2447
2311
  };
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
2312
  }
2464
- catch (error) {
2465
- throw error;
2313
+ else {
2314
+ txFormattedForEthers = {
2315
+ to: tokenAddress,
2316
+ value: '0x0',
2317
+ gasLimit,
2318
+ maxFeePerGas,
2319
+ maxPriorityFeePerGas,
2320
+ data: txData,
2321
+ nonce,
2322
+ chainId: activeNetwork.chainId,
2323
+ type: 2, // Need explicit type for hardware wallet serialization
2324
+ };
2466
2325
  }
2326
+ const rawTx = serializeTransaction(txFormattedForEthers);
2327
+ const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
2328
+ unsignedTransaction: rawTx,
2329
+ accountIndex: activeAccountId,
2330
+ expectedAddress: activeAccount.address,
2331
+ }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2332
+ assertCurrent();
2333
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
2334
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
2335
+ return finalTx;
2467
2336
  };
2468
2337
  const sendERC1155TokenOnTrezor = async () => {
2469
2338
  assertCurrent();
@@ -2499,55 +2368,17 @@ export class EthereumTransactions {
2499
2368
  ...txToBeSignedByTrezor,
2500
2369
  type: isLegacy ? 0 : 2,
2501
2370
  });
2502
- const signature = await this.requireTrezorSigner().signEthTransaction({
2503
- index: `${activeAccountId}`,
2504
- tx: txToBeSignedByTrezor,
2371
+ const { rawTransaction: signedTx } = await signTrezorEvmTransaction(this.requireTrezorSigner(), {
2372
+ accountIndex: activeAccountId,
2505
2373
  coin: activeNetwork.currency,
2374
+ expectedAddress: activeAccount.address,
2506
2375
  slip44: activeNetwork.slip44,
2376
+ unsignedTransaction: expectedUnsignedTransaction,
2507
2377
  }, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
2508
2378
  assertCurrent();
2509
- if (signature.success) {
2510
- try {
2511
- let txFormattedForEthers;
2512
- if (isLegacy) {
2513
- txFormattedForEthers = {
2514
- to: tokenAddress,
2515
- value: '0x0',
2516
- gasLimit,
2517
- gasPrice,
2518
- data: txData,
2519
- nonce,
2520
- chainId: activeNetwork.chainId,
2521
- type: 0,
2522
- };
2523
- }
2524
- else {
2525
- txFormattedForEthers = {
2526
- to: tokenAddress,
2527
- value: '0x0',
2528
- gasLimit,
2529
- maxFeePerGas,
2530
- maxPriorityFeePerGas,
2531
- data: txData,
2532
- nonce,
2533
- chainId: activeNetwork.chainId,
2534
- type: 2, // Need explicit type for hardware wallet serialization
2535
- };
2536
- }
2537
- const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
2538
- assertCurrent();
2539
- assertHardwareTransactionSender(signedTx, activeAccount.address);
2540
- const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, expectedUnsignedTransaction);
2541
- return finalTx;
2542
- }
2543
- catch (error) {
2544
- console.log({ error });
2545
- throw error;
2546
- }
2547
- }
2548
- else {
2549
- throw new Error(`Transaction Signature Failed. Error: ${signature}`);
2550
- }
2379
+ assertHardwareTransactionSender(signedTx, activeAccount.address);
2380
+ const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, expectedUnsignedTransaction);
2381
+ return finalTx;
2551
2382
  }
2552
2383
  catch (error) {
2553
2384
  console.log({ error });
@@ -2785,6 +2616,52 @@ export class EthereumTransactions {
2785
2616
  console.log('[EthereumTransactions] Deferring provider initialization:', error?.message || error);
2786
2617
  }
2787
2618
  }
2619
+ signPreparedEvmTransaction(input, options) {
2620
+ const selected = this.captureEvmAccountContext();
2621
+ const request = snapshotPreparedEvmRequest(input);
2622
+ const target = {
2623
+ id: request.account.id,
2624
+ type: request.account.type,
2625
+ };
2626
+ const context = this.captureEvmAccountContext(target);
2627
+ const hostGuard = preparedEvmGuard(options);
2628
+ const targetRecord = this.getState().accounts[target.type]?.[target.id];
2629
+ const encryptedKey = targetRecord?.xprv;
2630
+ const assertCurrent = () => {
2631
+ selected.assertCurrent();
2632
+ context.assertCurrent();
2633
+ const live = this.getState().accounts[target.type]?.[target.id];
2634
+ if (!live ||
2635
+ live.id !== target.id ||
2636
+ live.isSmartAccount ||
2637
+ live.isLedgerWallet ||
2638
+ live.isTrezorWallet ||
2639
+ typeof encryptedKey !== 'string' ||
2640
+ !encryptedKey ||
2641
+ live.xprv !== encryptedKey ||
2642
+ selected.activeAccountId !== request.selectedAccount.id ||
2643
+ selected.activeAccountType !== request.selectedAccount.type ||
2644
+ getAddress(selected.activeAccount.address) !==
2645
+ request.selectedAccount.expectedAddress ||
2646
+ getAddress(context.activeAccount.address) !==
2647
+ request.account.expectedAddress ||
2648
+ context.activeNetwork.kind !== 'ethereum' ||
2649
+ context.activeNetwork.slip44 !== 60 ||
2650
+ String(context.activeNetwork.chainId) !== request.network.chainId ||
2651
+ context.activeNetwork.url !== request.network.url)
2652
+ throw new Error('Prepared EVM signing context changed or is unsupported');
2653
+ };
2654
+ assertCurrent();
2655
+ hostGuard();
2656
+ assertCurrent();
2657
+ const key = this.getDecryptedPrivateKey(target);
2658
+ if (getAddress(key.address) !== request.account.expectedAddress)
2659
+ throw new Error('Prepared EVM signer address mismatch');
2660
+ const { signed } = signPreparedEvmEnvelope(request.transaction, key.decryptedPrivateKey);
2661
+ hostGuard();
2662
+ assertCurrent();
2663
+ return signed;
2664
+ }
2788
2665
  setWeb3Provider(network) {
2789
2666
  this.abortController.abort();
2790
2667
  this.abortController = new AbortController();