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