@obolnetwork/obol-sdk 2.10.2 → 2.11.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 (51) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/abi/BatchDeposit.js +120 -0
  3. package/dist/cjs/src/abi/Multicall3.js +253 -0
  4. package/dist/cjs/src/bytecodes.js +4 -4
  5. package/dist/cjs/src/constants.js +13 -7
  6. package/dist/cjs/src/eoa/eoa.js +48 -0
  7. package/dist/cjs/src/eoa/eoaHelpers.js +45 -1
  8. package/dist/cjs/src/index.js +2 -2
  9. package/dist/cjs/src/schema.js +83 -1
  10. package/dist/cjs/src/splits/splitHelpers.js +64 -19
  11. package/dist/cjs/src/splits/splits.js +54 -11
  12. package/dist/cjs/test/eoa/eoa.spec.js +75 -0
  13. package/dist/cjs/test/splits/splits.spec.js +84 -0
  14. package/dist/esm/package.json +1 -1
  15. package/dist/esm/src/abi/BatchDeposit.js +117 -0
  16. package/dist/esm/src/abi/Multicall3.js +250 -0
  17. package/dist/esm/src/bytecodes.js +3 -3
  18. package/dist/esm/src/constants.js +14 -8
  19. package/dist/esm/src/eoa/eoa.js +50 -2
  20. package/dist/esm/src/eoa/eoaHelpers.js +43 -0
  21. package/dist/esm/src/index.js +2 -2
  22. package/dist/esm/src/schema.js +82 -0
  23. package/dist/esm/src/splits/splitHelpers.js +61 -17
  24. package/dist/esm/src/splits/splits.js +56 -13
  25. package/dist/esm/test/eoa/eoa.spec.js +76 -1
  26. package/dist/esm/test/splits/splits.spec.js +85 -1
  27. package/dist/types/src/abi/BatchDeposit.d.ts +49 -0
  28. package/dist/types/src/abi/Multicall3.d.ts +37 -0
  29. package/dist/types/src/bytecodes.d.ts +3 -3
  30. package/dist/types/src/eoa/eoa.d.ts +34 -1
  31. package/dist/types/src/eoa/eoaHelpers.d.ts +15 -0
  32. package/dist/types/src/schema.d.ts +76 -0
  33. package/dist/types/src/splits/splitHelpers.d.ts +22 -1
  34. package/dist/types/src/splits/splits.d.ts +33 -1
  35. package/dist/types/src/types.d.ts +40 -1
  36. package/package.json +1 -1
  37. package/src/abi/BatchDeposit.ts +117 -0
  38. package/src/abi/Multicall3.ts +250 -0
  39. package/src/bytecodes.ts +3 -3
  40. package/src/constants.ts +16 -10
  41. package/src/eoa/eoa.ts +60 -2
  42. package/src/eoa/eoaHelpers.ts +68 -0
  43. package/src/index.ts +4 -4
  44. package/src/schema.ts +84 -0
  45. package/src/splits/splitHelpers.ts +99 -19
  46. package/src/splits/splits.ts +65 -15
  47. package/src/types.ts +43 -1
  48. package/dist/cjs/src/abi/Multicall.js +0 -148
  49. package/dist/esm/src/abi/Multicall.js +0 -145
  50. package/dist/types/src/abi/Multicall.d.ts +0 -35
  51. package/src/abi/Multicall.ts +0 -145
@@ -1,6 +1,6 @@
1
1
  import pjson from '../package.json';
2
2
  import { FORK_MAPPING } from './types';
3
- import { HOLESKY_MULTICALL_BYTECODE, HOLESKY_OWR_FACTORY_BYTECODE, HOLESKY_SPLITMAIN_BYTECODE, HOODI_MULTICALL_BYTECODE, HOODI_OVM_FACTORY_BYTECODE, HOODI_OWR_FACTORY_BYTECODE, HOODI_SPLIT_V2_FACTORY_BYTECODE, HOODI_SPLITMAIN_BYTECODE, HOODI_WAREHOUSE_BYTECODE, MAINNET_MULTICALL_BYTECODE, MAINNET_OVM_FACTORY_BYTECODE, MAINNET_OWR_FACTORY_BYTECODE, MAINNET_SPLIT_V2_FACTORY_BYTECODE, MAINNET_SPLITMAIN_BYTECODE, MAINNET_WAREHOUSE_BYTECODE, } from './bytecodes';
3
+ import { HOLESKY_MULTICALL3_BYTECODE, HOLESKY_OWR_FACTORY_BYTECODE, HOLESKY_SPLITMAIN_BYTECODE, HOODI_MULTICALL3_BYTECODE, HOODI_OVM_FACTORY_BYTECODE, HOODI_OWR_FACTORY_BYTECODE, HOODI_SPLIT_V2_FACTORY_BYTECODE, HOODI_SPLITMAIN_BYTECODE, HOODI_WAREHOUSE_BYTECODE, MAINNET_MULTICALL3_BYTECODE, MAINNET_OVM_FACTORY_BYTECODE, MAINNET_OWR_FACTORY_BYTECODE, MAINNET_SPLIT_V2_FACTORY_BYTECODE, MAINNET_SPLITMAIN_BYTECODE, MAINNET_WAREHOUSE_BYTECODE, } from './bytecodes';
4
4
  import * as dotenv from 'dotenv';
5
5
  dotenv.config();
6
6
  export const CONFLICT_ERROR_MSG = 'Conflict';
@@ -112,9 +112,9 @@ export const CHAIN_CONFIGURATION = {
112
112
  address: '0x2ed6c4B5dA6378c7897AC67Ba9e43102Feb694EE',
113
113
  bytecode: MAINNET_SPLITMAIN_BYTECODE,
114
114
  },
115
- MULTICALL_CONTRACT: {
116
- address: '0xeefba1e63905ef1d7acba5a8513c70307c1ce441',
117
- bytecode: MAINNET_MULTICALL_BYTECODE,
115
+ MULTICALL3_CONTRACT: {
116
+ address: '0xcA11bde05977b3631167028862bE2a173976CA11',
117
+ bytecode: MAINNET_MULTICALL3_BYTECODE,
118
118
  },
119
119
  OWR_FACTORY_CONTRACT: {
120
120
  address: '0x119acd7844cbdd5fc09b1c6a4408f490c8f7f522',
@@ -140,15 +140,18 @@ export const CHAIN_CONFIGURATION = {
140
140
  EOA_WITHDRAWAL_CONTRACT: {
141
141
  address: '0x00000961Ef480Eb55e80D19ad83579A64c007002',
142
142
  },
143
+ BATCH_DEPOSIT_CONTRACT: {
144
+ address: '0xcD7a6C118Ac8F6544BC5076F2D8Fb86D2C546756',
145
+ },
143
146
  },
144
147
  [FORK_MAPPING['0x01017000']]: {
145
148
  SPLITMAIN_CONTRACT: {
146
149
  address: '0xfC8a305728051367797DADE6Aa0344E0987f5286',
147
150
  bytecode: HOLESKY_SPLITMAIN_BYTECODE,
148
151
  },
149
- MULTICALL_CONTRACT: {
152
+ MULTICALL3_CONTRACT: {
150
153
  address: '0xcA11bde05977b3631167028862bE2a173976CA11',
151
- bytecode: HOLESKY_MULTICALL_BYTECODE,
154
+ bytecode: HOLESKY_MULTICALL3_BYTECODE,
152
155
  },
153
156
  OWR_FACTORY_CONTRACT: {
154
157
  address: '0xc0961353fcc43a99e3041db07ac646720e116256',
@@ -167,9 +170,9 @@ export const CHAIN_CONFIGURATION = {
167
170
  address: '0xc05ae267291705ac16F75283572294ed2a91CBc7',
168
171
  bytecode: HOODI_SPLITMAIN_BYTECODE,
169
172
  },
170
- MULTICALL_CONTRACT: {
173
+ MULTICALL3_CONTRACT: {
171
174
  address: '0xcA11bde05977b3631167028862bE2a173976CA11',
172
- bytecode: HOODI_MULTICALL_BYTECODE,
175
+ bytecode: HOODI_MULTICALL3_BYTECODE,
173
176
  },
174
177
  OWR_FACTORY_CONTRACT: {
175
178
  address: '0x9ff0c649d0bf5fe7efa4d72e94bed7302ed5c8d7',
@@ -195,6 +198,9 @@ export const CHAIN_CONFIGURATION = {
195
198
  EOA_WITHDRAWAL_CONTRACT: {
196
199
  address: '0x00000961Ef480Eb55e80D19ad83579A64c007002',
197
200
  },
201
+ BATCH_DEPOSIT_CONTRACT: {
202
+ address: '0x4c9506eE2fCd962f03022D9d8ebf6f7828abEfaf',
203
+ },
198
204
  },
199
205
  };
200
206
  export const DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT = 1;
@@ -7,9 +7,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { submitEOAWithdrawalRequest } from './eoaHelpers';
10
+ import { submitEOAWithdrawalRequest, submitEOABatchDeposit, } from './eoaHelpers';
11
11
  import { validatePayload } from '../ajv';
12
- import { eoaWithdrawalPayloadSchema } from '../schema';
12
+ import { eoaWithdrawalPayloadSchema, eoaDepositPayloadSchema } from '../schema';
13
13
  import { CHAIN_CONFIGURATION } from '../constants';
14
14
  /**
15
15
  * EOA can be used for managing EOA (Externally Owned Account) operations like withdrawals.
@@ -76,4 +76,52 @@ export class EOA {
76
76
  });
77
77
  });
78
78
  }
79
+ /**
80
+ * Deposits to batch deposit contract.
81
+ *
82
+ * This method allows depositing multiple validators to the Ethereum beacon chain
83
+ * using the Pier Two batch deposit contract for gas efficiency.
84
+ * Each deposit includes validator public key, withdrawal credentials, signature, and amount.
85
+ *
86
+ * @remarks
87
+ * **⚠️ Important:** If you're storing the private key in an `.env` file, ensure it is securely managed
88
+ * and not pushed to version control.
89
+ *
90
+ * **⚠️ Gas Limit:** Due to EVM constraints, it is recommended to deposit in batches of up to 500 at a time.
91
+ *
92
+ * @param {EOADepositPayload} payload - Data needed to deposit to batch contract
93
+ * @returns {Promise<{txHashes: string[]}>} Array of transaction hashes for all batches
94
+ * @throws Will throw an error if the signer is not provided, contract is not configured, or the deposit fails
95
+ *
96
+ * An example of how to use deposit:
97
+ * ```typescript
98
+ * const result = await client.eoa.deposit({
99
+ * deposits: [{
100
+ * pubKey: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
101
+ * withdrawalCredentials: '0x1234567890123456789012345678901234567890',
102
+ * signature: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
103
+ * amount: '32000000000000000000' // 32 ETH in wei
104
+ * }]
105
+ * });
106
+ * console.log('Deposits completed:', result.txHashes);
107
+ * ```
108
+ */
109
+ deposit(payload) {
110
+ var _a;
111
+ return __awaiter(this, void 0, void 0, function* () {
112
+ if (!this.signer) {
113
+ throw new Error('Signer is required in deposit');
114
+ }
115
+ const chainConfig = CHAIN_CONFIGURATION[this.chainId];
116
+ if (!((_a = chainConfig === null || chainConfig === void 0 ? void 0 : chainConfig.BATCH_DEPOSIT_CONTRACT) === null || _a === void 0 ? void 0 : _a.address)) {
117
+ throw new Error(`Batch deposit contract is not configured for chain ${this.chainId}`);
118
+ }
119
+ const validatedPayload = validatePayload(payload, eoaDepositPayloadSchema);
120
+ return yield submitEOABatchDeposit({
121
+ deposits: validatedPayload.deposits,
122
+ batchDepositContractAddress: chainConfig.BATCH_DEPOSIT_CONTRACT.address,
123
+ signer: this.signer,
124
+ });
125
+ });
126
+ }
79
127
  }
@@ -8,6 +8,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { ETHER_TO_GWEI } from '../constants';
11
+ import { Contract } from 'ethers';
12
+ import { BatchDepositContract } from '../abi/BatchDeposit';
11
13
  /**
12
14
  * Helper function to submit withdrawal request for EOA
13
15
  */
@@ -31,3 +33,44 @@ export function submitEOAWithdrawalRequest({ pubkey, allocation, withdrawalAddre
31
33
  return { txHash: receipt.hash };
32
34
  });
33
35
  }
36
+ /**
37
+ * Helper function to submit batch deposit request for EOA
38
+ */
39
+ export function submitEOABatchDeposit({ deposits, batchDepositContractAddress, signer, }) {
40
+ return __awaiter(this, void 0, void 0, function* () {
41
+ if (!deposits || deposits.length === 0) {
42
+ throw new Error('No deposits provided');
43
+ }
44
+ // Create contract instance
45
+ const batchDepositContract = new Contract(batchDepositContractAddress, BatchDepositContract.abi, signer);
46
+ const BATCH_SIZE = 500;
47
+ const txHashes = [];
48
+ try {
49
+ // Process deposits in batches of 500
50
+ for (let i = 0; i < deposits.length; i += BATCH_SIZE) {
51
+ const batchDeposits = deposits.slice(i, i + BATCH_SIZE);
52
+ // Calculate total value needed for this batch
53
+ const totalValue = batchDeposits.reduce((sum, deposit) => sum + BigInt(deposit.amount), BigInt(0));
54
+ // Prepare deposit data for this batch
55
+ const depositData = batchDeposits.map(deposit => ({
56
+ pubKey: deposit.pubKey,
57
+ withdrawalCredentials: deposit.withdrawalCredentials,
58
+ signature: deposit.signature,
59
+ amount: BigInt(deposit.amount),
60
+ }));
61
+ // Execute batch deposit for this batch
62
+ const tx = yield batchDepositContract.batchDeposit(depositData, {
63
+ value: totalValue,
64
+ });
65
+ const receipt = yield tx.wait();
66
+ if (receipt === null || receipt === void 0 ? void 0 : receipt.hash) {
67
+ txHashes.push(receipt.hash);
68
+ }
69
+ }
70
+ return { txHashes };
71
+ }
72
+ catch (error) {
73
+ throw new Error("Failed to submit batch deposit'}");
74
+ }
75
+ });
76
+ }
@@ -135,9 +135,9 @@ export class Client extends Base {
135
135
  throw new Error(`SPLITMAIN_CONTRACT is not configured for chain ${this.chainId}`);
136
136
  }
137
137
  const checkSplitMainAddress = yield isContractAvailable(chainConfig.SPLITMAIN_CONTRACT.address, this.signer.provider, chainConfig.SPLITMAIN_CONTRACT.bytecode);
138
- const checkMulticallAddress = yield isContractAvailable(chainConfig.MULTICALL_CONTRACT.address, this.signer.provider, chainConfig.MULTICALL_CONTRACT.bytecode);
138
+ const checkMulticall3Address = yield isContractAvailable(chainConfig.MULTICALL3_CONTRACT.address, this.signer.provider, chainConfig.MULTICALL3_CONTRACT.bytecode);
139
139
  const checkOWRFactoryAddress = yield isContractAvailable(chainConfig.OWR_FACTORY_CONTRACT.address, this.signer.provider, chainConfig.OWR_FACTORY_CONTRACT.bytecode);
140
- if (!checkMulticallAddress ||
140
+ if (!checkMulticall3Address ||
141
141
  !checkSplitMainAddress ||
142
142
  !checkOWRFactoryAddress) {
143
143
  throw new Error(`Something isn not working as expected, check this issue with obol-sdk team on ${OBOL_SDK_EMAIL}`);
@@ -255,3 +255,85 @@ export const eoaWithdrawalPayloadSchema = {
255
255
  },
256
256
  required: ['pubkey', 'allocation', 'requiredFee'],
257
257
  };
258
+ export const ovmDepositPayloadSchema = {
259
+ type: 'object',
260
+ properties: {
261
+ ovmAddress: {
262
+ type: 'string',
263
+ pattern: '^0x[a-fA-F0-9]{40}$',
264
+ },
265
+ deposits: {
266
+ type: 'array',
267
+ minItems: 1,
268
+ items: {
269
+ type: 'object',
270
+ properties: {
271
+ pubkey: {
272
+ type: 'string',
273
+ pattern: '^0x[a-fA-F0-9]{96}$',
274
+ },
275
+ withdrawal_credentials: {
276
+ type: 'string',
277
+ pattern: '^0x[a-fA-F0-9]{64}$',
278
+ description: '32 bytes withdrawal credentials',
279
+ },
280
+ signature: {
281
+ type: 'string',
282
+ pattern: '^0x[a-fA-F0-9]{192}$',
283
+ description: '96 bytes signature (190 hex chars + 0x prefix)',
284
+ },
285
+ deposit_data_root: {
286
+ type: 'string',
287
+ pattern: '^0x[a-fA-F0-9]{64}$', // 32 bytes = 64 hex chars + 0x prefix
288
+ },
289
+ amount: {
290
+ type: 'string',
291
+ pattern: '^[0-9]+$',
292
+ },
293
+ },
294
+ required: [
295
+ 'pubkey',
296
+ 'withdrawal_credentials',
297
+ 'signature',
298
+ 'deposit_data_root',
299
+ 'amount',
300
+ ],
301
+ },
302
+ },
303
+ },
304
+ required: ['ovmAddress', 'deposits'],
305
+ };
306
+ export const eoaDepositPayloadSchema = {
307
+ type: 'object',
308
+ properties: {
309
+ deposits: {
310
+ type: 'array',
311
+ minItems: 1,
312
+ items: {
313
+ type: 'object',
314
+ properties: {
315
+ pubKey: {
316
+ type: 'string',
317
+ pattern: '^0x[a-fA-F0-9]{96}$',
318
+ },
319
+ withdrawalCredentials: {
320
+ type: 'string',
321
+ pattern: '^0x[a-fA-F0-9]{64}$',
322
+ description: '32 bytes withdrawal credentials',
323
+ },
324
+ signature: {
325
+ type: 'string',
326
+ pattern: '^0x[a-fA-F0-9]{192}$',
327
+ description: '96 bytes signature (190 hex chars + 0x prefix)',
328
+ },
329
+ amount: {
330
+ type: 'string',
331
+ pattern: '^[0-9]+$',
332
+ },
333
+ },
334
+ required: ['pubKey', 'withdrawalCredentials', 'signature', 'amount'],
335
+ },
336
+ },
337
+ },
338
+ required: ['deposits'],
339
+ };
@@ -11,9 +11,9 @@ import { Contract, Interface, parseEther, ZeroAddress } from 'ethers';
11
11
  import { OWRContract, OWRFactoryContract } from '../abi/OWR';
12
12
  import { OVMFactoryContract, OVMContract } from '../abi/OVMFactory';
13
13
  import { splitMainEthereumAbi } from '../abi/SplitMain';
14
- import { MultiCallContract } from '../abi/Multicall';
15
14
  import { CHAIN_CONFIGURATION, ETHER_TO_GWEI } from '../constants';
16
15
  import { splitV2FactoryAbi } from '../abi/splitV2FactoryAbi';
16
+ import { MultiCall3Contract } from '../abi/Multicall3';
17
17
  const splitMainContractInterface = new Interface(splitMainEthereumAbi);
18
18
  const owrFactoryContractInterface = new Interface(OWRFactoryContract.abi);
19
19
  const ovmFactoryContractInterface = new Interface(OVMFactoryContract.abi);
@@ -125,7 +125,7 @@ export const handleDeployOWRAndSplitter = ({ signer, isSplitterDeployed, predict
125
125
  splitterAddress = result.splitterAddress;
126
126
  }
127
127
  catch (error) {
128
- throw new Error(`Failed to deploy both splitter and OWR contracts: ${(_e = error.message) !== null && _e !== void 0 ? _e : 'Multicall contract deployment failed'}`);
128
+ throw new Error(`Failed to deploy both splitter and OWR contracts: ${(_e = error.message) !== null && _e !== void 0 ? _e : 'Multicall3 contract deployment failed'}`);
129
129
  }
130
130
  return {
131
131
  withdrawal_address: owrAddress,
@@ -242,7 +242,7 @@ export const deploySplitterAndOWRContracts = ({ owrArgs, splitterArgs, signer, c
242
242
  target: getChainConfig(chainId).OWR_FACTORY_CONTRACT.address,
243
243
  callData: owrTxData,
244
244
  });
245
- const executeMultiCalls = yield multicall(executeCalls, signer, chainId);
245
+ const executeMultiCalls = yield multicall3(executeCalls, signer, chainId);
246
246
  const splitAddressData = (_s = executeMultiCalls === null || executeMultiCalls === void 0 ? void 0 : executeMultiCalls.logs[0]) === null || _s === void 0 ? void 0 : _s.topics[1];
247
247
  const formattedSplitterAddress = '0x' + (splitAddressData === null || splitAddressData === void 0 ? void 0 : splitAddressData.slice(26, 66));
248
248
  const owrAddressData = (_t = executeMultiCalls === null || executeMultiCalls === void 0 ? void 0 : executeMultiCalls.logs[1]) === null || _t === void 0 ? void 0 : _t.topics[1];
@@ -282,39 +282,39 @@ export const getOWRTranches = ({ owrAddress, signer, }) => __awaiter(void 0, voi
282
282
  throw new Error(`Unexpected error in getOWRTranches: ${(_v = error.message) !== null && _v !== void 0 ? _v : 'Unknown error while fetching OWR tranche data'}`);
283
283
  }
284
284
  });
285
- export const multicall = (calls, signer, chainId) => __awaiter(void 0, void 0, void 0, function* () {
285
+ export const multicall3 = (calls, signer, chainId) => __awaiter(void 0, void 0, void 0, function* () {
286
286
  var _w, _x, _y;
287
287
  try {
288
288
  const chainConfig = getChainConfig(chainId);
289
- const multicallAddress = chainConfig.MULTICALL_CONTRACT.address;
290
- const multiCallContractInstance = new Contract(multicallAddress, MultiCallContract.abi, signer);
289
+ const multicall3Address = chainConfig.MULTICALL3_CONTRACT.address;
290
+ const multiCall3ContractInstance = new Contract(multicall3Address, MultiCall3Contract.abi, signer);
291
291
  let tx;
292
292
  try {
293
- tx = yield multiCallContractInstance.aggregate(calls);
293
+ tx = yield multiCall3ContractInstance.aggregate(calls);
294
294
  }
295
295
  catch (error) {
296
- throw new Error(`Failed to submit multicall transaction: ${(_w = error.message) !== null && _w !== void 0 ? _w : 'Transaction submission failed'}`);
296
+ throw new Error(`Failed to submit multicall3 transaction: ${(_w = error.message) !== null && _w !== void 0 ? _w : 'Transaction submission failed'}`);
297
297
  }
298
298
  let receipt;
299
299
  try {
300
300
  receipt = yield tx.wait();
301
301
  }
302
302
  catch (error) {
303
- throw new Error(`Multicall transaction failed or was reverted: ${(_x = error.message) !== null && _x !== void 0 ? _x : 'Transaction execution failed'}`);
303
+ throw new Error(`Multicall3 transaction failed or was reverted: ${(_x = error.message) !== null && _x !== void 0 ? _x : 'Transaction execution failed'}`);
304
304
  }
305
305
  if (!receipt) {
306
- throw new Error('Multicall transaction succeeded but no receipt was returned');
306
+ throw new Error('Multicall3 transaction succeeded but no receipt was returned');
307
307
  }
308
308
  return receipt;
309
309
  }
310
310
  catch (error) {
311
311
  // Re-throw if it's already our custom error
312
312
  if (error.message.includes('Failed to') ||
313
- error.message.includes('Multicall transaction')) {
313
+ error.message.includes('Multicall3 transaction')) {
314
314
  throw error;
315
315
  }
316
316
  // Handle unexpected errors
317
- throw new Error(`Unexpected error in multicall: ${(_y = error.message) !== null && _y !== void 0 ? _y : 'Unknown error during multicall execution'}`);
317
+ throw new Error(`Unexpected error in multicall3: ${(_y = error.message) !== null && _y !== void 0 ? _y : 'Unknown error during multicall3 execution'}`);
318
318
  }
319
319
  });
320
320
  const encodeCreateSplitTxData = (accounts, percentAllocations, distributorFee, controller) => {
@@ -446,8 +446,8 @@ export const deployOVMAndSplitV2 = ({ ovmArgs, rewardRecipients, isRewardsSplitt
446
446
  target: chainConfig.OVM_FACTORY_CONTRACT.address,
447
447
  callData: ovmTxData,
448
448
  });
449
- // Execute multicall
450
- const executeMultiCalls = yield multicall(executeCalls, signer, chainId);
449
+ // Execute multicall3
450
+ const executeMultiCalls = yield multicall3(executeCalls, signer, chainId);
451
451
  // Extract addresses from events
452
452
  let ovmAddress;
453
453
  const logsCount = ((_7 = executeMultiCalls === null || executeMultiCalls === void 0 ? void 0 : executeMultiCalls.logs) === null || _7 === void 0 ? void 0 : _7.length) || 0;
@@ -461,7 +461,7 @@ export const deployOVMAndSplitV2 = ({ ovmArgs, rewardRecipients, isRewardsSplitt
461
461
  ovmAddress = '0x' + ((_13 = (_12 = executeMultiCalls === null || executeMultiCalls === void 0 ? void 0 : executeMultiCalls.logs[7]) === null || _12 === void 0 ? void 0 : _12.topics[1]) === null || _13 === void 0 ? void 0 : _13.slice(26, 66));
462
462
  }
463
463
  if (!ovmAddress) {
464
- throw new Error('Failed to extract contract addresses from multicall events');
464
+ throw new Error('Failed to extract contract addresses from multicall3 events');
465
465
  }
466
466
  return ovmAddress;
467
467
  }
@@ -493,7 +493,6 @@ const getChainConfig = (chainId) => {
493
493
  * @returns Promise that resolves to the transaction hash
494
494
  */
495
495
  export const requestWithdrawalFromOVM = ({ ovmAddress, pubKeys, amounts, withdrawalFees, signer, }) => __awaiter(void 0, void 0, void 0, function* () {
496
- var _15;
497
496
  try {
498
497
  // Convert string amounts to bigint
499
498
  const bigintAmounts = amounts.map(amount => BigInt(amount));
@@ -505,6 +504,51 @@ export const requestWithdrawalFromOVM = ({ ovmAddress, pubKeys, amounts, withdra
505
504
  return { txHash: receipt.hash };
506
505
  }
507
506
  catch (error) {
508
- throw new Error(`Failed to request withdrawal from OVM: ${(_15 = error.message) !== null && _15 !== void 0 ? _15 : 'Request withdrawal failed'}`);
507
+ const errorMessage = error instanceof Error ? error.message : 'Request withdrawal failed';
508
+ throw new Error(`Failed to request withdrawal from OVM: ${errorMessage}`);
509
+ }
510
+ });
511
+ /**
512
+ * Deposits to OVM contract using multicall3 for batch operations
513
+ * @param ovmAddress - The address of the OVM contract
514
+ * @param deposits - Array of deposit objects with all required parameters
515
+ * @param signer - The signer to use for the transaction
516
+ * @returns Promise that resolves to an array of transaction hashes
517
+ */
518
+ export const depositWithMulticall3 = ({ ovmAddress, deposits, signer, chainId, }) => __awaiter(void 0, void 0, void 0, function* () {
519
+ try {
520
+ const ovmContract = new Contract(ovmAddress, OVMContract.abi, signer);
521
+ const chainConfig = getChainConfig(chainId);
522
+ const multicall3Address = chainConfig.MULTICALL3_CONTRACT.address;
523
+ const multiCall3ContractInstance = new Contract(multicall3Address, MultiCall3Contract.abi, signer);
524
+ const BATCH_SIZE = 500;
525
+ const txHashes = [];
526
+ // Process deposits in batches of 500
527
+ for (let i = 0; i < deposits.length; i += BATCH_SIZE) {
528
+ const batchDeposits = deposits.slice(i, i + BATCH_SIZE);
529
+ // Use Multicall3 aggregate3Value (payable per-call)
530
+ const callsWithValue = batchDeposits.map(deposit => ({
531
+ target: ovmAddress,
532
+ allowFailure: false,
533
+ callData: ovmContract.interface.encodeFunctionData('deposit', [
534
+ deposit.pubkey,
535
+ deposit.withdrawal_credentials,
536
+ deposit.signature,
537
+ deposit.deposit_data_root,
538
+ ]),
539
+ value: BigInt(deposit.amount),
540
+ }));
541
+ const totalBatchValue = callsWithValue.reduce((sum, c) => sum + c.value, BigInt(0));
542
+ const tx = yield multiCall3ContractInstance.aggregate3Value(callsWithValue, { value: totalBatchValue });
543
+ const receipt = yield tx.wait();
544
+ if (receipt === null || receipt === void 0 ? void 0 : receipt.hash) {
545
+ txHashes.push(receipt.hash);
546
+ }
547
+ }
548
+ return { txHashes };
549
+ }
550
+ catch (error) {
551
+ const errorMessage = error instanceof Error ? error.message : 'Deposit failed';
552
+ throw new Error(`Failed to deposit to OVM with multicall3: ${errorMessage}`);
509
553
  }
510
554
  });
@@ -7,9 +7,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { formatRecipientsForSplitV2, predictSplitV2Address, isSplitV2Deployed, deployOVMContract, deployOVMAndSplitV2, requestWithdrawalFromOVM, } from './splitHelpers';
10
+ import { formatRecipientsForSplitV2, predictSplitV2Address, isSplitV2Deployed, deployOVMContract, deployOVMAndSplitV2, requestWithdrawalFromOVM, depositWithMulticall3, } from './splitHelpers';
11
11
  import { CHAIN_CONFIGURATION, SPLITS_V2_SALT, DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT, isChainSupportedForSplitters, } from '../constants';
12
- import { ovmRewardsSplitPayloadSchema, ovmTotalSplitPayloadSchema, ovmRequestWithdrawalPayloadSchema, } from '../schema';
12
+ import { ovmRewardsSplitPayloadSchema, ovmTotalSplitPayloadSchema, ovmRequestWithdrawalPayloadSchema, ovmDepositPayloadSchema, } from '../schema';
13
13
  import { validatePayload } from '../ajv';
14
14
  import { isContractAvailable } from '../utils';
15
15
  /**
@@ -62,21 +62,21 @@ export class ObolSplits {
62
62
  }
63
63
  const ovmFactoryConfig = chainConfig.OVM_FACTORY_CONTRACT;
64
64
  const splitV2FactoryConfig = chainConfig.SPLIT_V2_FACTORY_CONTRACT;
65
- const multiCallConfig = chainConfig.MULTICALL_CONTRACT;
65
+ const multiCall3Config = chainConfig.MULTICALL3_CONTRACT;
66
66
  if (!(ovmFactoryConfig === null || ovmFactoryConfig === void 0 ? void 0 : ovmFactoryConfig.address) ||
67
67
  !(ovmFactoryConfig === null || ovmFactoryConfig === void 0 ? void 0 : ovmFactoryConfig.bytecode) ||
68
68
  !(splitV2FactoryConfig === null || splitV2FactoryConfig === void 0 ? void 0 : splitV2FactoryConfig.address) ||
69
69
  !(splitV2FactoryConfig === null || splitV2FactoryConfig === void 0 ? void 0 : splitV2FactoryConfig.bytecode) ||
70
- !(multiCallConfig === null || multiCallConfig === void 0 ? void 0 : multiCallConfig.address) ||
71
- !(multiCallConfig === null || multiCallConfig === void 0 ? void 0 : multiCallConfig.bytecode)) {
70
+ !(multiCall3Config === null || multiCall3Config === void 0 ? void 0 : multiCall3Config.address) ||
71
+ !(multiCall3Config === null || multiCall3Config === void 0 ? void 0 : multiCall3Config.bytecode)) {
72
72
  throw new Error(`Contracts configuration is incomplete for chain ${this.chainId}`);
73
73
  }
74
74
  const checkOVMFactoryContract = yield isContractAvailable(ovmFactoryConfig.address, this.provider, ovmFactoryConfig.bytecode);
75
75
  const checkSplitV2FactoryContract = yield isContractAvailable(splitV2FactoryConfig.address, this.provider, splitV2FactoryConfig.bytecode);
76
- const checkMultiCallContract = yield isContractAvailable(multiCallConfig.address, this.provider, multiCallConfig.bytecode);
76
+ const checkMultiCall3Contract = yield isContractAvailable(multiCall3Config.address, this.provider, multiCall3Config.bytecode);
77
77
  if (!checkOVMFactoryContract ||
78
78
  !checkSplitV2FactoryContract ||
79
- !checkMultiCallContract) {
79
+ !checkMultiCall3Contract) {
80
80
  throw new Error(`Splitter contract is not deployed or available on chain ${this.chainId}`);
81
81
  }
82
82
  const retroActiveFundingRecipient = {
@@ -178,21 +178,21 @@ export class ObolSplits {
178
178
  }
179
179
  const ovmFactoryConfig = chainConfig.OVM_FACTORY_CONTRACT;
180
180
  const splitV2FactoryConfig = chainConfig.SPLIT_V2_FACTORY_CONTRACT;
181
- const multiCallConfig = chainConfig.MULTICALL_CONTRACT;
181
+ const multiCall3Config = chainConfig.MULTICALL3_CONTRACT;
182
182
  if (!(ovmFactoryConfig === null || ovmFactoryConfig === void 0 ? void 0 : ovmFactoryConfig.address) ||
183
183
  !(ovmFactoryConfig === null || ovmFactoryConfig === void 0 ? void 0 : ovmFactoryConfig.bytecode) ||
184
184
  !(splitV2FactoryConfig === null || splitV2FactoryConfig === void 0 ? void 0 : splitV2FactoryConfig.address) ||
185
185
  !(splitV2FactoryConfig === null || splitV2FactoryConfig === void 0 ? void 0 : splitV2FactoryConfig.bytecode) ||
186
- !(multiCallConfig === null || multiCallConfig === void 0 ? void 0 : multiCallConfig.address) ||
187
- !(multiCallConfig === null || multiCallConfig === void 0 ? void 0 : multiCallConfig.bytecode)) {
186
+ !(multiCall3Config === null || multiCall3Config === void 0 ? void 0 : multiCall3Config.address) ||
187
+ !(multiCall3Config === null || multiCall3Config === void 0 ? void 0 : multiCall3Config.bytecode)) {
188
188
  throw new Error(`Contracts configuration is incomplete for chain ${this.chainId}`);
189
189
  }
190
190
  const checkOVMFactoryContract = yield isContractAvailable(ovmFactoryConfig.address, this.provider, ovmFactoryConfig.bytecode);
191
191
  const checkSplitV2FactoryContract = yield isContractAvailable(splitV2FactoryConfig.address, this.provider, splitV2FactoryConfig.bytecode);
192
- const checkMultiCallContract = yield isContractAvailable(multiCallConfig.address, this.provider, multiCallConfig.bytecode);
192
+ const checkMultiCall3Contract = yield isContractAvailable(multiCall3Config.address, this.provider, multiCall3Config.bytecode);
193
193
  if (!checkOVMFactoryContract ||
194
194
  !checkSplitV2FactoryContract ||
195
- !checkMultiCallContract) {
195
+ !checkMultiCall3Contract) {
196
196
  throw new Error(`Splitter contract is not deployed or available on chain ${this.chainId}`);
197
197
  }
198
198
  const retroActiveFundingRecipient = {
@@ -252,7 +252,7 @@ export class ObolSplits {
252
252
  };
253
253
  }
254
254
  else {
255
- // Use multicall to deploy any contracts that aren't deployed
255
+ // Use multicall3 to deploy any contracts that aren't deployed
256
256
  const ovmAddress = yield deployOVMAndSplitV2({
257
257
  ovmArgs: {
258
258
  OVMOwnerAddress: validatedPayload.OVMOwnerAddress,
@@ -316,4 +316,47 @@ export class ObolSplits {
316
316
  });
317
317
  });
318
318
  }
319
+ /**
320
+ * Deposits to OVM contract using multicall3 for batch operations.
321
+ *
322
+ * This method allows depositing to an OVM contract using multicall3 for efficient batch processing.
323
+ * Each deposit includes validator public key, withdrawal credentials, signature, deposit data root, and amount.
324
+ *
325
+ * @remarks
326
+ * **⚠️ Important:** If you're storing the private key in an `.env` file, ensure it is securely managed
327
+ * and not pushed to version control.
328
+ *
329
+ * @param {OVMDepositPayload} payload - Data needed to deposit to OVM
330
+ * @returns {Promise<{txHashes: string[]}>} Array of transaction hashes for all batches
331
+ * @throws Will throw an error if the signer is not provided, OVM address is invalid, or the deposit fails
332
+ *
333
+ * An example of how to use deposit:
334
+ * ```typescript
335
+ * const result = await client.splits.deposit({
336
+ * ovmAddress: '0x1234567890123456789012345678901234567890',
337
+ * deposits: [{
338
+ * pubkey: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
339
+ * withdrawal_credentials: '0x1234567890123456789012345678901234567890',
340
+ * signature: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
341
+ * deposit_data_root: '0x1234567890123456789012345678901234567890123456789012345678901234',
342
+ * amount: '32000000000000000000' // 32 ETH in wei
343
+ * }]
344
+ * });
345
+ * console.log('Deposits completed:', result.txHashes);
346
+ * ```
347
+ */
348
+ deposit(payload) {
349
+ return __awaiter(this, void 0, void 0, function* () {
350
+ if (!this.signer) {
351
+ throw new Error('Signer is required in deposit');
352
+ }
353
+ const validatedPayload = validatePayload(payload, ovmDepositPayloadSchema);
354
+ return yield depositWithMulticall3({
355
+ ovmAddress: validatedPayload.ovmAddress,
356
+ deposits: validatedPayload.deposits,
357
+ signer: this.signer,
358
+ chainId: this.chainId,
359
+ });
360
+ });
361
+ }
319
362
  }
@@ -8,10 +8,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { EOA } from '../../src/eoa/eoa';
11
- import { submitEOAWithdrawalRequest } from '../../src/eoa/eoaHelpers';
11
+ import { submitEOAWithdrawalRequest, submitEOABatchDeposit, } from '../../src/eoa/eoaHelpers';
12
12
  // Mock the helper function
13
13
  jest.mock('../../src/eoa/eoaHelpers', () => ({
14
14
  submitEOAWithdrawalRequest: jest.fn(),
15
+ submitEOABatchDeposit: jest.fn(),
15
16
  }));
16
17
  describe('EOA', () => {
17
18
  let eoa;
@@ -93,4 +94,78 @@ describe('EOA', () => {
93
94
  expect(result).toEqual(mockResult);
94
95
  }));
95
96
  });
97
+ describe('deposit', () => {
98
+ const mockDeposits = [
99
+ {
100
+ pubKey: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
101
+ withdrawalCredentials: '0x1234567890123456789012345678901234567890123456789012345678901234',
102
+ signature: '0x121234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012000000000000000000',
103
+ amount: '32000000000000000000', // 32 ETH in wei
104
+ },
105
+ ];
106
+ it('should successfully deposit to batch contract', () => __awaiter(void 0, void 0, void 0, function* () {
107
+ const mockPayload = {
108
+ deposits: mockDeposits,
109
+ };
110
+ const mockResult = {
111
+ txHashes: [
112
+ '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
113
+ ],
114
+ };
115
+ submitEOABatchDeposit.mockResolvedValue(mockResult);
116
+ const result = yield eoa.deposit(mockPayload);
117
+ expect(submitEOABatchDeposit).toHaveBeenCalledWith({
118
+ deposits: mockDeposits,
119
+ batchDepositContractAddress: '0xcD7a6C118Ac8F6544BC5076F2D8Fb86D2C546756',
120
+ signer: mockSigner,
121
+ });
122
+ expect(result).toEqual(mockResult);
123
+ }));
124
+ it('should throw error when signer is not provided', () => __awaiter(void 0, void 0, void 0, function* () {
125
+ const eoaWithoutSigner = new EOA(undefined, 1, mockProvider);
126
+ const mockPayload = {
127
+ deposits: mockDeposits,
128
+ };
129
+ yield expect(eoaWithoutSigner.deposit(mockPayload)).rejects.toThrow('Signer is required in deposit');
130
+ }));
131
+ it('should throw error when batch deposit contract is not configured for chain', () => __awaiter(void 0, void 0, void 0, function* () {
132
+ const eoaUnsupportedChain = new EOA(mockSigner, 999, mockProvider);
133
+ const mockPayload = {
134
+ deposits: mockDeposits,
135
+ };
136
+ yield expect(eoaUnsupportedChain.deposit(mockPayload)).rejects.toThrow('Batch deposit contract is not configured for chain 999');
137
+ }));
138
+ it('should handle multiple deposits', () => __awaiter(void 0, void 0, void 0, function* () {
139
+ const multipleDeposits = [
140
+ {
141
+ pubKey: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
142
+ withdrawalCredentials: '0x1234567890123456789012345678901234567890123456789012345678901234',
143
+ signature: '0x121234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012000000000000000000',
144
+ amount: '32000000000000000000',
145
+ },
146
+ {
147
+ pubKey: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdcd',
148
+ withdrawalCredentials: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd',
149
+ signature: '0x121234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012000000000000000000',
150
+ amount: '16000000000000000000',
151
+ },
152
+ ];
153
+ const mockPayload = {
154
+ deposits: multipleDeposits,
155
+ };
156
+ const mockResult = {
157
+ txHashes: [
158
+ '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
159
+ ],
160
+ };
161
+ submitEOABatchDeposit.mockResolvedValue(mockResult);
162
+ const result = yield eoa.deposit(mockPayload);
163
+ expect(submitEOABatchDeposit).toHaveBeenCalledWith({
164
+ deposits: multipleDeposits,
165
+ batchDepositContractAddress: '0xcD7a6C118Ac8F6544BC5076F2D8Fb86D2C546756',
166
+ signer: mockSigner,
167
+ });
168
+ expect(result).toEqual(mockResult);
169
+ }));
170
+ });
96
171
  });