@obolnetwork/obol-sdk 2.5.1 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/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 +502 -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/splits/splitHelpers.js +327 -0
  10. package/dist/cjs/src/types.js +1 -0
  11. package/dist/cjs/test/exit/ethUtils.spec.js +83 -0
  12. package/dist/cjs/test/exit/exit.spec.js +399 -0
  13. package/dist/cjs/test/exit/verificationHelpers.spec.js +68 -0
  14. package/dist/cjs/test/{incentives.test.js → incentives/incentives.spec.js} +4 -4
  15. package/dist/esm/package.json +3 -3
  16. package/dist/esm/src/constants.js +12 -0
  17. package/dist/esm/src/exits/ethUtils.js +52 -0
  18. package/dist/esm/src/exits/exit.js +475 -0
  19. package/dist/esm/src/exits/verificationHelpers.js +81 -0
  20. package/dist/esm/src/{incentiveHelpers.js → incentives/incentiveHelpers.js} +1 -1
  21. package/dist/esm/src/{incentives.js → incentives/incentives.js} +3 -3
  22. package/dist/esm/src/index.js +20 -3
  23. package/dist/esm/src/splits/splitHelpers.js +317 -0
  24. package/dist/esm/src/types.js +1 -0
  25. package/dist/esm/test/exit/ethUtils.spec.js +81 -0
  26. package/dist/esm/test/exit/exit.spec.js +374 -0
  27. package/dist/esm/test/exit/verificationHelpers.spec.js +66 -0
  28. package/dist/esm/test/{incentives.test.js → incentives/incentives.spec.js} +4 -4
  29. package/dist/types/src/constants.d.ts +5 -0
  30. package/dist/types/src/exits/ethUtils.d.ts +13 -0
  31. package/dist/types/src/exits/exit.d.ts +200 -0
  32. package/dist/types/src/exits/verificationHelpers.d.ts +20 -0
  33. package/dist/types/src/{incentiveHelpers.d.ts → incentives/incentiveHelpers.d.ts} +1 -1
  34. package/dist/types/src/{incentives.d.ts → incentives/incentives.d.ts} +1 -1
  35. package/dist/types/src/index.d.ts +16 -2
  36. package/dist/types/src/{splitHelpers.d.ts → splits/splitHelpers.d.ts} +1 -1
  37. package/dist/types/src/types.d.ts +133 -0
  38. package/dist/types/test/exit/verificationHelpers.spec.d.ts +1 -0
  39. package/dist/types/test/incentives/incentives.spec.d.ts +1 -0
  40. package/package.json +3 -3
  41. package/src/constants.ts +13 -0
  42. package/src/exits/ethUtils.ts +49 -0
  43. package/src/exits/exit.ts +661 -0
  44. package/src/exits/verificationHelpers.ts +110 -0
  45. package/src/{incentiveHelpers.ts → incentives/incentiveHelpers.ts} +2 -2
  46. package/src/{incentives.ts → incentives/incentives.ts} +3 -3
  47. package/src/index.ts +29 -3
  48. package/src/splits/splitHelpers.ts +557 -0
  49. package/src/types.ts +158 -0
  50. package/dist/cjs/src/splitHelpers.js +0 -187
  51. package/dist/cjs/test/methods.test.js +0 -418
  52. package/dist/esm/src/splitHelpers.js +0 -177
  53. package/dist/esm/test/methods.test.js +0 -393
  54. package/src/splitHelpers.ts +0 -355
  55. /package/dist/types/test/{incentives.test.d.ts → exit/ethUtils.spec.d.ts} +0 -0
  56. /package/dist/types/test/{methods.test.d.ts → exit/exit.spec.d.ts} +0 -0
@@ -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
+ };
package/src/types.ts CHANGED
@@ -352,3 +352,161 @@ export type SignerType = Signer | JsonRpcSigner | Wallet;
352
352
  * claimIncentives Response
353
353
  */
354
354
  export type ClaimIncentivesResponse = { txHash: string | null };
355
+
356
+ /**
357
+ * Represents the structure of an Ethereum operator for exit validation, primarily their ENR.
358
+ */
359
+ export interface ExitOperator {
360
+ /** The operator's Ethereum Node Record (ENR). */
361
+ enr: string;
362
+ }
363
+
364
+ /**
365
+ * Represents the core definition of a cluster relevant for exit validation.
366
+ */
367
+ export interface ExitClusterDefinition {
368
+ /** The cluster nodes operators with their ENRs. */
369
+ operators: ExitOperator[];
370
+
371
+ /** The cluster fork version. */
372
+ fork_version: string;
373
+
374
+ /** The distributed validator threshold. */
375
+ threshold: number;
376
+ }
377
+
378
+ /**
379
+ * Represents a distributed validator's information relevant for exit validation.
380
+ */
381
+ export interface ExitDistributedValidator {
382
+ /** The public key of the distributed validator. */
383
+ distributed_public_key: string;
384
+
385
+ /** The public key shares of the distributed validator. */
386
+ public_shares: string[];
387
+ }
388
+
389
+ /**
390
+ * Combined cluster information needed for exit validation in the SDK.
391
+ */
392
+ export interface ExitClusterConfig {
393
+ /** The cluster definition with operators, fork version and threshold. */
394
+ definition: ExitClusterDefinition;
395
+
396
+ /** The cluster distributed validators. */
397
+ distributed_validators: ExitDistributedValidator[];
398
+ }
399
+
400
+ /**
401
+ * Represents the message part of a signed exit for exit validation.
402
+ */
403
+ export interface ExitValidationMessage {
404
+ /** The epoch at which the validator wishes to exit. */
405
+ epoch: string;
406
+
407
+ /** The index of the validator in the beacon chain. */
408
+ validator_index: string;
409
+ }
410
+
411
+ /**
412
+ * Represents a signed exit message for exit validation.
413
+ */
414
+ export interface SignedExitValidationMessage {
415
+ /** The exit message containing epoch and validator index. */
416
+ message: ExitValidationMessage;
417
+
418
+ /** BLS signature of the exit message. */
419
+ signature: string;
420
+ }
421
+
422
+ /**
423
+ * Represents a single partial exit blob for exit validation.
424
+ */
425
+ export interface ExitValidationBlob {
426
+ /** The public key of the validator to exit. */
427
+ public_key: string;
428
+
429
+ /** The signed exit message for the validator. */
430
+ signed_exit_message: SignedExitValidationMessage;
431
+ }
432
+
433
+ /**
434
+ * Represents the overall exit payload structure for exit validation.
435
+ */
436
+ export interface ExitValidationPayload {
437
+ /** Array of partial exits for validators. */
438
+ partial_exits: ExitValidationBlob[];
439
+
440
+ /** Operator's share index (1-based). */
441
+ share_idx: number;
442
+
443
+ /** Signature of the ExitValidationPayload by the operator. */
444
+ signature: string;
445
+ }
446
+
447
+ /**
448
+ * Represents the data structure for an already existing exit blob for exit validation.
449
+ */
450
+ export interface ExistingExitValidationBlobData {
451
+ /**
452
+ * The BLS public key of the validator in hex format
453
+ */
454
+ public_key: string;
455
+ /**
456
+ * The epoch number when the exit is scheduled to occur
457
+ */
458
+ epoch: string;
459
+ /**
460
+ * The unique index of the validator in the beacon chain
461
+ */
462
+ validator_index: string;
463
+ /**
464
+ * Array of distributed validator shares exit data, where each share contains
465
+ * the partial exit signature from each operator in the cluster
466
+ */
467
+ shares_exit_data: Array<Record<string, { partial_exit_signature: string }>>;
468
+ }
469
+
470
+ // ExitBlob is an exit message alongside its BLS12-381 hex-encoded signature.
471
+ export interface FullExitBlob {
472
+ /**
473
+ * The signed voluntary exit message containing the exit details and signature
474
+ */
475
+ signed_exit_message: {
476
+ /**
477
+ * The voluntary exit message details
478
+ */
479
+ message: {
480
+ /**
481
+ * The epoch number when the validator exit will be processed
482
+ */
483
+ epoch: string;
484
+ /**
485
+ * The unique index of the validator requesting to exit
486
+ */
487
+ validator_index: string;
488
+ };
489
+ /**
490
+ * The BLS12-381 hex-encoded signature of the exit message
491
+ */
492
+ signature: string;
493
+ };
494
+
495
+ /** The public key of the validator to exit. */
496
+ public_key: string;
497
+ }
498
+
499
+ /**
500
+ * Generic HTTP request function type.
501
+ * Args:
502
+ * url: string - The URL to request.
503
+ * config?: Record<string, any> - Optional request configuration (e.g., method, headers, body for POST).
504
+ * Returns:
505
+ * Promise<any> - The response data.
506
+ */
507
+ export type HttpRequestFunc = (
508
+ url: string,
509
+ config?: Record<string, any>,
510
+ ) => Promise<any>;
511
+
512
+ // Add other SDK-specific types below or import from other type files