@obolnetwork/obol-sdk 2.5.1 → 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.
- 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 +420 -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 +328 -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 +393 -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 +303 -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 +180 -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 +98 -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 +564 -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 +123 -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
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
|
}
|
|
@@ -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
|
+
};
|