@toruslabs/ethereum-controllers 8.16.0 → 8.17.0

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 (56) hide show
  1. package/dist/ethereumControllers.umd.min.js +1 -1
  2. package/dist/lib.cjs/Eip5792/walletGetCallsStatus.js +75 -0
  3. package/dist/lib.cjs/Eip5792/walletGetCapabilities.js +64 -0
  4. package/dist/lib.cjs/Eip5792/walletSendCalls.js +163 -0
  5. package/dist/lib.cjs/Eip7702/eip7702Utils.js +82 -0
  6. package/dist/lib.cjs/Eip7702/walletGetUpgradeStatus.js +35 -0
  7. package/dist/lib.cjs/Eip7702/walletUpgradeAccount.js +58 -0
  8. package/dist/lib.cjs/Keyring/KeyringController.js +50 -14
  9. package/dist/lib.cjs/Network/createEthereumMiddleware.js +114 -1
  10. package/dist/lib.cjs/Transaction/TransactionController.js +117 -6
  11. package/dist/lib.cjs/Transaction/TransactionGasUtil.js +13 -0
  12. package/dist/lib.cjs/Transaction/TransactionStateManager.js +9 -0
  13. package/dist/lib.cjs/Transaction/TransactionUtils.js +103 -10
  14. package/dist/lib.cjs/index.js +36 -0
  15. package/dist/lib.cjs/types/Eip5792/index.d.ts +3 -0
  16. package/dist/lib.cjs/types/Eip5792/walletGetCallsStatus.d.ts +19 -0
  17. package/dist/lib.cjs/types/Eip5792/walletGetCapabilities.d.ts +17 -0
  18. package/dist/lib.cjs/types/Eip5792/walletSendCalls.d.ts +45 -0
  19. package/dist/lib.cjs/types/Eip7702/eip7702Utils.d.ts +27 -0
  20. package/dist/lib.cjs/types/Eip7702/walletGetUpgradeStatus.d.ts +11 -0
  21. package/dist/lib.cjs/types/Eip7702/walletUpgradeAccount.d.ts +13 -0
  22. package/dist/lib.cjs/types/Keyring/KeyringController.d.ts +2 -0
  23. package/dist/lib.cjs/types/Network/createEthereumMiddleware.d.ts +53 -1
  24. package/dist/lib.cjs/types/Transaction/TransactionController.d.ts +9 -1
  25. package/dist/lib.cjs/types/Transaction/TransactionStateManager.d.ts +1 -0
  26. package/dist/lib.cjs/types/Transaction/TransactionUtils.d.ts +38 -1
  27. package/dist/lib.cjs/types/index.d.ts +4 -0
  28. package/dist/lib.cjs/types/utils/abis.d.ts +15 -0
  29. package/dist/lib.cjs/types/utils/constants.d.ts +2 -0
  30. package/dist/lib.cjs/types/utils/eip5792Types.d.ts +155 -0
  31. package/dist/lib.cjs/types/utils/eip7702Types.d.ts +60 -0
  32. package/dist/lib.cjs/types/utils/interfaces.d.ts +22 -2
  33. package/dist/lib.cjs/utils/abis.js +30 -0
  34. package/dist/lib.cjs/utils/constants.js +4 -1
  35. package/dist/lib.cjs/utils/eip5792Types.js +41 -0
  36. package/dist/lib.cjs/utils/eip7702Types.js +14 -0
  37. package/dist/lib.cjs/utils/interfaces.js +8 -0
  38. package/dist/lib.esm/Eip5792/walletGetCallsStatus.js +76 -0
  39. package/dist/lib.esm/Eip5792/walletGetCapabilities.js +63 -0
  40. package/dist/lib.esm/Eip5792/walletSendCalls.js +168 -0
  41. package/dist/lib.esm/Eip7702/eip7702Utils.js +81 -0
  42. package/dist/lib.esm/Eip7702/walletGetUpgradeStatus.js +33 -0
  43. package/dist/lib.esm/Eip7702/walletUpgradeAccount.js +56 -0
  44. package/dist/lib.esm/Keyring/KeyringController.js +53 -15
  45. package/dist/lib.esm/Network/createEthereumMiddleware.js +117 -2
  46. package/dist/lib.esm/Transaction/TransactionController.js +124 -10
  47. package/dist/lib.esm/Transaction/TransactionGasUtil.js +13 -0
  48. package/dist/lib.esm/Transaction/TransactionStateManager.js +9 -0
  49. package/dist/lib.esm/Transaction/TransactionUtils.js +103 -11
  50. package/dist/lib.esm/index.js +11 -4
  51. package/dist/lib.esm/utils/abis.js +30 -1
  52. package/dist/lib.esm/utils/constants.js +4 -2
  53. package/dist/lib.esm/utils/eip5792Types.js +105 -0
  54. package/dist/lib.esm/utils/eip7702Types.js +15 -0
  55. package/dist/lib.esm/utils/interfaces.js +28 -0
  56. package/package.json +7 -4
@@ -0,0 +1,56 @@
1
+ import { rpcErrors } from '@web3auth/auth';
2
+ import { TRANSACTION_ENVELOPE_TYPES } from '../utils/constants.js';
3
+ import { getIsEip7702UpgradeSupported } from './eip7702Utils.js';
4
+
5
+ /**
6
+ * Handle and prepare the transaction to upgrade an EOA to the EIP-7702 smart account.
7
+ *
8
+ * @param req - The request object.
9
+ * @param getEthCode - The function to get the code of an account.
10
+ * @param processTransaction - The function to process a transaction.
11
+ * @returns The transaction hash.
12
+ */
13
+ async function walletUpgradeAccount(req, getEthCode, processTransaction) {
14
+ const {
15
+ method,
16
+ params
17
+ } = req;
18
+ if (method !== "wallet_upgradeAccount") {
19
+ return;
20
+ }
21
+ const upgradeAccountParam = Array.isArray(params) ? params[0] : params;
22
+ const account = upgradeAccountParam.account;
23
+ const chainId = upgradeAccountParam.chainId;
24
+ if (!account || !chainId) {
25
+ throw rpcErrors.invalidParams("wallet_upgradeAccount: account and chainId are required");
26
+ }
27
+ const {
28
+ isSupported,
29
+ upgradeContractAddress,
30
+ delegationAddress
31
+ } = await getIsEip7702UpgradeSupported(account, chainId, getEthCode);
32
+ if (!isSupported) {
33
+ throw rpcErrors.methodNotSupported(`Account upgrade is not supported for chain ${chainId}`);
34
+ }
35
+ if (!upgradeContractAddress) {
36
+ throw rpcErrors.internal({
37
+ message: `Upgrade contract address not found for chain ${chainId}`
38
+ });
39
+ }
40
+ const isAlreadyUpgraded = Boolean(delegationAddress);
41
+ if (isAlreadyUpgraded) {
42
+ throw rpcErrors.invalidParams(`Account is already upgraded for chain ${chainId}`);
43
+ }
44
+ return processTransaction({
45
+ from: account,
46
+ to: account,
47
+ type: TRANSACTION_ENVELOPE_TYPES.SET_CODE,
48
+ // Type 4 transaction
49
+ authorizationList: [{
50
+ address: upgradeContractAddress,
51
+ chainId
52
+ }]
53
+ }, req);
54
+ }
55
+
56
+ export { walletUpgradeAccount };
@@ -1,6 +1,8 @@
1
+ import { encode } from '@ethereumjs/rlp';
1
2
  import { addHexPrefix, bytesToHex, privateToPublic, toChecksumAddress, privateToAddress, stripHexPrefix, bigIntToBytes, isHexString } from '@ethereumjs/util';
2
3
  import { BaseKeyringController, ecsignature, concatSig } from '@toruslabs/base-controllers';
3
- import { SigningKey, hashMessage, TypedDataEncoder } from 'ethers';
4
+ import { SigningKey, concat, keccak256, hashMessage, TypedDataEncoder } from 'ethers';
5
+ import { toHex } from 'viem';
4
6
 
5
7
  class KeyringController extends BaseKeyringController {
6
8
  constructor({
@@ -23,24 +25,60 @@ class KeyringController extends BaseKeyringController {
23
25
  localTx.signature = privKey.sign(localTx.unsignedHash);
24
26
  return localTx;
25
27
  }
28
+ async signEip7702Authorization(authorization, address) {
29
+ const wallet = this._getWalletForAccount(address);
30
+ const privKey = new SigningKey(addHexPrefix(wallet.privateKey));
31
+
32
+ // Convert hex strings to proper types for RLP encoding
33
+ // EIP-7702 authorization tuple: [chain_id_decimal, address, nonce_decimal]
34
+ const chainIdDecimal = parseInt(authorization.chainId, 16);
35
+ const nonceDecimal = parseInt(authorization.nonce, 16);
36
+
37
+ // RLP encode the eip7702 authorization
38
+ const encodedAuthorization = encode([chainIdDecimal, authorization.address, nonceDecimal]);
39
+ // Prefixed with EIP7702 Domain Separator (0x05)
40
+ const prefixedAuthorization = concat(["0x05", encodedAuthorization]);
41
+ const authorizationHash = keccak256(prefixedAuthorization);
42
+ const {
43
+ r,
44
+ s,
45
+ v
46
+ } = privKey.sign(authorizationHash);
47
+ const signedAuthorization = {
48
+ address: authorization.address,
49
+ chainId: authorization.chainId,
50
+ nonce: authorization.nonce,
51
+ r: addHexPrefix(r),
52
+ s: addHexPrefix(s),
53
+ yParity: toHex(v - 27 === 0 ? 0 : 1)
54
+ };
55
+ return signedAuthorization;
56
+ }
26
57
  getAccounts() {
27
58
  return this.state.wallets.map(w => w.publicKey);
28
59
  }
29
60
  importAccount(accountPrivateKey) {
30
- const hexPrivateKey = accountPrivateKey.padStart(64, "0");
31
- const bufferPrivKey = Buffer.from(hexPrivateKey, "hex");
32
- const publicKey = bytesToHex(privateToPublic(bufferPrivKey));
33
- const address = toChecksumAddress(bytesToHex(privateToAddress(bufferPrivKey)));
34
- const existingWallet = this.state.wallets.find(w => w.address === address);
35
- if (existingWallet) return existingWallet.address;
36
- this.update({
37
- wallets: [...this.state.wallets, {
38
- publicKey,
39
- privateKey: accountPrivateKey,
40
- address
41
- }]
42
- });
43
- return address;
61
+ try {
62
+ const hexPrivateKey = accountPrivateKey.padStart(64, "0");
63
+ const bufferPrivKey = Buffer.from(hexPrivateKey, "hex");
64
+ const publicKey = bytesToHex(privateToPublic(bufferPrivKey));
65
+ const address = toChecksumAddress(bytesToHex(privateToAddress(bufferPrivKey)));
66
+ const existingWallet = this.state.wallets.find(w => w.address === address);
67
+ if (existingWallet) return existingWallet.address;
68
+ this.update({
69
+ wallets: [...this.state.wallets, {
70
+ publicKey,
71
+ privateKey: accountPrivateKey,
72
+ address
73
+ }]
74
+ });
75
+ return address;
76
+ } catch (error) {
77
+ if (error instanceof Error && error.message.includes("expected 32 bytes")) {
78
+ throw new Error("invalid private key, expected hex or");
79
+ }
80
+ throw error;
81
+ }
44
82
  }
45
83
  removeAccount(address) {
46
84
  const newWallets = [...this.state.wallets];
@@ -1,7 +1,14 @@
1
1
  import { PROVIDER_JRPC_METHODS, createFetchMiddleware, createGenericJRPCMiddleware } from '@toruslabs/base-controllers';
2
2
  import { createAsyncMiddleware, mergeMiddleware, createScaffoldMiddleware } from '@web3auth/auth';
3
+ import { walletGetUpgradeStatus } from '../Eip7702/walletGetUpgradeStatus.js';
4
+ import { walletUpgradeAccount } from '../Eip7702/walletUpgradeAccount.js';
3
5
  import { METHOD_TYPES, TRANSACTION_ENVELOPE_TYPES, BUNDLER_METHOD_TYPES, PAYMASTER_METHOD_TYPES } from '../utils/constants.js';
6
+ import { EIP_5792_METHODS } from '../utils/eip5792Types.js';
7
+ import { EIP_7702_METHODS } from '../utils/eip7702Types.js';
4
8
  import { transactionDecoder } from '../utils/transaction.js';
9
+ import { walletGetCallsStatus } from '../Eip5792/walletGetCallsStatus.js';
10
+ import { walletSendCalls } from '../Eip5792/walletSendCalls.js';
11
+ import { walletGetCapabilities } from '../Eip5792/walletGetCapabilities.js';
5
12
 
6
13
  function createGetAccountsMiddleware({
7
14
  getAccounts
@@ -348,6 +355,101 @@ function createAAMiddleware({
348
355
  }
349
356
  return mergeMiddleware(middlewares);
350
357
  }
358
+
359
+ /**
360
+ * Middleware to handle EIP-7702 methods.
361
+ *
362
+ * @param getEthCode - Function to get the deployment bytecode of an address. (eth_getCode)
363
+ * @param processTransaction - Function to process a transaction.
364
+ * @returns JRPCMiddleware to handle EIP-7702 methods
365
+ */
366
+ function createEip7702Middleware({
367
+ getEthCode,
368
+ processTransaction
369
+ }) {
370
+ return createScaffoldMiddleware({
371
+ [EIP_7702_METHODS.WALLET_UPGRADE_ACCOUNT]: createAsyncMiddleware(async (request, response, next) => {
372
+ if (request.method !== EIP_7702_METHODS.WALLET_UPGRADE_ACCOUNT) {
373
+ return next();
374
+ }
375
+ if (!getEthCode) throw new Error("WalletMiddleware - opts.getEthCode not provided");
376
+ if (!processTransaction) throw new Error("WalletMiddleware - opts.processTransaction not provided");
377
+ const result = await walletUpgradeAccount(request, getEthCode, processTransaction);
378
+ response.result = result;
379
+ }),
380
+ [EIP_7702_METHODS.WALLET_GET_ACCOUNT_UPGRADE_STATUS]: createAsyncMiddleware(async (request, response, next) => {
381
+ if (request.method !== EIP_7702_METHODS.WALLET_GET_ACCOUNT_UPGRADE_STATUS) {
382
+ return next();
383
+ }
384
+ if (!getEthCode) {
385
+ throw new Error("getEthCode not provided");
386
+ }
387
+ const result = await walletGetUpgradeStatus(request, getEthCode);
388
+ response.result = result;
389
+ })
390
+ });
391
+ }
392
+
393
+ /**
394
+ * Middleware to handle EIP-5792 methods.
395
+ * Supports wallet capabilities and batch call operations for EIP-7702 upgraded accounts.
396
+ *
397
+ * @param eip5792Config - Configuration for EIP-5792 handlers.
398
+ * @param processTransaction - Function to process a transaction.
399
+ * @returns JRPCMiddleware to handle EIP-5792 methods
400
+ */
401
+ function createEip5792Middleware({
402
+ eip5792Config,
403
+ processTransaction,
404
+ processTransactionBatch,
405
+ getTransactionByBatchId,
406
+ getEthCode
407
+ }) {
408
+ if (!eip5792Config) {
409
+ // Return a pass-through middleware if EIP-5792 is not configured
410
+ return (_req, _res, next) => next();
411
+ }
412
+ const {
413
+ getSupportedChains,
414
+ getCachedDelegations,
415
+ updateDelegationCache
416
+ } = eip5792Config;
417
+
418
+ // Context for wallet_getCapabilities
419
+ const getCapabilitiesContext = {
420
+ getSupportedChains,
421
+ getCachedDelegations,
422
+ updateDelegationCache
423
+ };
424
+
425
+ // Context for wallet_sendCalls
426
+ const sendCallsContext = {
427
+ processTransactionBatch
428
+ };
429
+ return createScaffoldMiddleware({
430
+ [EIP_5792_METHODS.WALLET_GET_CAPABILITIES]: createAsyncMiddleware(async (request, response, next) => {
431
+ if (request.method !== EIP_5792_METHODS.WALLET_GET_CAPABILITIES) {
432
+ return next();
433
+ }
434
+ const result = await walletGetCapabilities(request, getEthCode, getCapabilitiesContext);
435
+ response.result = result;
436
+ }),
437
+ [EIP_5792_METHODS.WALLET_SEND_CALLS]: createAsyncMiddleware(async (request, response, next) => {
438
+ if (request.method !== EIP_5792_METHODS.WALLET_SEND_CALLS) {
439
+ return next();
440
+ }
441
+ const result = await walletSendCalls(request, getEthCode, sendCallsContext);
442
+ response.result = result;
443
+ }),
444
+ [EIP_5792_METHODS.WALLET_GET_CALLS_STATUS]: createAsyncMiddleware(async (request, response, next) => {
445
+ if (request.method !== EIP_5792_METHODS.WALLET_GET_CALLS_STATUS) {
446
+ return next();
447
+ }
448
+ const result = walletGetCallsStatus(request, getTransactionByBatchId);
449
+ response.result = result;
450
+ })
451
+ });
452
+ }
351
453
  function createEthereumMiddleware(providerHandlers, providerConfig, analytics) {
352
454
  const {
353
455
  requestAccounts,
@@ -355,6 +457,7 @@ function createEthereumMiddleware(providerHandlers, providerConfig, analytics) {
355
457
  getPrivateKey,
356
458
  getPublicKey,
357
459
  processTransaction,
460
+ processTransactionBatch,
358
461
  processSignTransaction,
359
462
  processEstimateUserOperationGas,
360
463
  processEthSignMessage,
@@ -362,16 +465,28 @@ function createEthereumMiddleware(providerHandlers, providerConfig, analytics) {
362
465
  processPersonalMessage,
363
466
  getPendingNonce,
364
467
  getPendingTransactionByHash,
468
+ getTransactionByBatchId,
365
469
  processSwitchEthereumChain,
366
470
  processWalletSwitchChain,
367
471
  processAddEthereumChain,
368
472
  getProviderState,
369
473
  aaConfig,
370
- version
474
+ version,
475
+ getEthCode,
476
+ eip5792Config
371
477
  } = providerHandlers;
372
478
  const middlewares = [createScaffoldMiddleware({
373
479
  version,
374
480
  [PROVIDER_JRPC_METHODS.GET_PROVIDER_STATE]: getProviderState
481
+ }), createEip7702Middleware({
482
+ getEthCode,
483
+ processTransaction
484
+ }), createEip5792Middleware({
485
+ eip5792Config,
486
+ processTransaction,
487
+ processTransactionBatch,
488
+ getEthCode,
489
+ getTransactionByBatchId
375
490
  }), createRequestAccountsMiddleware({
376
491
  requestAccounts
377
492
  }), createGetAccountsMiddleware({
@@ -410,4 +525,4 @@ function createEthereumMiddleware(providerHandlers, providerConfig, analytics) {
410
525
  return mergeMiddleware(middlewares);
411
526
  }
412
527
 
413
- export { createAAMiddleware, createEthereumMiddleware, createGetAccountsMiddleware, createPendingNonceMiddleware, createPendingTxMiddleware, createProcessAddEthereumChain, createProcessEstimateUserOperationGasMiddleware, createProcessEthSignMessage, createProcessPersonalMessage, createProcessSignTransactionMiddleware, createProcessSwitchEthereumChain, createProcessTransactionMiddleware, createProcessTypedMessageV4, createProcessWalletSwitchChain, createRequestAccountsMiddleware, formatTxMetaForRpcResult };
528
+ export { createAAMiddleware, createEip5792Middleware, createEip7702Middleware, createEthereumMiddleware, createGetAccountsMiddleware, createPendingNonceMiddleware, createPendingTxMiddleware, createProcessAddEthereumChain, createProcessEstimateUserOperationGasMiddleware, createProcessEthSignMessage, createProcessPersonalMessage, createProcessSignTransactionMiddleware, createProcessSwitchEthereumChain, createProcessTransactionMiddleware, createProcessTypedMessageV4, createProcessWalletSwitchChain, createRequestAccountsMiddleware, formatTxMetaForRpcResult };
@@ -2,18 +2,22 @@ import _objectSpread from '@babel/runtime/helpers/objectSpread2';
2
2
  import _defineProperty from '@babel/runtime/helpers/defineProperty';
3
3
  import { addHexPrefix, stripHexPrefix } from '@ethereumjs/util';
4
4
  import { TRANSACTION_TYPES, TX_EVENTS, TransactionStatus } from '@toruslabs/base-controllers';
5
- import { providerErrors, rpcErrors } from '@web3auth/auth';
5
+ import { rpcErrors, providerErrors } from '@web3auth/auth';
6
6
  import { BigNumber } from 'bignumber.js';
7
- import { Transaction, keccak256 } from 'ethers';
7
+ import { Signature, Transaction, keccak256 } from 'ethers';
8
8
  import log from 'loglevel';
9
- import { METHOD_TYPES, TRANSACTION_ENVELOPE_TYPES, GAS_ESTIMATE_TYPES, CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP } from '../utils/constants.js';
9
+ import { toHex } from 'viem';
10
+ import { generateEIP7702BatchTransaction } from '../Eip7702/eip7702Utils.js';
11
+ import { METHOD_TYPES, TRANSACTION_ENVELOPE_TYPES, CONTRACT_TYPE_ERC7821, GAS_ESTIMATE_TYPES, CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP } from '../utils/constants.js';
10
12
  import { decGWEIToHexWEI } from '../utils/conversionUtils.js';
11
13
  import { bnLessThan, getChainType, GAS_LIMITS } from '../utils/helpers.js';
14
+ import { TRANSACTION_CATEGORY_EIP7702 } from '../utils/interfaces.js';
12
15
  import { NonceTracker } from './NonceTracker.js';
13
16
  import { PendingTransactionTracker } from './PendingTransactionTracker.js';
14
17
  import { TransactionGasUtil } from './TransactionGasUtil.js';
15
18
  import { TransactionStateManager } from './TransactionStateManager.js';
16
- import { isEIP1559Transaction, normalizeTxParameters, validateTxParameters, determineTransactionType } from './TransactionUtils.js';
19
+ import { createNestedTransactionMeta, isEip7702SetCodeTx, isEip7702UpgradeWithDataToSelfTransaction, normalizeTxParameters, validateTxParameters, determineTransactionType, isEIP1559Transaction } from './TransactionUtils.js';
20
+ import { generateBatchId } from '../Eip5792/walletSendCalls.js';
17
21
 
18
22
  class TransactionController extends TransactionStateManager {
19
23
  constructor({
@@ -22,6 +26,7 @@ class TransactionController extends TransactionStateManager {
22
26
  provider,
23
27
  blockTracker,
24
28
  signEthTx,
29
+ signEip7702Authorization,
25
30
  getCurrentChainId,
26
31
  getCurrentNetworkEIP1559Compatibility,
27
32
  getCurrentAccountEIP1559Compatibility,
@@ -41,6 +46,7 @@ class TransactionController extends TransactionStateManager {
41
46
  _defineProperty(this, "_getCurrentNetworkEIP1559Compatibility", void 0);
42
47
  _defineProperty(this, "_getCurrentAccountEIP1559Compatibility", void 0);
43
48
  _defineProperty(this, "signEthTx", void 0);
49
+ _defineProperty(this, "signEip7702Authorization", void 0);
44
50
  _defineProperty(this, "provider", void 0);
45
51
  _defineProperty(this, "blockTracker", void 0);
46
52
  _defineProperty(this, "inProcessOfSigning", new Set());
@@ -52,6 +58,7 @@ class TransactionController extends TransactionStateManager {
52
58
  this.getSelectedAddress = getSelectedAddress;
53
59
  this.getEIP1559GasFeeEstimates = getEIP1559GasFeeEstimates;
54
60
  this.signEthTx = signEthTx;
61
+ this.signEip7702Authorization = signEip7702Authorization;
55
62
  this.provider = provider;
56
63
  this.txGasUtil = new TransactionGasUtil(this.provider, this.blockTracker);
57
64
  this.nonceTracker = new NonceTracker({
@@ -82,6 +89,57 @@ class TransactionController extends TransactionStateManager {
82
89
  const txMeta = await this.createTransaction(txParams, req);
83
90
  return this.processApproval(txMeta);
84
91
  }
92
+ async addNewUnapprovedTransactionsBatch(batchRequest, req) {
93
+ var _batchRequest$batchId;
94
+ const batchRequestParams = Array.isArray(req.params) ? req.params[0] : req.params;
95
+ const {
96
+ from,
97
+ chainId
98
+ } = batchRequestParams;
99
+ const {
100
+ transactions,
101
+ requiredEip7702Upgrade,
102
+ eip7702UpgradeContractAddress: delegationAddress
103
+ } = batchRequest;
104
+ const batchId = (_batchRequest$batchId = batchRequest.batchId) !== null && _batchRequest$batchId !== void 0 ? _batchRequest$batchId : generateBatchId();
105
+ let txParams;
106
+ const txMetaOverride = {
107
+ batchId,
108
+ type: CONTRACT_TYPE_ERC7821
109
+ };
110
+ if (transactions.length === 1) {
111
+ // if it's single call, we can just process as a normal transaction
112
+ txParams = {
113
+ from,
114
+ to: transactions[0].params.to,
115
+ value: transactions[0].params.value,
116
+ data: transactions[0].params.data,
117
+ chainId
118
+ };
119
+ } else {
120
+ // process multiple transactions as a batch
121
+ const nestedTxMeta = await Promise.all(transactions.map(txParam => createNestedTransactionMeta(from, txParam, this.provider)));
122
+ const batchTransaction = generateEIP7702BatchTransaction(from, nestedTxMeta);
123
+ txParams = _objectSpread(_objectSpread({}, batchTransaction), {}, {
124
+ from
125
+ });
126
+ if (requiredEip7702Upgrade) {
127
+ if (!delegationAddress) {
128
+ throw rpcErrors.invalidParams("Delegation address is required for EIP-7702 upgrade");
129
+ }
130
+ txParams.authorizationList = [{
131
+ address: delegationAddress,
132
+ chainId: addHexPrefix(chainId)
133
+ }];
134
+ txParams.type = TRANSACTION_ENVELOPE_TYPES.SET_CODE;
135
+ }
136
+ txMetaOverride.nestedTransactions = nestedTxMeta;
137
+ txMetaOverride.transactionCategory = TRANSACTION_CATEGORY_EIP7702.BATCH;
138
+ }
139
+ const txMeta = await this.createTransaction(txParams, req, txMetaOverride);
140
+ await this.processApproval(txMeta);
141
+ return batchId;
142
+ }
85
143
  async processApproval(txMeta) {
86
144
  return new Promise((resolve, reject) => {
87
145
  const handleFinished = msg => {
@@ -152,7 +210,7 @@ class TransactionController extends TransactionStateManager {
152
210
  async signTransaction(txId, isSignOnly) {
153
211
  const txMeta = this.getTransaction(txId);
154
212
  const chainId = this.getCurrentChainId();
155
- const type = isEIP1559Transaction(txMeta) ? TRANSACTION_ENVELOPE_TYPES.FEE_MARKET : TRANSACTION_ENVELOPE_TYPES.LEGACY;
213
+ const type = this.getTransactionType(txMeta);
156
214
  const txParams = {
157
215
  type: Number.parseInt(type, 16),
158
216
  chainId,
@@ -167,6 +225,34 @@ class TransactionController extends TransactionStateManager {
167
225
  value: txMeta.transaction.value
168
226
  };
169
227
  const fromAddress = txMeta.transaction.from;
228
+
229
+ // Sign Authorization if present
230
+ if (txMeta.transaction.authorizationList) {
231
+ const signedAuthorizations = [];
232
+ txParams.authorizationList = [];
233
+ for (const authorization of txMeta.transaction.authorizationList) {
234
+ // prepare and normalize the authorization
235
+ const authorizationNonce = authorization.nonce ? parseInt(authorization.nonce, 16) : parseInt(txMeta.transaction.nonce, 16) + 1;
236
+ const normalizedAuthorization = {
237
+ address: authorization.address,
238
+ chainId: authorization.chainId,
239
+ nonce: toHex(authorizationNonce)
240
+ };
241
+ const signedAuthorization = await this.signEip7702Authorization(normalizedAuthorization, fromAddress);
242
+ signedAuthorizations.push(signedAuthorization);
243
+ txParams.authorizationList.push({
244
+ address: signedAuthorization.address,
245
+ chainId: BigInt(signedAuthorization.chainId),
246
+ nonce: BigInt(signedAuthorization.nonce),
247
+ signature: Signature.from({
248
+ r: signedAuthorization.r,
249
+ s: signedAuthorization.s,
250
+ yParity: parseInt(signedAuthorization.yParity, 16)
251
+ })
252
+ });
253
+ }
254
+ txMeta.transaction.authorizationList = signedAuthorizations;
255
+ }
170
256
  const tx = Transaction.from(txParams);
171
257
  const signedEthTx = await this.signEthTx(tx, fromAddress);
172
258
  txMeta.r = signedEthTx.signature.r;
@@ -242,7 +328,11 @@ class TransactionController extends TransactionStateManager {
242
328
  return updateTxMeta;
243
329
  }
244
330
  async addTxGasDefaults(txMeta) {
245
- const eip1559Compatibility = txMeta.transaction.type !== TRANSACTION_ENVELOPE_TYPES.LEGACY && (await this.getEIP1559Compatibility());
331
+ const isSetCodeTx = isEip7702SetCodeTx(txMeta);
332
+
333
+ // EIP-7702 type 4 (SET_CODE) transactions are EIP-1559 compatible
334
+ // They use maxFeePerGas and maxPriorityFeePerGas like type 2 transactions
335
+ const eip1559Compatibility = isSetCodeTx || txMeta.transaction.type !== TRANSACTION_ENVELOPE_TYPES.LEGACY && (await this.getEIP1559Compatibility());
246
336
  const {
247
337
  gasPrice: defaultGasPrice,
248
338
  maxFeePerGas: defaultMaxFeePerGas,
@@ -374,13 +464,18 @@ class TransactionController extends TransactionStateManager {
374
464
  };
375
465
  }
376
466
  async getDefaultGasLimit(txMeta) {
467
+ var _txMeta$nestedTransac;
377
468
  const chainId = this.getCurrentChainId();
378
469
  const customNetworkGasBuffer = CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP[chainId];
379
470
  const chainType = getChainType(chainId);
471
+ const isSetCodeTx = isEip7702SetCodeTx(txMeta);
380
472
  if (txMeta.transaction.gas) {
381
473
  return {};
382
474
  }
383
- if (txMeta.transaction.to && txMeta.transactionCategory === TRANSACTION_TYPES.SENT_ETHER && chainType !== "custom" && !txMeta.transaction.data) {
475
+
476
+ // For EIP-7702 type 4 transactions (SET_CODE), we need to estimate gas including authorization costs
477
+ // Simple send optimization doesn't apply to SET_CODE transactions as they involve code execution
478
+ if (!isSetCodeTx && txMeta.transaction.to && txMeta.transactionCategory === TRANSACTION_TYPES.SENT_ETHER && chainType !== "custom" && !txMeta.transaction.data) {
384
479
  // This is a standard ether simple send, gas requirement is exactly 21k
385
480
  return {
386
481
  gasLimit: GAS_LIMITS.SIMPLE
@@ -393,13 +488,24 @@ class TransactionController extends TransactionStateManager {
393
488
  } = await this.txGasUtil.analyzeGasUsage(txMeta);
394
489
 
395
490
  // add additional gas buffer to our estimation for safety
396
- const gasLimit = this.txGasUtil.addGasBuffer(addHexPrefix(estimatedGasHex), blockGasLimit, customNetworkGasBuffer);
491
+ let gasLimit;
492
+ if (isEip7702UpgradeWithDataToSelfTransaction(txMeta) && (_txMeta$nestedTransac = txMeta.nestedTransactions) !== null && _txMeta$nestedTransac !== void 0 && _txMeta$nestedTransac.length) {
493
+ // Base multiplier for EIP-7702 upgrade with data to self transactions
494
+ // Add additional buffer based on the number of batch calls in ERC7821 execute
495
+ const batchCallsCount = txMeta.nestedTransactions.length;
496
+ const baseMultiplier = 1.5;
497
+ const perCallBuffer = 0.1;
498
+ const eip7702UpgradeWithDataToSelfGasBufferMultiplier = baseMultiplier + perCallBuffer * batchCallsCount;
499
+ gasLimit = this.txGasUtil.addGasBuffer(addHexPrefix(estimatedGasHex), blockGasLimit, eip7702UpgradeWithDataToSelfGasBufferMultiplier);
500
+ } else {
501
+ gasLimit = this.txGasUtil.addGasBuffer(addHexPrefix(estimatedGasHex), blockGasLimit, customNetworkGasBuffer);
502
+ }
397
503
  return {
398
504
  gasLimit,
399
505
  simulationFails
400
506
  };
401
507
  }
402
- async createTransaction(txParameters, req) {
508
+ async createTransaction(txParameters, req, overrideTxMeta) {
403
509
  const normalizedTxParameters = normalizeTxParameters(txParameters);
404
510
  const eip1559Compatibility = await this.getEIP1559Compatibility(txParameters.from);
405
511
  validateTxParameters(normalizedTxParameters, eip1559Compatibility);
@@ -411,13 +517,15 @@ class TransactionController extends TransactionStateManager {
411
517
  type,
412
518
  category,
413
519
  methodParams
414
- } = await determineTransactionType(txParameters, this.provider);
520
+ } = await determineTransactionType(txParameters, this.provider, overrideTxMeta);
415
521
  txMeta.contractType = type;
416
522
  txMeta.transactionCategory = category;
417
523
  txMeta.methodParams = methodParams;
418
524
  txMeta.isUserOperation = req.isUserOperation;
419
525
  txMeta.isSignOnly = req.isSignOnly;
420
526
  txMeta.transaction.value = txMeta.transaction.value ? addHexPrefix(txMeta.transaction.value) : "0x0";
527
+ txMeta.batchId = overrideTxMeta === null || overrideTxMeta === void 0 ? void 0 : overrideTxMeta.batchId;
528
+ txMeta.nestedTransactions = overrideTxMeta === null || overrideTxMeta === void 0 ? void 0 : overrideTxMeta.nestedTransactions;
421
529
  this.emit(`${txMeta.id}:unapproved`, txMeta);
422
530
  txMeta = this.addTransactionToState(txMeta);
423
531
  txMeta = await this.addTransactionGasDefaults(txMeta);
@@ -499,6 +607,12 @@ class TransactionController extends TransactionStateManager {
499
607
  if (tx.status !== TransactionStatus.failed) this.setTxStatusDropped(tx.id);
500
608
  });
501
609
  }
610
+ getTransactionType(txMeta) {
611
+ if (isEip7702SetCodeTx(txMeta)) {
612
+ return TRANSACTION_ENVELOPE_TYPES.SET_CODE;
613
+ }
614
+ return isEIP1559Transaction(txMeta) ? TRANSACTION_ENVELOPE_TYPES.FEE_MARKET : TRANSACTION_ENVELOPE_TYPES.LEGACY;
615
+ }
502
616
  }
503
617
 
504
618
  export { TransactionController };
@@ -1,8 +1,11 @@
1
+ import _objectSpread from '@babel/runtime/helpers/objectSpread2';
1
2
  import _defineProperty from '@babel/runtime/helpers/defineProperty';
2
3
  import { stripHexPrefix, addHexPrefix } from '@ethereumjs/util';
3
4
  import { cloneDeep } from '@toruslabs/base-controllers';
4
5
  import { BN } from 'bn.js';
5
6
  import log from 'loglevel';
7
+ import { DUMMY_AUTHORIZATION_SIGNATURE } from '../utils/eip7702Types.js';
8
+ import { isEip7702SetCodeTx } from './TransactionUtils.js';
6
9
 
7
10
  class TransactionGasUtil {
8
11
  constructor(provider, blockTracker) {
@@ -69,6 +72,16 @@ class TransactionGasUtil {
69
72
  delete txParams.gasPrice;
70
73
  delete txParams.maxFeePerGas;
71
74
  delete txParams.maxPriorityFeePerGas;
75
+ const eip7702SetCodeTx = isEip7702SetCodeTx(txMeta);
76
+ if (eip7702SetCodeTx) {
77
+ // assign dummy signature for the gas estimation
78
+ txParams.authorizationList = txParams.authorizationList.map(authorization => _objectSpread(_objectSpread({}, authorization), {}, {
79
+ nonce: authorization.nonce || "0x1",
80
+ r: DUMMY_AUTHORIZATION_SIGNATURE,
81
+ s: DUMMY_AUTHORIZATION_SIGNATURE,
82
+ yParity: "0x1"
83
+ }));
84
+ }
72
85
  return this.provider.request({
73
86
  method: "eth_estimateGas",
74
87
  params: [txParams]
@@ -206,6 +206,15 @@ class TransactionStateManager extends BaseTransactionStateManager {
206
206
  }
207
207
  return filteredTransactions;
208
208
  }
209
+ getTransactionByBatchId(batchId) {
210
+ const searchCriteria = {
211
+ batchId
212
+ };
213
+ const [batchTx] = this.getTransactions({
214
+ searchCriteria
215
+ });
216
+ return batchTx;
217
+ }
209
218
  getApprovedTransactions(address) {
210
219
  const searchCriteria = {
211
220
  status: TransactionStatus.approved