@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.
- package/dist/cjs/package.json +3 -3
- package/dist/cjs/src/constants.js +13 -1
- package/dist/cjs/src/exits/ethUtils.js +57 -0
- package/dist/cjs/src/exits/exit.js +502 -0
- package/dist/cjs/src/exits/verificationHelpers.js +86 -0
- package/dist/cjs/src/{incentiveHelpers.js → incentives/incentiveHelpers.js} +1 -1
- package/dist/cjs/src/{incentives.js → incentives/incentives.js} +3 -3
- package/dist/cjs/src/index.js +22 -4
- package/dist/cjs/src/splits/splitHelpers.js +327 -0
- package/dist/cjs/src/types.js +1 -0
- package/dist/cjs/test/exit/ethUtils.spec.js +83 -0
- package/dist/cjs/test/exit/exit.spec.js +399 -0
- package/dist/cjs/test/exit/verificationHelpers.spec.js +68 -0
- package/dist/cjs/test/{incentives.test.js → incentives/incentives.spec.js} +4 -4
- package/dist/esm/package.json +3 -3
- package/dist/esm/src/constants.js +12 -0
- package/dist/esm/src/exits/ethUtils.js +52 -0
- package/dist/esm/src/exits/exit.js +475 -0
- package/dist/esm/src/exits/verificationHelpers.js +81 -0
- package/dist/esm/src/{incentiveHelpers.js → incentives/incentiveHelpers.js} +1 -1
- package/dist/esm/src/{incentives.js → incentives/incentives.js} +3 -3
- package/dist/esm/src/index.js +20 -3
- package/dist/esm/src/splits/splitHelpers.js +317 -0
- package/dist/esm/src/types.js +1 -0
- package/dist/esm/test/exit/ethUtils.spec.js +81 -0
- package/dist/esm/test/exit/exit.spec.js +374 -0
- package/dist/esm/test/exit/verificationHelpers.spec.js +66 -0
- package/dist/esm/test/{incentives.test.js → incentives/incentives.spec.js} +4 -4
- package/dist/types/src/constants.d.ts +5 -0
- package/dist/types/src/exits/ethUtils.d.ts +13 -0
- package/dist/types/src/exits/exit.d.ts +200 -0
- package/dist/types/src/exits/verificationHelpers.d.ts +20 -0
- package/dist/types/src/{incentiveHelpers.d.ts → incentives/incentiveHelpers.d.ts} +1 -1
- package/dist/types/src/{incentives.d.ts → incentives/incentives.d.ts} +1 -1
- package/dist/types/src/index.d.ts +16 -2
- package/dist/types/src/{splitHelpers.d.ts → splits/splitHelpers.d.ts} +1 -1
- package/dist/types/src/types.d.ts +133 -0
- package/dist/types/test/exit/verificationHelpers.spec.d.ts +1 -0
- package/dist/types/test/incentives/incentives.spec.d.ts +1 -0
- package/package.json +3 -3
- package/src/constants.ts +13 -0
- package/src/exits/ethUtils.ts +49 -0
- package/src/exits/exit.ts +661 -0
- package/src/exits/verificationHelpers.ts +110 -0
- package/src/{incentiveHelpers.ts → incentives/incentiveHelpers.ts} +2 -2
- package/src/{incentives.ts → incentives/incentives.ts} +3 -3
- package/src/index.ts +29 -3
- package/src/splits/splitHelpers.ts +557 -0
- package/src/types.ts +158 -0
- package/dist/cjs/src/splitHelpers.js +0 -187
- package/dist/cjs/test/methods.test.js +0 -418
- package/dist/esm/src/splitHelpers.js +0 -177
- package/dist/esm/test/methods.test.js +0 -393
- package/src/splitHelpers.ts +0 -355
- /package/dist/types/test/{incentives.test.d.ts → exit/ethUtils.spec.d.ts} +0 -0
- /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
|