@obolnetwork/obol-sdk 2.5.0 → 2.6.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 (59) hide show
  1. package/dist/cjs/package.json +3 -3
  2. package/dist/cjs/src/constants.js +13 -1
  3. package/dist/cjs/src/exits/ethUtils.js +57 -0
  4. package/dist/cjs/src/exits/exit.js +420 -0
  5. package/dist/cjs/src/exits/verificationHelpers.js +86 -0
  6. package/dist/cjs/src/{incentiveHelpers.js → incentives/incentiveHelpers.js} +1 -1
  7. package/dist/cjs/src/{incentives.js → incentives/incentives.js} +3 -3
  8. package/dist/cjs/src/index.js +22 -4
  9. package/dist/cjs/src/schema.js +1 -1
  10. package/dist/cjs/src/splits/splitHelpers.js +327 -0
  11. package/dist/cjs/src/types.js +1 -0
  12. package/dist/cjs/test/exit/ethUtils.spec.js +83 -0
  13. package/dist/cjs/test/exit/exit.spec.js +328 -0
  14. package/dist/cjs/test/exit/verificationHelpers.spec.js +68 -0
  15. package/dist/cjs/test/{incentives.test.js → incentives/incentives.spec.js} +4 -4
  16. package/dist/esm/package.json +3 -3
  17. package/dist/esm/src/constants.js +12 -0
  18. package/dist/esm/src/exits/ethUtils.js +52 -0
  19. package/dist/esm/src/exits/exit.js +393 -0
  20. package/dist/esm/src/exits/verificationHelpers.js +81 -0
  21. package/dist/esm/src/{incentiveHelpers.js → incentives/incentiveHelpers.js} +1 -1
  22. package/dist/esm/src/{incentives.js → incentives/incentives.js} +3 -3
  23. package/dist/esm/src/index.js +20 -3
  24. package/dist/esm/src/schema.js +1 -1
  25. package/dist/esm/src/splits/splitHelpers.js +317 -0
  26. package/dist/esm/src/types.js +1 -0
  27. package/dist/esm/test/exit/ethUtils.spec.js +81 -0
  28. package/dist/esm/test/exit/exit.spec.js +303 -0
  29. package/dist/esm/test/exit/verificationHelpers.spec.js +66 -0
  30. package/dist/esm/test/{incentives.test.js → incentives/incentives.spec.js} +4 -4
  31. package/dist/types/src/constants.d.ts +5 -0
  32. package/dist/types/src/exits/ethUtils.d.ts +13 -0
  33. package/dist/types/src/exits/exit.d.ts +180 -0
  34. package/dist/types/src/exits/verificationHelpers.d.ts +20 -0
  35. package/dist/types/src/{incentiveHelpers.d.ts → incentives/incentiveHelpers.d.ts} +1 -1
  36. package/dist/types/src/{incentives.d.ts → incentives/incentives.d.ts} +1 -1
  37. package/dist/types/src/index.d.ts +16 -2
  38. package/dist/types/src/{splitHelpers.d.ts → splits/splitHelpers.d.ts} +1 -1
  39. package/dist/types/src/types.d.ts +98 -0
  40. package/dist/types/test/exit/verificationHelpers.spec.d.ts +1 -0
  41. package/dist/types/test/incentives/incentives.spec.d.ts +1 -0
  42. package/package.json +3 -3
  43. package/src/constants.ts +13 -0
  44. package/src/exits/ethUtils.ts +49 -0
  45. package/src/exits/exit.ts +564 -0
  46. package/src/exits/verificationHelpers.ts +110 -0
  47. package/src/{incentiveHelpers.ts → incentives/incentiveHelpers.ts} +2 -2
  48. package/src/{incentives.ts → incentives/incentives.ts} +3 -3
  49. package/src/index.ts +29 -3
  50. package/src/schema.ts +1 -1
  51. package/src/splits/splitHelpers.ts +557 -0
  52. package/src/types.ts +123 -0
  53. package/dist/cjs/src/splitHelpers.js +0 -187
  54. package/dist/cjs/test/methods.test.js +0 -418
  55. package/dist/esm/src/splitHelpers.js +0 -177
  56. package/dist/esm/test/methods.test.js +0 -393
  57. package/src/splitHelpers.ts +0 -355
  58. /package/dist/types/test/{incentives.test.d.ts → exit/ethUtils.spec.d.ts} +0 -0
  59. /package/dist/types/test/{methods.test.d.ts → exit/exit.spec.d.ts} +0 -0
package/src/index.ts CHANGED
@@ -45,15 +45,17 @@ import {
45
45
  handleDeployOWRAndSplitter,
46
46
  predictSplitterAddress,
47
47
  getOWRTranches,
48
- } from './splitHelpers.js';
48
+ } from './splits/splitHelpers.js';
49
49
  import { isContractAvailable } from './utils.js';
50
- import { Incentives } from './incentives.js';
50
+ import { Incentives } from './incentives/incentives.js';
51
+ import { Exit } from './exits/exit.js';
51
52
  export * from './types.js';
52
53
  export * from './services.js';
53
54
  export * from './verification/signature-validator.js';
54
55
  export * from './verification/common.js';
55
56
  export * from './constants.js';
56
- export { Incentives } from './incentives.js';
57
+ export { Incentives } from './incentives/incentives.js';
58
+ export { Exit } from './exits/exit.js';
57
59
 
58
60
  /**
59
61
  * Obol sdk Client can be used for creating, managing and activating distributed validators.
@@ -70,6 +72,12 @@ export class Client extends Base {
70
72
  */
71
73
  public incentives: Incentives;
72
74
 
75
+ /**
76
+ * The exit module, responsible for managing exit validation.
77
+ * @type {Exit}
78
+ */
79
+ public exit: Exit;
80
+
73
81
  /**
74
82
  * The blockchain provider, used to interact with the network.
75
83
  * It can be null, undefined, or a valid provider instance and defaults to the Signer provider if Signer is passed.
@@ -103,6 +111,7 @@ export class Client extends Base {
103
111
  this.request.bind(this),
104
112
  this.provider,
105
113
  );
114
+ this.exit = new Exit(this.chainId, this.provider);
106
115
  }
107
116
 
108
117
  /**
@@ -341,6 +350,7 @@ export class Client extends Base {
341
350
  const { accounts, percentAllocations } = formatSplitRecipients(
342
351
  copiedSplitRecipients,
343
352
  );
353
+
344
354
  const predictedSplitterAddress = await predictSplitterAddress({
345
355
  signer: this.signer,
346
356
  accounts,
@@ -555,4 +565,20 @@ export class Client extends Base {
555
565
  );
556
566
  return lock;
557
567
  }
568
+
569
+ /**
570
+ * @param lockHash - The configuration hash in cluster-definition
571
+ * @returns {Promise<ClusterLock>} The matched cluster details (lock) from DB
572
+ * @throws On not found cluster definition or lock.
573
+ *
574
+ */
575
+ async getClusterLockByHash(lockHash: string): Promise<ClusterLock> {
576
+ const lock: ClusterLock = await this.request(
577
+ `/${DEFAULT_BASE_VERSION}/lock/${lockHash}`,
578
+ {
579
+ method: 'GET',
580
+ },
581
+ );
582
+ return lock;
583
+ }
558
584
  }
package/src/schema.ts CHANGED
@@ -73,7 +73,7 @@ export const definitionSchema = {
73
73
  },
74
74
  compounding: {
75
75
  type: 'boolean',
76
- default: true,
76
+ default: false,
77
77
  },
78
78
  target_gas_limit: {
79
79
  type: 'number',
@@ -0,0 +1,557 @@
1
+ import {
2
+ type OWRTranches,
3
+ type ClusterValidator,
4
+ type ETH_ADDRESS,
5
+ type SplitRecipient,
6
+ type SignerType,
7
+ } from '../types';
8
+ import { Contract, Interface, parseEther, ZeroAddress } from 'ethers';
9
+ import { OWRContract, OWRFactoryContract } from '../abi/OWR';
10
+ import { splitMainEthereumAbi } from '../abi/SplitMain';
11
+ import { MultiCallContract } from '../abi/Multicall';
12
+ import { CHAIN_CONFIGURATION } from '../constants';
13
+
14
+ const splitMainContractInterface = new Interface(splitMainEthereumAbi);
15
+ const owrFactoryContractInterface = new Interface(OWRFactoryContract.abi);
16
+
17
+ type Call = {
18
+ target: ETH_ADDRESS;
19
+ callData: string;
20
+ };
21
+
22
+ type OWRArgs = {
23
+ recoveryAddress: ETH_ADDRESS;
24
+ principalRecipient: ETH_ADDRESS;
25
+ amountOfPrincipalStake: number;
26
+ predictedSplitterAddress: ETH_ADDRESS;
27
+ };
28
+
29
+ type SplitArgs = {
30
+ accounts: ETH_ADDRESS[];
31
+ percentAllocations: number[];
32
+ distributorFee: number;
33
+ controllerAddress: ETH_ADDRESS;
34
+ };
35
+
36
+ export const formatSplitRecipients = (
37
+ recipients: SplitRecipient[],
38
+ ): { accounts: ETH_ADDRESS[]; percentAllocations: number[] } => {
39
+ // Has to be sorted when passed
40
+ recipients.sort((a, b) => a.account.localeCompare(b.account));
41
+ const accounts = recipients.map(item => item.account);
42
+ const percentAllocations = recipients.map(recipient => {
43
+ const splitTostring = (recipient.percentAllocation * 1e4).toFixed(0);
44
+ return parseInt(splitTostring);
45
+ });
46
+ return { accounts, percentAllocations };
47
+ };
48
+
49
+ export const predictSplitterAddress = async ({
50
+ signer,
51
+ accounts,
52
+ percentAllocations,
53
+ chainId,
54
+ distributorFee,
55
+ controllerAddress,
56
+ }: {
57
+ signer: SignerType;
58
+ accounts: ETH_ADDRESS[];
59
+ percentAllocations: number[];
60
+ chainId: number;
61
+ distributorFee: number;
62
+ controllerAddress: ETH_ADDRESS;
63
+ }): Promise<ETH_ADDRESS> => {
64
+ try {
65
+ const splitMainContractInstance = new Contract(
66
+ CHAIN_CONFIGURATION[chainId].SPLITMAIN_ADDRESS.address,
67
+ splitMainEthereumAbi,
68
+ signer,
69
+ );
70
+
71
+ let predictedSplitterAddress: string;
72
+
73
+ if (controllerAddress === ZeroAddress) {
74
+ try {
75
+ predictedSplitterAddress =
76
+ await splitMainContractInstance.predictImmutableSplitAddress(
77
+ accounts,
78
+ percentAllocations,
79
+ distributorFee,
80
+ );
81
+ } catch (error: any) {
82
+ throw new Error(
83
+ `Failed to predict immutable splitter address: ${error.message ?? 'Contract call failed'}`,
84
+ );
85
+ }
86
+ } else {
87
+ try {
88
+ // It throws on deployed Immutable splitter
89
+ predictedSplitterAddress =
90
+ await splitMainContractInstance.createSplit.staticCall(
91
+ accounts,
92
+ percentAllocations,
93
+ distributorFee,
94
+ controllerAddress,
95
+ );
96
+ } catch (error: any) {
97
+ throw new Error(
98
+ `Failed to predict mutable splitter address via static call: ${error.message ?? 'Static call failed'}`,
99
+ );
100
+ }
101
+ }
102
+
103
+ return predictedSplitterAddress;
104
+ } catch (error: any) {
105
+ // Re-throw if it's already our custom error
106
+ if (error.message.includes('Failed to predict')) {
107
+ throw error;
108
+ }
109
+ // Handle unexpected errors
110
+ throw new Error(
111
+ `Unexpected error in predictSplitterAddress: ${error.message ?? 'Unknown contract interaction error'}`,
112
+ );
113
+ }
114
+ };
115
+
116
+ export const handleDeployOWRAndSplitter = async ({
117
+ signer,
118
+ isSplitterDeployed,
119
+ predictedSplitterAddress,
120
+ accounts,
121
+ percentAllocations,
122
+ etherAmount,
123
+ principalRecipient,
124
+ chainId,
125
+ distributorFee,
126
+ controllerAddress,
127
+ recoveryAddress,
128
+ }: {
129
+ signer: SignerType;
130
+ isSplitterDeployed: boolean;
131
+ predictedSplitterAddress: ETH_ADDRESS;
132
+ accounts: ETH_ADDRESS[];
133
+ percentAllocations: number[];
134
+ etherAmount: number;
135
+ principalRecipient: ETH_ADDRESS;
136
+ chainId: number;
137
+ distributorFee: number;
138
+ controllerAddress: ETH_ADDRESS;
139
+ recoveryAddress: ETH_ADDRESS;
140
+ }): Promise<ClusterValidator> => {
141
+ try {
142
+ if (isSplitterDeployed) {
143
+ let owrAddress: ETH_ADDRESS;
144
+ try {
145
+ owrAddress = await createOWRContract({
146
+ owrArgs: {
147
+ principalRecipient,
148
+ amountOfPrincipalStake: etherAmount,
149
+ predictedSplitterAddress,
150
+ recoveryAddress,
151
+ },
152
+ signer,
153
+ chainId,
154
+ });
155
+ } catch (error: any) {
156
+ throw new Error(
157
+ `Failed to create OWR contract with existing splitter: ${error.message ?? 'OWR contract creation failed'}`,
158
+ );
159
+ }
160
+ return {
161
+ withdrawal_address: owrAddress,
162
+ fee_recipient_address: predictedSplitterAddress,
163
+ };
164
+ } else {
165
+ let owrAddress: ETH_ADDRESS;
166
+ let splitterAddress: ETH_ADDRESS;
167
+ try {
168
+ const result = await deploySplitterAndOWRContracts({
169
+ owrArgs: {
170
+ principalRecipient,
171
+ amountOfPrincipalStake: etherAmount,
172
+ predictedSplitterAddress,
173
+ recoveryAddress,
174
+ },
175
+ splitterArgs: {
176
+ accounts,
177
+ percentAllocations,
178
+ distributorFee,
179
+ controllerAddress,
180
+ },
181
+ signer,
182
+ chainId,
183
+ });
184
+ owrAddress = result.owrAddress;
185
+ splitterAddress = result.splitterAddress;
186
+ } catch (error: any) {
187
+ throw new Error(
188
+ `Failed to deploy both splitter and OWR contracts: ${error.message ?? 'Multicall contract deployment failed'}`,
189
+ );
190
+ }
191
+
192
+ return {
193
+ withdrawal_address: owrAddress,
194
+ fee_recipient_address: splitterAddress,
195
+ };
196
+ }
197
+ } catch (error: any) {
198
+ // Re-throw if it's already our custom error
199
+ if (error.message.includes('Failed to')) {
200
+ throw error;
201
+ }
202
+ // Handle unexpected errors
203
+ throw new Error(
204
+ `Unexpected error in handleDeployOWRAndSplitter: ${error.message ?? 'Unknown error during contract deployment orchestration'}`,
205
+ );
206
+ }
207
+ };
208
+
209
+ const createOWRContract = async ({
210
+ owrArgs,
211
+ signer,
212
+ chainId,
213
+ }: {
214
+ owrArgs: OWRArgs;
215
+ signer: SignerType;
216
+ chainId: number;
217
+ }): Promise<ETH_ADDRESS> => {
218
+ try {
219
+ const OWRFactoryInstance = new Contract(
220
+ CHAIN_CONFIGURATION[chainId].OWR_FACTORY_ADDRESS.address,
221
+ OWRFactoryContract.abi,
222
+ signer,
223
+ );
224
+
225
+ let tx;
226
+ try {
227
+ tx = await OWRFactoryInstance.createOWRecipient(
228
+ owrArgs.recoveryAddress,
229
+ owrArgs.principalRecipient,
230
+ owrArgs.predictedSplitterAddress,
231
+ parseEther(owrArgs.amountOfPrincipalStake.toString()),
232
+ );
233
+ } catch (error: any) {
234
+ throw new Error(
235
+ `Failed to submit OWR contract creation transaction: ${error.message ?? 'Transaction submission failed'}`,
236
+ );
237
+ }
238
+
239
+ let receipt;
240
+ try {
241
+ receipt = await tx.wait();
242
+ } catch (error: any) {
243
+ throw new Error(
244
+ `OWR contract creation transaction failed or was reverted: ${error.message ?? 'Transaction execution failed'}`,
245
+ );
246
+ }
247
+
248
+ if (!receipt?.logs?.length) {
249
+ throw new Error(
250
+ 'OWR contract creation transaction succeeded but no events were emitted - unable to determine contract address',
251
+ );
252
+ }
253
+
254
+ const OWRAddressData = receipt.logs[0]?.topics[1];
255
+ if (!OWRAddressData) {
256
+ throw new Error(
257
+ 'OWR contract creation transaction succeeded but contract address could not be extracted from events',
258
+ );
259
+ }
260
+
261
+ const formattedOWRAddress = '0x' + OWRAddressData.slice(26, 66);
262
+
263
+ // Basic address validation
264
+ if (
265
+ formattedOWRAddress.length !== 42 ||
266
+ !formattedOWRAddress.startsWith('0x')
267
+ ) {
268
+ throw new Error(
269
+ `Invalid OWR contract address format: ${formattedOWRAddress}`,
270
+ );
271
+ }
272
+
273
+ return formattedOWRAddress;
274
+ } catch (error: any) {
275
+ // Re-throw if it's already our custom error
276
+ if (
277
+ error.message.includes('Failed to') ||
278
+ error.message.includes('OWR contract') ||
279
+ error.message.includes('Invalid OWR')
280
+ ) {
281
+ throw error;
282
+ }
283
+ // Handle unexpected errors
284
+ throw new Error(
285
+ `Unexpected error in createOWRContract: ${error.message ?? 'Unknown error during OWR contract creation'}`,
286
+ );
287
+ }
288
+ };
289
+
290
+ export const deploySplitterContract = async ({
291
+ signer,
292
+ accounts,
293
+ percentAllocations,
294
+ chainId,
295
+ distributorFee,
296
+ controllerAddress,
297
+ }: {
298
+ signer: SignerType;
299
+ accounts: ETH_ADDRESS[];
300
+ percentAllocations: number[];
301
+ chainId: number;
302
+ distributorFee: number;
303
+ controllerAddress: ETH_ADDRESS;
304
+ }): Promise<ETH_ADDRESS> => {
305
+ try {
306
+ const splitMainContractInstance = new Contract(
307
+ CHAIN_CONFIGURATION[chainId].SPLITMAIN_ADDRESS.address,
308
+ splitMainEthereumAbi,
309
+ signer,
310
+ );
311
+
312
+ let tx;
313
+ try {
314
+ tx = await splitMainContractInstance.createSplit(
315
+ accounts,
316
+ percentAllocations,
317
+ distributorFee,
318
+ controllerAddress,
319
+ );
320
+ } catch (error: any) {
321
+ throw new Error(
322
+ `Failed to submit splitter contract creation transaction: ${error.message ?? 'Transaction submission failed'}`,
323
+ );
324
+ }
325
+
326
+ let receipt;
327
+ try {
328
+ receipt = await tx.wait();
329
+ } catch (error: any) {
330
+ throw new Error(
331
+ `Splitter contract creation transaction failed or was reverted: ${error.message ?? 'Transaction execution failed'}`,
332
+ );
333
+ }
334
+
335
+ if (!receipt?.logs?.length) {
336
+ throw new Error(
337
+ 'Splitter contract creation transaction succeeded but no events were emitted - unable to determine contract address',
338
+ );
339
+ }
340
+
341
+ const splitterAddressData = receipt.logs[0]?.topics[1];
342
+ if (!splitterAddressData) {
343
+ throw new Error(
344
+ 'Splitter contract creation transaction succeeded but contract address could not be extracted from events',
345
+ );
346
+ }
347
+
348
+ const formattedSplitterAddress = '0x' + splitterAddressData.slice(26, 66);
349
+
350
+ // Basic address validation
351
+ if (
352
+ formattedSplitterAddress.length !== 42 ||
353
+ !formattedSplitterAddress.startsWith('0x')
354
+ ) {
355
+ throw new Error(
356
+ `Invalid splitter contract address format: ${formattedSplitterAddress}`,
357
+ );
358
+ }
359
+
360
+ return formattedSplitterAddress;
361
+ } catch (error: any) {
362
+ // Re-throw if it's already our custom error
363
+ if (
364
+ error.message.includes('Failed to') ||
365
+ error.message.includes('Splitter contract') ||
366
+ error.message.includes('Invalid splitter')
367
+ ) {
368
+ throw error;
369
+ }
370
+ // Handle unexpected errors
371
+ throw new Error(
372
+ `Unexpected error in deploySplitterContract: ${error.message ?? 'Unknown error during splitter contract creation'}`,
373
+ );
374
+ }
375
+ };
376
+
377
+ export const deploySplitterAndOWRContracts = async ({
378
+ owrArgs,
379
+ splitterArgs,
380
+ signer,
381
+ chainId,
382
+ }: {
383
+ owrArgs: OWRArgs;
384
+ splitterArgs: SplitArgs;
385
+ signer: SignerType;
386
+ chainId: number;
387
+ }): Promise<{ owrAddress: ETH_ADDRESS; splitterAddress: ETH_ADDRESS }> => {
388
+ const executeCalls: Call[] = [];
389
+
390
+ const splitTxData = encodeCreateSplitTxData(
391
+ splitterArgs.accounts,
392
+ splitterArgs.percentAllocations,
393
+ splitterArgs.distributorFee,
394
+ splitterArgs.controllerAddress,
395
+ );
396
+
397
+ const owrTxData = encodeCreateOWRecipientTxData(
398
+ owrArgs.recoveryAddress,
399
+ owrArgs.principalRecipient,
400
+ owrArgs.predictedSplitterAddress,
401
+ owrArgs.amountOfPrincipalStake,
402
+ );
403
+
404
+ executeCalls.push(
405
+ {
406
+ target: CHAIN_CONFIGURATION[chainId].SPLITMAIN_ADDRESS.address,
407
+ callData: splitTxData,
408
+ },
409
+ {
410
+ target: CHAIN_CONFIGURATION[chainId].OWR_FACTORY_ADDRESS.address,
411
+ callData: owrTxData,
412
+ },
413
+ );
414
+ const multicallAddess =
415
+ CHAIN_CONFIGURATION[chainId].MULTICALL_ADDRESS.address;
416
+
417
+ const executeMultiCalls = await multicall(
418
+ executeCalls,
419
+ signer,
420
+ multicallAddess,
421
+ );
422
+
423
+ const splitAddressData = executeMultiCalls?.logs[0]?.topics[1];
424
+ const formattedSplitterAddress = '0x' + splitAddressData?.slice(26, 66);
425
+ const owrAddressData = executeMultiCalls?.logs[1]?.topics[1];
426
+ const formattedOwrAddress = '0x' + owrAddressData?.slice(26, 66);
427
+
428
+ return {
429
+ owrAddress: formattedOwrAddress,
430
+ splitterAddress: formattedSplitterAddress,
431
+ };
432
+ };
433
+
434
+ export const getOWRTranches = async ({
435
+ owrAddress,
436
+ signer,
437
+ }: {
438
+ owrAddress: ETH_ADDRESS;
439
+ signer: SignerType;
440
+ }): Promise<OWRTranches> => {
441
+ try {
442
+ const owrContract = new Contract(owrAddress, OWRContract.abi, signer);
443
+
444
+ let res;
445
+ try {
446
+ res = await owrContract.getTranches();
447
+ } catch (error: any) {
448
+ throw new Error(
449
+ `Failed to call getTranches on OWR contract at ${owrAddress}: ${error.message ?? 'Contract call failed'}`,
450
+ );
451
+ }
452
+
453
+ if (!res) {
454
+ throw new Error(
455
+ `OWR contract at ${owrAddress} returned empty result for getTranches()`,
456
+ );
457
+ }
458
+
459
+ return {
460
+ principalRecipient: res.principalRecipient,
461
+ rewardRecipient: res.rewardRecipient,
462
+ amountOfPrincipalStake: res.amountOfPrincipalStake,
463
+ };
464
+ } catch (error: any) {
465
+ // Re-throw if it's already our custom error
466
+ if (
467
+ error.message.includes('Failed to') ||
468
+ error.message.includes('OWR contract')
469
+ ) {
470
+ throw error;
471
+ }
472
+ // Handle unexpected errors
473
+ throw new Error(
474
+ `Unexpected error in getOWRTranches: ${error.message ?? 'Unknown error while fetching OWR tranche data'}`,
475
+ );
476
+ }
477
+ };
478
+
479
+ export const multicall = async (
480
+ calls: Call[],
481
+ signer: SignerType,
482
+ multicallAddress: string,
483
+ ): Promise<any> => {
484
+ try {
485
+ const multiCallContractInstance = new Contract(
486
+ multicallAddress,
487
+ MultiCallContract.abi,
488
+ signer,
489
+ );
490
+
491
+ let tx;
492
+ try {
493
+ tx = await multiCallContractInstance.aggregate(calls);
494
+ } catch (error: any) {
495
+ throw new Error(
496
+ `Failed to submit multicall transaction: ${error.message ?? 'Transaction submission failed'}`,
497
+ );
498
+ }
499
+
500
+ let receipt;
501
+ try {
502
+ receipt = await tx.wait();
503
+ } catch (error: any) {
504
+ throw new Error(
505
+ `Multicall transaction failed or was reverted: ${error.message ?? 'Transaction execution failed'}`,
506
+ );
507
+ }
508
+
509
+ if (!receipt) {
510
+ throw new Error(
511
+ 'Multicall transaction succeeded but no receipt was returned',
512
+ );
513
+ }
514
+
515
+ return receipt;
516
+ } catch (error: any) {
517
+ // Re-throw if it's already our custom error
518
+ if (
519
+ error.message.includes('Failed to') ||
520
+ error.message.includes('Multicall transaction')
521
+ ) {
522
+ throw error;
523
+ }
524
+ // Handle unexpected errors
525
+ throw new Error(
526
+ `Unexpected error in multicall: ${error.message ?? 'Unknown error during multicall execution'}`,
527
+ );
528
+ }
529
+ };
530
+
531
+ const encodeCreateSplitTxData = (
532
+ accounts: ETH_ADDRESS[],
533
+ percentAllocations: number[],
534
+ distributorFee: number,
535
+ controller: ETH_ADDRESS,
536
+ ): ETH_ADDRESS => {
537
+ return splitMainContractInterface.encodeFunctionData('createSplit', [
538
+ accounts,
539
+ percentAllocations,
540
+ distributorFee,
541
+ controller,
542
+ ]);
543
+ };
544
+
545
+ const encodeCreateOWRecipientTxData = (
546
+ recoveryAddress: ETH_ADDRESS,
547
+ principalRecipient: ETH_ADDRESS,
548
+ rewardRecipient: ETH_ADDRESS,
549
+ amountOfPrincipalStake: number,
550
+ ): ETH_ADDRESS => {
551
+ return owrFactoryContractInterface.encodeFunctionData('createOWRecipient', [
552
+ recoveryAddress,
553
+ principalRecipient,
554
+ rewardRecipient,
555
+ parseEther(amountOfPrincipalStake.toString()),
556
+ ]);
557
+ };