@obolnetwork/obol-sdk 2.7.0 → 2.8.1
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 +1 -1
- package/dist/cjs/src/abi/OVMFactory.js +620 -0
- package/dist/cjs/src/abi/splitV2FactoryAbi.js +420 -0
- package/dist/cjs/src/ajv.js +33 -6
- package/dist/cjs/src/bytecodes.js +10 -1
- package/dist/cjs/src/constants.js +71 -15
- package/dist/cjs/src/index.js +21 -9
- package/dist/cjs/src/schema.js +70 -1
- package/dist/cjs/src/splits/splitHelpers.js +188 -14
- package/dist/cjs/src/splits/splits.js +283 -0
- package/dist/cjs/src/types.js +0 -1
- package/dist/cjs/test/client/ajv.spec.js +269 -0
- package/dist/cjs/test/client/methods.spec.js +418 -0
- package/dist/cjs/test/fixtures.js +13 -1
- package/dist/cjs/test/splits/splits.spec.js +239 -0
- package/dist/esm/package.json +1 -1
- package/dist/esm/src/abi/OVMFactory.js +617 -0
- package/dist/esm/src/abi/splitV2FactoryAbi.js +417 -0
- package/dist/esm/src/ajv.js +33 -6
- package/dist/esm/src/bytecodes.js +9 -0
- package/dist/esm/src/constants.js +70 -15
- package/dist/esm/src/index.js +20 -9
- package/dist/esm/src/schema.js +70 -1
- package/dist/esm/src/splits/splitHelpers.js +182 -13
- package/dist/esm/src/splits/splits.js +279 -0
- package/dist/esm/src/types.js +0 -1
- package/dist/esm/test/client/ajv.spec.js +267 -0
- package/dist/esm/test/client/methods.spec.js +393 -0
- package/dist/esm/test/fixtures.js +12 -0
- package/dist/esm/test/splits/splits.spec.js +237 -0
- package/dist/types/src/abi/OVMFactory.d.ts +662 -0
- package/dist/types/src/abi/splitV2FactoryAbi.d.ts +60 -0
- package/dist/types/src/bytecodes.d.ts +9 -0
- package/dist/types/src/constants.d.ts +10 -21
- package/dist/types/src/exits/verificationHelpers.d.ts +1 -0
- package/dist/types/src/index.d.ts +7 -0
- package/dist/types/src/schema.d.ts +145 -0
- package/dist/types/src/splits/splitHelpers.d.ts +39 -2
- package/dist/types/src/splits/splits.d.ts +51 -0
- package/dist/types/src/types.d.ts +87 -2
- package/dist/types/src/verification/common.d.ts +1 -0
- package/dist/types/test/client/ajv.spec.d.ts +1 -0
- package/dist/types/test/client/methods.spec.d.ts +1 -0
- package/dist/types/test/fixtures.d.ts +10 -0
- package/dist/types/test/splits/splits.spec.d.ts +1 -0
- package/package.json +1 -1
- package/src/abi/OVMFactory.ts +617 -0
- package/src/abi/splitV2FactoryAbi.ts +417 -0
- package/src/ajv.ts +55 -13
- package/src/bytecodes.ts +19 -0
- package/src/constants.ts +84 -16
- package/src/index.ts +36 -15
- package/src/schema.ts +79 -0
- package/src/splits/splitHelpers.ts +373 -18
- package/src/splits/splits.ts +406 -0
- package/src/types.ts +100 -3
package/src/index.ts
CHANGED
|
@@ -12,10 +12,10 @@ import {
|
|
|
12
12
|
TermsAndConditionsSigningTypes,
|
|
13
13
|
DEFAULT_BASE_VERSION,
|
|
14
14
|
TERMS_AND_CONDITIONS_HASH,
|
|
15
|
-
AVAILABLE_SPLITTER_CHAINS,
|
|
16
15
|
CHAIN_CONFIGURATION,
|
|
17
16
|
DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT,
|
|
18
17
|
OBOL_SDK_EMAIL,
|
|
18
|
+
isChainSupportedForSplitters,
|
|
19
19
|
} from './constants.js';
|
|
20
20
|
import { ConflictError } from './errors.js';
|
|
21
21
|
import {
|
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
import { isContractAvailable } from './utils.js';
|
|
50
50
|
import { Incentives } from './incentives/incentives.js';
|
|
51
51
|
import { Exit } from './exits/exit.js';
|
|
52
|
+
import { ObolSplits } from './splits/splits.js';
|
|
52
53
|
export * from './types.js';
|
|
53
54
|
export * from './services.js';
|
|
54
55
|
export * from './verification/signature-validator.js';
|
|
@@ -56,6 +57,7 @@ export * from './verification/common.js';
|
|
|
56
57
|
export * from './constants.js';
|
|
57
58
|
export { Incentives } from './incentives/incentives.js';
|
|
58
59
|
export { Exit } from './exits/exit.js';
|
|
60
|
+
export { ObolSplits } from './splits/splits.js';
|
|
59
61
|
|
|
60
62
|
/**
|
|
61
63
|
* Obol sdk Client can be used for creating, managing and activating distributed validators.
|
|
@@ -78,6 +80,12 @@ export class Client extends Base {
|
|
|
78
80
|
*/
|
|
79
81
|
public exit: Exit;
|
|
80
82
|
|
|
83
|
+
/**
|
|
84
|
+
* The splits module, responsible for managing splits.
|
|
85
|
+
* @type {ObolSplits}
|
|
86
|
+
*/
|
|
87
|
+
public splits: ObolSplits;
|
|
88
|
+
|
|
81
89
|
/**
|
|
82
90
|
* The blockchain provider, used to interact with the network.
|
|
83
91
|
* It can be null, undefined, or a valid provider instance and defaults to the Signer provider if Signer is passed.
|
|
@@ -112,6 +120,7 @@ export class Client extends Base {
|
|
|
112
120
|
this.provider,
|
|
113
121
|
);
|
|
114
122
|
this.exit = new Exit(this.chainId, this.provider);
|
|
123
|
+
this.splits = new ObolSplits(this.signer, this.chainId, this.provider);
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
/**
|
|
@@ -205,28 +214,35 @@ export class Client extends Base {
|
|
|
205
214
|
);
|
|
206
215
|
|
|
207
216
|
// Check if we allow splitters on this chainId
|
|
208
|
-
if (!
|
|
217
|
+
if (!isChainSupportedForSplitters(this.chainId)) {
|
|
209
218
|
throw new Error(
|
|
210
219
|
`Splitter configuration is not supported on ${this.chainId} chain`,
|
|
211
220
|
);
|
|
212
221
|
}
|
|
213
222
|
|
|
223
|
+
const chainConfig = CHAIN_CONFIGURATION[this.chainId];
|
|
224
|
+
if (!chainConfig?.SPLITMAIN_CONTRACT) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`SPLITMAIN_CONTRACT is not configured for chain ${this.chainId}`,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
214
230
|
const checkSplitMainAddress = await isContractAvailable(
|
|
215
|
-
|
|
231
|
+
chainConfig.SPLITMAIN_CONTRACT.address,
|
|
216
232
|
this.signer.provider as ProviderType,
|
|
217
|
-
|
|
233
|
+
chainConfig.SPLITMAIN_CONTRACT.bytecode,
|
|
218
234
|
);
|
|
219
235
|
|
|
220
236
|
const checkMulticallAddress = await isContractAvailable(
|
|
221
|
-
|
|
237
|
+
chainConfig.MULTICALL_CONTRACT.address,
|
|
222
238
|
this.signer.provider as ProviderType,
|
|
223
|
-
|
|
239
|
+
chainConfig.MULTICALL_CONTRACT.bytecode,
|
|
224
240
|
);
|
|
225
241
|
|
|
226
242
|
const checkOWRFactoryAddress = await isContractAvailable(
|
|
227
|
-
|
|
243
|
+
chainConfig.OWR_FACTORY_CONTRACT.address,
|
|
228
244
|
this.signer.provider as ProviderType,
|
|
229
|
-
|
|
245
|
+
chainConfig.OWR_FACTORY_CONTRACT.bytecode,
|
|
230
246
|
);
|
|
231
247
|
|
|
232
248
|
if (
|
|
@@ -240,8 +256,7 @@ export class Client extends Base {
|
|
|
240
256
|
}
|
|
241
257
|
|
|
242
258
|
const retroActiveFundingRecipient = {
|
|
243
|
-
account:
|
|
244
|
-
CHAIN_CONFIGURATION[this.chainId].RETROACTIVE_FUNDING_ADDRESS.address,
|
|
259
|
+
account: chainConfig.RETROACTIVE_FUNDING_CONTRACT.address,
|
|
245
260
|
percentAllocation: validatedPayload.ObolRAFSplit,
|
|
246
261
|
};
|
|
247
262
|
|
|
@@ -320,16 +335,23 @@ export class Client extends Base {
|
|
|
320
335
|
);
|
|
321
336
|
|
|
322
337
|
// Check if we allow splitters on this chainId
|
|
323
|
-
if (!
|
|
338
|
+
if (!isChainSupportedForSplitters(this.chainId)) {
|
|
324
339
|
throw new Error(
|
|
325
340
|
`Splitter configuration is not supported on ${this.chainId} chain`,
|
|
326
341
|
);
|
|
327
342
|
}
|
|
328
343
|
|
|
344
|
+
const chainConfig = CHAIN_CONFIGURATION[this.chainId];
|
|
345
|
+
if (!chainConfig?.SPLITMAIN_CONTRACT) {
|
|
346
|
+
throw new Error(
|
|
347
|
+
`SPLITMAIN_CONTRACT is not configured for chain ${this.chainId}`,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
329
351
|
const checkSplitMainAddress = await isContractAvailable(
|
|
330
|
-
|
|
352
|
+
chainConfig.SPLITMAIN_CONTRACT.address,
|
|
331
353
|
this.signer.provider as ProviderType,
|
|
332
|
-
|
|
354
|
+
chainConfig.SPLITMAIN_CONTRACT.bytecode,
|
|
333
355
|
);
|
|
334
356
|
|
|
335
357
|
if (!checkSplitMainAddress) {
|
|
@@ -339,8 +361,7 @@ export class Client extends Base {
|
|
|
339
361
|
}
|
|
340
362
|
|
|
341
363
|
const retroActiveFundingRecipient = {
|
|
342
|
-
account:
|
|
343
|
-
CHAIN_CONFIGURATION[this.chainId].RETROACTIVE_FUNDING_ADDRESS.address,
|
|
364
|
+
account: chainConfig.RETROACTIVE_FUNDING_CONTRACT.address,
|
|
344
365
|
percentAllocation: validatedPayload.ObolRAFSplit,
|
|
345
366
|
};
|
|
346
367
|
|
package/src/schema.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { ZeroAddress } from 'ethers';
|
|
|
2
2
|
import {
|
|
3
3
|
DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT,
|
|
4
4
|
DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT,
|
|
5
|
+
PRINCIPAL_THRESHOLD,
|
|
5
6
|
} from './constants';
|
|
6
7
|
import { VALID_DEPOSIT_AMOUNTS, VALID_NON_COMPOUNDING_AMOUNTS } from './ajv';
|
|
7
8
|
|
|
@@ -150,3 +151,81 @@ export const rewardsSplitterPayloadSchema = {
|
|
|
150
151
|
validateRewardsSplitRecipients: true,
|
|
151
152
|
required: ['splitRecipients', 'principalRecipient', 'etherAmount'],
|
|
152
153
|
};
|
|
154
|
+
|
|
155
|
+
export const ovmBaseSplitPayload = {
|
|
156
|
+
rewardSplitRecipients: {
|
|
157
|
+
type: 'array',
|
|
158
|
+
items: {
|
|
159
|
+
type: 'object',
|
|
160
|
+
properties: {
|
|
161
|
+
address: {
|
|
162
|
+
type: 'string',
|
|
163
|
+
pattern: '^0x[a-fA-F0-9]{40}$',
|
|
164
|
+
},
|
|
165
|
+
percentAllocation: { type: 'number' },
|
|
166
|
+
},
|
|
167
|
+
required: ['address', 'percentAllocation'],
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
OVMOwnerAddress: {
|
|
171
|
+
type: 'string',
|
|
172
|
+
pattern: '^0x[a-fA-F0-9]{40}$',
|
|
173
|
+
},
|
|
174
|
+
splitOwnerAddress: {
|
|
175
|
+
type: 'string',
|
|
176
|
+
pattern: '^0x[a-fA-F0-9]{40}$',
|
|
177
|
+
default: ZeroAddress,
|
|
178
|
+
},
|
|
179
|
+
principalThreshold: {
|
|
180
|
+
type: 'number',
|
|
181
|
+
minimum: 16,
|
|
182
|
+
default: PRINCIPAL_THRESHOLD,
|
|
183
|
+
},
|
|
184
|
+
distributorFeePercent: {
|
|
185
|
+
type: 'number',
|
|
186
|
+
minimum: 0,
|
|
187
|
+
maximum: 10,
|
|
188
|
+
default: 0,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export const ovmRewardsSplitPayloadSchema = {
|
|
193
|
+
type: 'object',
|
|
194
|
+
properties: {
|
|
195
|
+
...ovmBaseSplitPayload,
|
|
196
|
+
principalRecipient: {
|
|
197
|
+
type: 'string',
|
|
198
|
+
pattern: '^0x[a-fA-F0-9]{40}$',
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
validateOVMRewardsSplitRecipients: true,
|
|
202
|
+
required: ['rewardSplitRecipients', 'OVMOwnerAddress', 'principalRecipient'],
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export const ovmTotalSplitPayloadSchema = {
|
|
206
|
+
type: 'object',
|
|
207
|
+
properties: {
|
|
208
|
+
...ovmBaseSplitPayload,
|
|
209
|
+
principalSplitRecipients: {
|
|
210
|
+
type: 'array',
|
|
211
|
+
items: {
|
|
212
|
+
type: 'object',
|
|
213
|
+
properties: {
|
|
214
|
+
address: {
|
|
215
|
+
type: 'string',
|
|
216
|
+
pattern: '^0x[a-fA-F0-9]{40}$',
|
|
217
|
+
},
|
|
218
|
+
percentAllocation: { type: 'number' },
|
|
219
|
+
},
|
|
220
|
+
required: ['address', 'percentAllocation'],
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
validateOVMRewardsSplitRecipients: true,
|
|
225
|
+
validateOVMTotalSplitRecipients: true,
|
|
226
|
+
required: [
|
|
227
|
+
'rewardSplitRecipients',
|
|
228
|
+
'principalSplitRecipients',
|
|
229
|
+
'OVMOwnerAddress',
|
|
230
|
+
],
|
|
231
|
+
};
|
|
@@ -4,15 +4,22 @@ import {
|
|
|
4
4
|
type ETH_ADDRESS,
|
|
5
5
|
type SplitRecipient,
|
|
6
6
|
type SignerType,
|
|
7
|
+
type SplitV2Recipient,
|
|
8
|
+
type OVMArgs,
|
|
9
|
+
type ChainConfig,
|
|
7
10
|
} from '../types';
|
|
8
11
|
import { Contract, Interface, parseEther, ZeroAddress } from 'ethers';
|
|
9
12
|
import { OWRContract, OWRFactoryContract } from '../abi/OWR';
|
|
13
|
+
import { OVMFactoryContract } from '../abi/OVMFactory';
|
|
10
14
|
import { splitMainEthereumAbi } from '../abi/SplitMain';
|
|
11
15
|
import { MultiCallContract } from '../abi/Multicall';
|
|
12
16
|
import { CHAIN_CONFIGURATION } from '../constants';
|
|
17
|
+
import { splitV2FactoryAbi } from '../abi/splitV2FactoryAbi';
|
|
13
18
|
|
|
14
19
|
const splitMainContractInterface = new Interface(splitMainEthereumAbi);
|
|
15
20
|
const owrFactoryContractInterface = new Interface(OWRFactoryContract.abi);
|
|
21
|
+
const ovmFactoryContractInterface = new Interface(OVMFactoryContract.abi);
|
|
22
|
+
const splitV2FactoryInterface = new Interface(splitV2FactoryAbi);
|
|
16
23
|
|
|
17
24
|
type Call = {
|
|
18
25
|
target: ETH_ADDRESS;
|
|
@@ -33,14 +40,39 @@ type SplitArgs = {
|
|
|
33
40
|
controllerAddress: ETH_ADDRESS;
|
|
34
41
|
};
|
|
35
42
|
|
|
43
|
+
// Helper function to extract common recipient formatting logic
|
|
44
|
+
const formatRecipientsCommon = (
|
|
45
|
+
recipients: SplitRecipient[] | SplitV2Recipient[],
|
|
46
|
+
): {
|
|
47
|
+
sortedRecipients: any[];
|
|
48
|
+
getAddress: (item: any) => string;
|
|
49
|
+
getPercentAllocation: (item: any) => number;
|
|
50
|
+
} => {
|
|
51
|
+
const copiedRecipients = [...recipients];
|
|
52
|
+
|
|
53
|
+
// Handle both SplitRecipient and SplitV2Recipient types
|
|
54
|
+
const getAddress = (item: any): string => item.account || item.address;
|
|
55
|
+
const getPercentAllocation = (item: any): number => item.percentAllocation;
|
|
56
|
+
|
|
57
|
+
// Has to be sorted when passed
|
|
58
|
+
copiedRecipients.sort((a, b) => getAddress(a).localeCompare(getAddress(b)));
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
sortedRecipients: copiedRecipients,
|
|
62
|
+
getAddress,
|
|
63
|
+
getPercentAllocation,
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
36
67
|
export const formatSplitRecipients = (
|
|
37
68
|
recipients: SplitRecipient[],
|
|
38
69
|
): { accounts: ETH_ADDRESS[]; percentAllocations: number[] } => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
70
|
+
const { sortedRecipients, getAddress, getPercentAllocation } =
|
|
71
|
+
formatRecipientsCommon(recipients);
|
|
72
|
+
|
|
73
|
+
const accounts = sortedRecipients.map(item => getAddress(item));
|
|
74
|
+
const percentAllocations = sortedRecipients.map(recipient => {
|
|
75
|
+
const splitTostring = (getPercentAllocation(recipient) * 1e4).toFixed(0);
|
|
44
76
|
return parseInt(splitTostring);
|
|
45
77
|
});
|
|
46
78
|
return { accounts, percentAllocations };
|
|
@@ -63,7 +95,7 @@ export const predictSplitterAddress = async ({
|
|
|
63
95
|
}): Promise<ETH_ADDRESS> => {
|
|
64
96
|
try {
|
|
65
97
|
const splitMainContractInstance = new Contract(
|
|
66
|
-
|
|
98
|
+
getChainConfig(chainId).SPLITMAIN_CONTRACT.address,
|
|
67
99
|
splitMainEthereumAbi,
|
|
68
100
|
signer,
|
|
69
101
|
);
|
|
@@ -217,7 +249,7 @@ const createOWRContract = async ({
|
|
|
217
249
|
}): Promise<ETH_ADDRESS> => {
|
|
218
250
|
try {
|
|
219
251
|
const OWRFactoryInstance = new Contract(
|
|
220
|
-
|
|
252
|
+
getChainConfig(chainId).OWR_FACTORY_CONTRACT.address,
|
|
221
253
|
OWRFactoryContract.abi,
|
|
222
254
|
signer,
|
|
223
255
|
);
|
|
@@ -304,7 +336,7 @@ export const deploySplitterContract = async ({
|
|
|
304
336
|
}): Promise<ETH_ADDRESS> => {
|
|
305
337
|
try {
|
|
306
338
|
const splitMainContractInstance = new Contract(
|
|
307
|
-
|
|
339
|
+
getChainConfig(chainId).SPLITMAIN_CONTRACT.address,
|
|
308
340
|
splitMainEthereumAbi,
|
|
309
341
|
signer,
|
|
310
342
|
);
|
|
@@ -403,22 +435,16 @@ export const deploySplitterAndOWRContracts = async ({
|
|
|
403
435
|
|
|
404
436
|
executeCalls.push(
|
|
405
437
|
{
|
|
406
|
-
target:
|
|
438
|
+
target: getChainConfig(chainId).SPLITMAIN_CONTRACT.address,
|
|
407
439
|
callData: splitTxData,
|
|
408
440
|
},
|
|
409
441
|
{
|
|
410
|
-
target:
|
|
442
|
+
target: getChainConfig(chainId).OWR_FACTORY_CONTRACT.address,
|
|
411
443
|
callData: owrTxData,
|
|
412
444
|
},
|
|
413
445
|
);
|
|
414
|
-
const multicallAddess =
|
|
415
|
-
CHAIN_CONFIGURATION[chainId].MULTICALL_ADDRESS.address;
|
|
416
446
|
|
|
417
|
-
const executeMultiCalls = await multicall(
|
|
418
|
-
executeCalls,
|
|
419
|
-
signer,
|
|
420
|
-
multicallAddess,
|
|
421
|
-
);
|
|
447
|
+
const executeMultiCalls = await multicall(executeCalls, signer, chainId);
|
|
422
448
|
|
|
423
449
|
const splitAddressData = executeMultiCalls?.logs[0]?.topics[1];
|
|
424
450
|
const formattedSplitterAddress = '0x' + splitAddressData?.slice(26, 66);
|
|
@@ -479,9 +505,11 @@ export const getOWRTranches = async ({
|
|
|
479
505
|
export const multicall = async (
|
|
480
506
|
calls: Call[],
|
|
481
507
|
signer: SignerType,
|
|
482
|
-
|
|
508
|
+
chainId: number,
|
|
483
509
|
): Promise<any> => {
|
|
484
510
|
try {
|
|
511
|
+
const chainConfig = getChainConfig(chainId);
|
|
512
|
+
const multicallAddress = chainConfig.MULTICALL_CONTRACT.address;
|
|
485
513
|
const multiCallContractInstance = new Contract(
|
|
486
514
|
multicallAddress,
|
|
487
515
|
MultiCallContract.abi,
|
|
@@ -555,3 +583,330 @@ const encodeCreateOWRecipientTxData = (
|
|
|
555
583
|
parseEther(amountOfPrincipalStake.toString()),
|
|
556
584
|
]);
|
|
557
585
|
};
|
|
586
|
+
|
|
587
|
+
// OVM and SplitV2 Helper Functions
|
|
588
|
+
|
|
589
|
+
// Helper function to format recipients specifically for SplitV2 (returns SplitV2Recipient[])
|
|
590
|
+
export const formatRecipientsForSplitV2 = (
|
|
591
|
+
splitRecipients: SplitRecipient[] | SplitV2Recipient[],
|
|
592
|
+
): SplitV2Recipient[] => {
|
|
593
|
+
const { sortedRecipients, getAddress, getPercentAllocation } =
|
|
594
|
+
formatRecipientsCommon(splitRecipients);
|
|
595
|
+
|
|
596
|
+
return sortedRecipients
|
|
597
|
+
.filter(item => getAddress(item) !== '')
|
|
598
|
+
.map(item => ({
|
|
599
|
+
address: getAddress(item),
|
|
600
|
+
percentAllocation: parseFloat(getPercentAllocation(item).toString()),
|
|
601
|
+
}));
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
// Helper function to create SplitV2 parameters
|
|
605
|
+
const createSplitV2Params = (
|
|
606
|
+
recipients: SplitV2Recipient[],
|
|
607
|
+
distributorFeePercent: number,
|
|
608
|
+
): {
|
|
609
|
+
recipients: string[];
|
|
610
|
+
allocations: number[];
|
|
611
|
+
totalAllocation: number;
|
|
612
|
+
distributionIncentive: number;
|
|
613
|
+
} => {
|
|
614
|
+
const addresses = recipients.map(r => r.address);
|
|
615
|
+
const allocations = recipients.map(r =>
|
|
616
|
+
Math.floor(r.percentAllocation * 1e4),
|
|
617
|
+
); // Convert to basis points
|
|
618
|
+
const totalAllocation = allocations.reduce(
|
|
619
|
+
(sum, allocation) => sum + allocation,
|
|
620
|
+
0,
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
return {
|
|
624
|
+
recipients: addresses,
|
|
625
|
+
allocations,
|
|
626
|
+
totalAllocation,
|
|
627
|
+
distributionIncentive: distributorFeePercent,
|
|
628
|
+
};
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
export const predictSplitV2Address = async ({
|
|
632
|
+
splitOwnerAddress,
|
|
633
|
+
recipients,
|
|
634
|
+
distributorFeePercent,
|
|
635
|
+
salt,
|
|
636
|
+
signer,
|
|
637
|
+
chainId,
|
|
638
|
+
}: {
|
|
639
|
+
splitOwnerAddress: string;
|
|
640
|
+
recipients: SplitV2Recipient[];
|
|
641
|
+
distributorFeePercent: number;
|
|
642
|
+
salt: `0x${string}`;
|
|
643
|
+
signer: SignerType;
|
|
644
|
+
chainId: number;
|
|
645
|
+
}): Promise<string> => {
|
|
646
|
+
try {
|
|
647
|
+
const chainConfig = getChainConfig(chainId);
|
|
648
|
+
if (!chainConfig?.SPLIT_V2_FACTORY_CONTRACT?.address) {
|
|
649
|
+
throw new Error(`SplitV2 Factory not configured for chain ${chainId}`);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const splitV2FactoryContract = new Contract(
|
|
653
|
+
chainConfig.SPLIT_V2_FACTORY_CONTRACT.address,
|
|
654
|
+
splitV2FactoryAbi,
|
|
655
|
+
signer,
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
const splitParams = createSplitV2Params(recipients, distributorFeePercent);
|
|
659
|
+
|
|
660
|
+
const predictedAddress = await splitV2FactoryContract[
|
|
661
|
+
'predictDeterministicAddress((address[],uint256[],uint256,uint16),address,bytes32)'
|
|
662
|
+
](splitParams, splitOwnerAddress, salt);
|
|
663
|
+
|
|
664
|
+
return predictedAddress;
|
|
665
|
+
} catch (error: any) {
|
|
666
|
+
throw new Error(
|
|
667
|
+
`Failed to predict SplitV2 address: ${error.message ?? 'SplitV2 contract call failed'}`,
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
export const isSplitV2Deployed = async ({
|
|
673
|
+
splitOwnerAddress,
|
|
674
|
+
recipients,
|
|
675
|
+
distributorFeePercent,
|
|
676
|
+
salt,
|
|
677
|
+
signer,
|
|
678
|
+
chainId,
|
|
679
|
+
}: {
|
|
680
|
+
splitOwnerAddress: string;
|
|
681
|
+
recipients: SplitV2Recipient[];
|
|
682
|
+
distributorFeePercent: number;
|
|
683
|
+
salt: `0x${string}`;
|
|
684
|
+
signer: SignerType;
|
|
685
|
+
chainId: number;
|
|
686
|
+
}): Promise<boolean> => {
|
|
687
|
+
try {
|
|
688
|
+
const chainConfig = getChainConfig(chainId);
|
|
689
|
+
if (!chainConfig?.SPLIT_V2_FACTORY_CONTRACT?.address) {
|
|
690
|
+
throw new Error(`SplitV2 Factory not configured for chain ${chainId}`);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const splitV2FactoryContract = new Contract(
|
|
694
|
+
chainConfig.SPLIT_V2_FACTORY_CONTRACT.address,
|
|
695
|
+
splitV2FactoryAbi,
|
|
696
|
+
signer,
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
const splitParams = createSplitV2Params(recipients, distributorFeePercent);
|
|
700
|
+
|
|
701
|
+
const [, exists] = await splitV2FactoryContract.isDeployed(
|
|
702
|
+
splitParams,
|
|
703
|
+
splitOwnerAddress,
|
|
704
|
+
salt,
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
return exists;
|
|
708
|
+
} catch (error: any) {
|
|
709
|
+
// If the check fails, assume it's not deployed
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
export const deployOVMContract = async ({
|
|
715
|
+
OVMOwnerAddress,
|
|
716
|
+
principalRecipient,
|
|
717
|
+
rewardRecipient,
|
|
718
|
+
principalThreshold,
|
|
719
|
+
signer,
|
|
720
|
+
chainId,
|
|
721
|
+
}: {
|
|
722
|
+
OVMOwnerAddress: string;
|
|
723
|
+
principalRecipient: string;
|
|
724
|
+
rewardRecipient: string;
|
|
725
|
+
principalThreshold: number;
|
|
726
|
+
signer: SignerType;
|
|
727
|
+
chainId: number;
|
|
728
|
+
}): Promise<string> => {
|
|
729
|
+
try {
|
|
730
|
+
const chainConfig = getChainConfig(chainId);
|
|
731
|
+
if (!chainConfig?.OVM_FACTORY_CONTRACT?.address) {
|
|
732
|
+
throw new Error(`OVM Factory not configured for chain ${chainId}`);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const ovmFactoryContract = new Contract(
|
|
736
|
+
chainConfig.OVM_FACTORY_CONTRACT.address,
|
|
737
|
+
OVMFactoryContract.abi,
|
|
738
|
+
signer,
|
|
739
|
+
);
|
|
740
|
+
|
|
741
|
+
const tx = await ovmFactoryContract.createObolValidatorManager(
|
|
742
|
+
OVMOwnerAddress,
|
|
743
|
+
principalRecipient,
|
|
744
|
+
rewardRecipient,
|
|
745
|
+
principalThreshold,
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
const receipt = await tx.wait();
|
|
749
|
+
|
|
750
|
+
// Extract OVM address from logs
|
|
751
|
+
const ovmAddressLog = receipt?.logs[1]?.topics[1];
|
|
752
|
+
if (!ovmAddressLog) {
|
|
753
|
+
throw new Error('Failed to extract OVM address from transaction logs');
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const ovmAddress = '0x' + ovmAddressLog.slice(26, 66);
|
|
757
|
+
return ovmAddress;
|
|
758
|
+
} catch (error: any) {
|
|
759
|
+
throw new Error(
|
|
760
|
+
`Failed to deploy OVM contract: ${error.message ?? 'OVM deployment failed'}`,
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
export const deployOVMAndSplitV2 = async ({
|
|
766
|
+
ovmArgs,
|
|
767
|
+
rewardRecipients,
|
|
768
|
+
isRewardsSplitterDeployed,
|
|
769
|
+
distributorFeePercent,
|
|
770
|
+
salt,
|
|
771
|
+
signer,
|
|
772
|
+
chainId,
|
|
773
|
+
principalSplitRecipients,
|
|
774
|
+
isPrincipalSplitDeployed,
|
|
775
|
+
splitOwnerAddress,
|
|
776
|
+
}: {
|
|
777
|
+
ovmArgs: OVMArgs;
|
|
778
|
+
rewardRecipients: SplitV2Recipient[];
|
|
779
|
+
isRewardsSplitterDeployed?: boolean;
|
|
780
|
+
distributorFeePercent: number;
|
|
781
|
+
salt: `0x${string}`;
|
|
782
|
+
signer: SignerType;
|
|
783
|
+
chainId: number;
|
|
784
|
+
principalSplitRecipients?: SplitV2Recipient[];
|
|
785
|
+
isPrincipalSplitDeployed?: boolean;
|
|
786
|
+
splitOwnerAddress: string;
|
|
787
|
+
}): Promise<string> => {
|
|
788
|
+
try {
|
|
789
|
+
const chainConfig = getChainConfig(chainId);
|
|
790
|
+
if (!chainConfig?.OVM_FACTORY_CONTRACT?.address) {
|
|
791
|
+
throw new Error(`OVM Factory not configured for chain ${chainId}`);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (!chainConfig?.SPLIT_V2_FACTORY_CONTRACT?.address) {
|
|
795
|
+
throw new Error(`SplitV2 Factory not configured for chain ${chainId}`);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const executeCalls: Call[] = [];
|
|
799
|
+
|
|
800
|
+
if (rewardRecipients && !isRewardsSplitterDeployed) {
|
|
801
|
+
// Create rewards split call data
|
|
802
|
+
const splitParams = createSplitV2Params(
|
|
803
|
+
rewardRecipients,
|
|
804
|
+
distributorFeePercent,
|
|
805
|
+
);
|
|
806
|
+
const rewardsSplitTxData = encodeCreateSplitV2DeterministicTxData(
|
|
807
|
+
splitParams,
|
|
808
|
+
splitOwnerAddress,
|
|
809
|
+
salt,
|
|
810
|
+
);
|
|
811
|
+
|
|
812
|
+
executeCalls.push({
|
|
813
|
+
target: chainConfig.SPLIT_V2_FACTORY_CONTRACT.address,
|
|
814
|
+
callData: rewardsSplitTxData,
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Create principal split call data if needed (for total split scenario)
|
|
819
|
+
if (principalSplitRecipients && !isPrincipalSplitDeployed) {
|
|
820
|
+
const principalSplitParams = createSplitV2Params(
|
|
821
|
+
principalSplitRecipients,
|
|
822
|
+
distributorFeePercent,
|
|
823
|
+
);
|
|
824
|
+
const principalSplitTxData = encodeCreateSplitV2DeterministicTxData(
|
|
825
|
+
principalSplitParams,
|
|
826
|
+
splitOwnerAddress,
|
|
827
|
+
salt,
|
|
828
|
+
);
|
|
829
|
+
executeCalls.push({
|
|
830
|
+
target: chainConfig.SPLIT_V2_FACTORY_CONTRACT.address,
|
|
831
|
+
callData: principalSplitTxData,
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Create OVM call data
|
|
836
|
+
const ovmTxData = encodeCreateOVMTxData(
|
|
837
|
+
ovmArgs.OVMOwnerAddress,
|
|
838
|
+
ovmArgs.principalRecipient,
|
|
839
|
+
ovmArgs.rewardRecipient,
|
|
840
|
+
ovmArgs.principalThreshold,
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
executeCalls.push({
|
|
844
|
+
target: chainConfig.OVM_FACTORY_CONTRACT.address,
|
|
845
|
+
callData: ovmTxData,
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
// Execute multicall
|
|
849
|
+
const executeMultiCalls = await multicall(executeCalls, signer, chainId);
|
|
850
|
+
|
|
851
|
+
// Extract addresses from events
|
|
852
|
+
let ovmAddress: string | undefined;
|
|
853
|
+
const logsCount = executeMultiCalls?.logs?.length || 0;
|
|
854
|
+
|
|
855
|
+
if (logsCount === 2) {
|
|
856
|
+
ovmAddress = '0x' + executeMultiCalls?.logs[1]?.topics[1]?.slice(26, 66);
|
|
857
|
+
} else if (logsCount === 5) {
|
|
858
|
+
ovmAddress = '0x' + executeMultiCalls?.logs[4]?.topics[1]?.slice(26, 66);
|
|
859
|
+
} else {
|
|
860
|
+
ovmAddress = '0x' + executeMultiCalls?.logs[7]?.topics[1]?.slice(26, 66);
|
|
861
|
+
}
|
|
862
|
+
if (!ovmAddress) {
|
|
863
|
+
throw new Error(
|
|
864
|
+
'Failed to extract contract addresses from multicall events',
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
return ovmAddress;
|
|
869
|
+
} catch (error: any) {
|
|
870
|
+
throw new Error(
|
|
871
|
+
`Failed to deploy OVM and SplitV2: ${error.message ?? 'Deployment failed'}`,
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
|
|
876
|
+
const encodeCreateOVMTxData = (
|
|
877
|
+
OVMOwnerAddress: string,
|
|
878
|
+
principalRecipient: string,
|
|
879
|
+
rewardRecipient: string,
|
|
880
|
+
principalThreshold: number,
|
|
881
|
+
): string => {
|
|
882
|
+
return ovmFactoryContractInterface.encodeFunctionData(
|
|
883
|
+
'createObolValidatorManager',
|
|
884
|
+
[OVMOwnerAddress, principalRecipient, rewardRecipient, principalThreshold],
|
|
885
|
+
);
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
const encodeCreateSplitV2DeterministicTxData = (
|
|
889
|
+
splitParams: {
|
|
890
|
+
recipients: string[];
|
|
891
|
+
allocations: number[];
|
|
892
|
+
totalAllocation: number;
|
|
893
|
+
distributionIncentive: number;
|
|
894
|
+
},
|
|
895
|
+
splitOwnerAddress: string,
|
|
896
|
+
salt: `0x${string}`,
|
|
897
|
+
): string => {
|
|
898
|
+
// creatorAddress can be kept as default https://docs.splits.org/sdk/splits-v2#createsplit
|
|
899
|
+
return splitV2FactoryInterface.encodeFunctionData(
|
|
900
|
+
'createSplitDeterministic',
|
|
901
|
+
[splitParams, splitOwnerAddress, ZeroAddress, salt],
|
|
902
|
+
);
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
// Helper function to safely get chain configuration
|
|
906
|
+
const getChainConfig = (chainId: number): ChainConfig => {
|
|
907
|
+
const config = CHAIN_CONFIGURATION[chainId];
|
|
908
|
+
if (!config) {
|
|
909
|
+
throw new Error(`Chain configuration not found for chain ID ${chainId}`);
|
|
910
|
+
}
|
|
911
|
+
return config;
|
|
912
|
+
};
|