@obolnetwork/obol-sdk 2.9.0 → 2.10.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 +1 -1
- package/dist/cjs/src/ajv.js +0 -1
- package/dist/cjs/src/constants.js +9 -0
- package/dist/cjs/src/eoa/eoa.js +83 -0
- package/dist/cjs/src/eoa/eoaHelpers.js +37 -0
- package/dist/cjs/src/index.js +5 -1
- package/dist/cjs/src/schema.js +24 -2
- package/dist/cjs/src/splits/splitHelpers.js +6 -4
- package/dist/cjs/src/splits/splits.js +1 -0
- package/dist/cjs/test/client/ajv.spec.js +20 -1
- package/dist/cjs/test/eoa/eoa.spec.js +87 -0
- package/dist/cjs/test/splits/splits.spec.js +5 -0
- package/dist/esm/package.json +1 -1
- package/dist/esm/src/ajv.js +0 -1
- package/dist/esm/src/constants.js +9 -0
- package/dist/esm/src/eoa/eoa.js +79 -0
- package/dist/esm/src/eoa/eoaHelpers.js +33 -0
- package/dist/esm/src/index.js +3 -0
- package/dist/esm/src/schema.js +23 -1
- package/dist/esm/src/splits/splitHelpers.js +7 -5
- package/dist/esm/src/splits/splits.js +1 -0
- package/dist/esm/test/client/ajv.spec.js +20 -1
- package/dist/esm/test/eoa/eoa.spec.js +85 -0
- package/dist/esm/test/splits/splits.spec.js +5 -0
- package/dist/types/src/eoa/eoa.d.ts +42 -0
- package/dist/types/src/eoa/eoaHelpers.d.ts +16 -0
- package/dist/types/src/index.d.ts +7 -0
- package/dist/types/src/schema.d.ts +22 -0
- package/dist/types/src/splits/splitHelpers.d.ts +2 -1
- package/dist/types/src/types.d.ts +18 -2
- package/dist/types/test/eoa/eoa.spec.d.ts +1 -0
- package/package.json +1 -1
- package/src/ajv.ts +0 -1
- package/src/constants.ts +9 -0
- package/src/eoa/eoa.ts +94 -0
- package/src/eoa/eoaHelpers.ts +43 -0
- package/src/index.ts +9 -0
- package/src/schema.ts +24 -1
- package/src/splits/splitHelpers.ts +8 -4
- package/src/splits/splits.ts +1 -0
- package/src/types.ts +22 -2
package/dist/esm/src/schema.js
CHANGED
|
@@ -209,6 +209,10 @@ export const ovmTotalSplitPayloadSchema = {
|
|
|
209
209
|
export const ovmRequestWithdrawalPayloadSchema = {
|
|
210
210
|
type: 'object',
|
|
211
211
|
properties: {
|
|
212
|
+
withdrawalFees: {
|
|
213
|
+
type: 'string',
|
|
214
|
+
pattern: '^[0-9]+$',
|
|
215
|
+
},
|
|
212
216
|
ovmAddress: {
|
|
213
217
|
type: 'string',
|
|
214
218
|
pattern: '^0x[a-fA-F0-9]{40}$',
|
|
@@ -231,5 +235,23 @@ export const ovmRequestWithdrawalPayloadSchema = {
|
|
|
231
235
|
},
|
|
232
236
|
},
|
|
233
237
|
validateOVMRequestWithdrawalPayload: true,
|
|
234
|
-
required: ['ovmAddress', 'pubKeys', 'amounts'],
|
|
238
|
+
required: ['ovmAddress', 'pubKeys', 'amounts', 'withdrawalFees'],
|
|
239
|
+
};
|
|
240
|
+
export const eoaWithdrawalPayloadSchema = {
|
|
241
|
+
type: 'object',
|
|
242
|
+
properties: {
|
|
243
|
+
pubkey: {
|
|
244
|
+
type: 'string',
|
|
245
|
+
pattern: '^0x[a-fA-F0-9]{96}$', // 48 bytes = 96 hex chars + 0x prefix
|
|
246
|
+
},
|
|
247
|
+
allocation: {
|
|
248
|
+
type: 'number',
|
|
249
|
+
minimum: 0,
|
|
250
|
+
},
|
|
251
|
+
requiredFee: {
|
|
252
|
+
type: 'string',
|
|
253
|
+
pattern: '^[0-9]+$',
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
required: ['pubkey', 'allocation', 'requiredFee'],
|
|
235
257
|
};
|
|
@@ -12,7 +12,7 @@ import { OWRContract, OWRFactoryContract } from '../abi/OWR';
|
|
|
12
12
|
import { OVMFactoryContract, OVMContract } from '../abi/OVMFactory';
|
|
13
13
|
import { splitMainEthereumAbi } from '../abi/SplitMain';
|
|
14
14
|
import { MultiCallContract } from '../abi/Multicall';
|
|
15
|
-
import { CHAIN_CONFIGURATION } from '../constants';
|
|
15
|
+
import { CHAIN_CONFIGURATION, ETHER_TO_GWEI } from '../constants';
|
|
16
16
|
import { splitV2FactoryAbi } from '../abi/splitV2FactoryAbi';
|
|
17
17
|
const splitMainContractInterface = new Interface(splitMainEthereumAbi);
|
|
18
18
|
const owrFactoryContractInterface = new Interface(OWRFactoryContract.abi);
|
|
@@ -397,7 +397,7 @@ export const deployOVMContract = ({ OVMOwnerAddress, principalRecipient, rewardR
|
|
|
397
397
|
throw new Error(`OVM Factory not configured for chain ${chainId}`);
|
|
398
398
|
}
|
|
399
399
|
const ovmFactoryContract = new Contract(chainConfig.OVM_FACTORY_CONTRACT.address, OVMFactoryContract.abi, signer);
|
|
400
|
-
const tx = yield ovmFactoryContract.createObolValidatorManager(OVMOwnerAddress, principalRecipient, rewardRecipient, principalThreshold);
|
|
400
|
+
const tx = yield ovmFactoryContract.createObolValidatorManager(OVMOwnerAddress, principalRecipient, rewardRecipient, principalThreshold * ETHER_TO_GWEI);
|
|
401
401
|
const receipt = yield tx.wait();
|
|
402
402
|
// Extract OVM address from logs
|
|
403
403
|
const ovmAddressLog = (_3 = receipt === null || receipt === void 0 ? void 0 : receipt.logs[1]) === null || _3 === void 0 ? void 0 : _3.topics[1];
|
|
@@ -441,7 +441,7 @@ export const deployOVMAndSplitV2 = ({ ovmArgs, rewardRecipients, isRewardsSplitt
|
|
|
441
441
|
});
|
|
442
442
|
}
|
|
443
443
|
// Create OVM call data
|
|
444
|
-
const ovmTxData = encodeCreateOVMTxData(ovmArgs.OVMOwnerAddress, ovmArgs.principalRecipient, ovmArgs.rewardRecipient, ovmArgs.principalThreshold);
|
|
444
|
+
const ovmTxData = encodeCreateOVMTxData(ovmArgs.OVMOwnerAddress, ovmArgs.principalRecipient, ovmArgs.rewardRecipient, ovmArgs.principalThreshold * ETHER_TO_GWEI);
|
|
445
445
|
executeCalls.push({
|
|
446
446
|
target: chainConfig.OVM_FACTORY_CONTRACT.address,
|
|
447
447
|
callData: ovmTxData,
|
|
@@ -492,13 +492,15 @@ const getChainConfig = (chainId) => {
|
|
|
492
492
|
* @param signer - The signer to use for the transaction
|
|
493
493
|
* @returns Promise that resolves to the transaction hash
|
|
494
494
|
*/
|
|
495
|
-
export const requestWithdrawalFromOVM = ({ ovmAddress, pubKeys, amounts, signer, }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
495
|
+
export const requestWithdrawalFromOVM = ({ ovmAddress, pubKeys, amounts, withdrawalFees, signer, }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
496
496
|
var _15;
|
|
497
497
|
try {
|
|
498
498
|
// Convert string amounts to bigint
|
|
499
499
|
const bigintAmounts = amounts.map(amount => BigInt(amount));
|
|
500
500
|
const ovmContract = new Contract(ovmAddress, OVMContract.abi, signer);
|
|
501
|
-
const tx = yield ovmContract.requestWithdrawal(pubKeys, bigintAmounts
|
|
501
|
+
const tx = yield ovmContract.requestWithdrawal(pubKeys, bigintAmounts, {
|
|
502
|
+
value: BigInt(withdrawalFees),
|
|
503
|
+
});
|
|
502
504
|
const receipt = yield tx.wait();
|
|
503
505
|
return { txHash: receipt.hash };
|
|
504
506
|
}
|
|
@@ -272,6 +272,7 @@ describe('ovmRequestWithdrawalPayloadSchema', () => {
|
|
|
272
272
|
'0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
|
|
273
273
|
],
|
|
274
274
|
amounts: ['32000000000'],
|
|
275
|
+
withdrawalFees: '1',
|
|
275
276
|
};
|
|
276
277
|
it('should throw error when OVM address is missing', () => {
|
|
277
278
|
const payload = {
|
|
@@ -286,14 +287,32 @@ describe('ovmRequestWithdrawalPayloadSchema', () => {
|
|
|
286
287
|
pubKeys: validPayload.pubKeys,
|
|
287
288
|
amounts: validPayload.amounts,
|
|
288
289
|
};
|
|
289
|
-
expect(() => validatePayload(payload, ovmRequestWithdrawalPayloadSchema)).toThrow('Validation failed: /ovmAddress must match pattern');
|
|
290
|
+
expect(() => validatePayload(payload, ovmRequestWithdrawalPayloadSchema)).toThrow('Validation failed: must have required property \'withdrawalFees\', /ovmAddress must match pattern "^0x[a-fA-F0-9]{40}$"');
|
|
290
291
|
});
|
|
291
292
|
it('should throw error when number of public keys does not match number of amounts', () => {
|
|
292
293
|
const payload = {
|
|
293
294
|
ovmAddress: validPayload.ovmAddress,
|
|
294
295
|
pubKeys: validPayload.pubKeys,
|
|
295
296
|
amounts: ['32000000000', '16000000000'], // 2 amounts, 1 pubKey
|
|
297
|
+
withdrawalFees: validPayload.withdrawalFees,
|
|
296
298
|
};
|
|
297
299
|
expect(() => validatePayload(payload, ovmRequestWithdrawalPayloadSchema)).toThrow('Validation failed: must pass "validateOVMRequestWithdrawalPayload" keyword validation');
|
|
298
300
|
});
|
|
301
|
+
it('should throw error when withdrawalFees is missing', () => {
|
|
302
|
+
const payload = {
|
|
303
|
+
ovmAddress: validPayload.ovmAddress,
|
|
304
|
+
pubKeys: validPayload.pubKeys,
|
|
305
|
+
amounts: validPayload.amounts,
|
|
306
|
+
};
|
|
307
|
+
expect(() => validatePayload(payload, ovmRequestWithdrawalPayloadSchema)).toThrow("Validation failed: must have required property 'withdrawalFees'");
|
|
308
|
+
});
|
|
309
|
+
it('should throw error when withdrawalFees is invalid', () => {
|
|
310
|
+
const payload = {
|
|
311
|
+
ovmAddress: validPayload.ovmAddress,
|
|
312
|
+
pubKeys: validPayload.pubKeys,
|
|
313
|
+
amounts: validPayload.amounts,
|
|
314
|
+
withdrawalFees: 'invalid',
|
|
315
|
+
};
|
|
316
|
+
expect(() => validatePayload(payload, ovmRequestWithdrawalPayloadSchema)).toThrow('Validation failed: /withdrawalFees must match pattern "^[0-9]+$"');
|
|
317
|
+
});
|
|
299
318
|
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { EOA } from '../../src/eoa/eoa';
|
|
11
|
+
import { submitEOAWithdrawalRequest } from '../../src/eoa/eoaHelpers';
|
|
12
|
+
// Mock the helper function
|
|
13
|
+
jest.mock('../../src/eoa/eoaHelpers', () => ({
|
|
14
|
+
submitEOAWithdrawalRequest: jest.fn(),
|
|
15
|
+
}));
|
|
16
|
+
describe('EOA', () => {
|
|
17
|
+
let eoa;
|
|
18
|
+
let mockSigner;
|
|
19
|
+
let mockProvider;
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
// Clear all mocks before each test
|
|
22
|
+
jest.clearAllMocks();
|
|
23
|
+
mockSigner = {
|
|
24
|
+
getAddress: jest
|
|
25
|
+
.fn()
|
|
26
|
+
.mockResolvedValue('0x1234567890123456789012345678901234567890'),
|
|
27
|
+
};
|
|
28
|
+
mockProvider = {
|
|
29
|
+
getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }),
|
|
30
|
+
};
|
|
31
|
+
eoa = new EOA(mockSigner, 1, mockProvider);
|
|
32
|
+
});
|
|
33
|
+
describe('requestWithdrawal', () => {
|
|
34
|
+
it('should successfully request withdrawal', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
35
|
+
const mockPayload = {
|
|
36
|
+
pubkey: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
|
|
37
|
+
allocation: 32,
|
|
38
|
+
requiredFee: '1',
|
|
39
|
+
};
|
|
40
|
+
const mockResult = {
|
|
41
|
+
txHash: '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
|
|
42
|
+
};
|
|
43
|
+
submitEOAWithdrawalRequest.mockResolvedValue(mockResult);
|
|
44
|
+
const result = yield eoa.requestWithdrawal(mockPayload);
|
|
45
|
+
expect(submitEOAWithdrawalRequest).toHaveBeenCalledWith({
|
|
46
|
+
pubkey: mockPayload.pubkey,
|
|
47
|
+
allocation: mockPayload.allocation,
|
|
48
|
+
withdrawalAddress: '0x1234567890123456789012345678901234567890',
|
|
49
|
+
withdrawalContractAddress: '0x00000961Ef480Eb55e80D19ad83579A64c007002',
|
|
50
|
+
requiredFee: mockPayload.requiredFee,
|
|
51
|
+
chainId: 1,
|
|
52
|
+
signer: mockSigner,
|
|
53
|
+
provider: mockProvider,
|
|
54
|
+
});
|
|
55
|
+
expect(result).toEqual(mockResult);
|
|
56
|
+
}));
|
|
57
|
+
it('should throw error when signer is not provided', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
58
|
+
const eoaWithoutSigner = new EOA(undefined, 1, mockProvider);
|
|
59
|
+
const mockPayload = {
|
|
60
|
+
pubkey: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
|
|
61
|
+
allocation: 32,
|
|
62
|
+
requiredFee: '1',
|
|
63
|
+
};
|
|
64
|
+
yield expect(eoaWithoutSigner.requestWithdrawal(mockPayload)).rejects.toThrow('Signer is required in requestWithdrawal');
|
|
65
|
+
}));
|
|
66
|
+
it('should throw error when provider is not provided', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
67
|
+
const eoaWithoutProvider = new EOA(mockSigner, 1, null);
|
|
68
|
+
const mockPayload = {
|
|
69
|
+
pubkey: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
|
|
70
|
+
allocation: 32,
|
|
71
|
+
requiredFee: '1',
|
|
72
|
+
};
|
|
73
|
+
yield expect(eoaWithoutProvider.requestWithdrawal(mockPayload)).rejects.toThrow('Provider is required in requestWithdrawal');
|
|
74
|
+
}));
|
|
75
|
+
it('should throw error when EOA withdrawal contract is not configured for chain', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
76
|
+
const eoaUnsupportedChain = new EOA(mockSigner, 999, mockProvider);
|
|
77
|
+
const mockPayload = {
|
|
78
|
+
pubkey: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
|
|
79
|
+
allocation: 32,
|
|
80
|
+
requiredFee: '1',
|
|
81
|
+
};
|
|
82
|
+
yield expect(eoaUnsupportedChain.requestWithdrawal(mockPayload)).rejects.toThrow('EOA withdrawal contract is not configured for chain 999');
|
|
83
|
+
}));
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -244,6 +244,7 @@ describe('ObolSplits', () => {
|
|
|
244
244
|
ovmAddress: mockOVMAddress,
|
|
245
245
|
pubKeys: mockPubKeys,
|
|
246
246
|
amounts: mockAmounts,
|
|
247
|
+
withdrawalFees: '1',
|
|
247
248
|
});
|
|
248
249
|
expect(result).toEqual({
|
|
249
250
|
txHash: '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
|
|
@@ -252,6 +253,7 @@ describe('ObolSplits', () => {
|
|
|
252
253
|
ovmAddress: mockOVMAddress,
|
|
253
254
|
pubKeys: mockPubKeys,
|
|
254
255
|
amounts: mockAmounts,
|
|
256
|
+
withdrawalFees: '1',
|
|
255
257
|
signer: mockSigner,
|
|
256
258
|
});
|
|
257
259
|
}));
|
|
@@ -261,6 +263,7 @@ describe('ObolSplits', () => {
|
|
|
261
263
|
ovmAddress: mockOVMAddress,
|
|
262
264
|
pubKeys: mockPubKeys,
|
|
263
265
|
amounts: mockAmounts,
|
|
266
|
+
withdrawalFees: '1',
|
|
264
267
|
})).rejects.toThrow('Signer is required in requestWithdrawal');
|
|
265
268
|
}));
|
|
266
269
|
// it('should throw error when amount is below minimum (1,000,000 gwei)', async () => {
|
|
@@ -288,6 +291,7 @@ describe('ObolSplits', () => {
|
|
|
288
291
|
ovmAddress: mockOVMAddress,
|
|
289
292
|
pubKeys: multiplePubKeys,
|
|
290
293
|
amounts: multipleAmounts,
|
|
294
|
+
withdrawalFees: '1',
|
|
291
295
|
});
|
|
292
296
|
expect(result).toEqual({
|
|
293
297
|
txHash: '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
|
|
@@ -296,6 +300,7 @@ describe('ObolSplits', () => {
|
|
|
296
300
|
ovmAddress: mockOVMAddress,
|
|
297
301
|
pubKeys: multiplePubKeys,
|
|
298
302
|
amounts: multipleAmounts,
|
|
303
|
+
withdrawalFees: '1',
|
|
299
304
|
signer: mockSigner,
|
|
300
305
|
});
|
|
301
306
|
}));
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type ProviderType, type SignerType, type EOAWithdrawalPayload } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* EOA can be used for managing EOA (Externally Owned Account) operations like withdrawals.
|
|
4
|
+
* @class
|
|
5
|
+
* @internal Access it through Client.eoa.
|
|
6
|
+
* @example
|
|
7
|
+
* const obolClient = new Client(config);
|
|
8
|
+
* await obolClient.eoa.requestWithdrawal(EOAWithdrawalPayload);
|
|
9
|
+
*/
|
|
10
|
+
export declare class EOA {
|
|
11
|
+
private readonly signer;
|
|
12
|
+
readonly chainId: number;
|
|
13
|
+
readonly provider: ProviderType | undefined | null;
|
|
14
|
+
constructor(signer: SignerType | undefined, chainId: number, provider: ProviderType | undefined | null);
|
|
15
|
+
/**
|
|
16
|
+
* Requests withdrawal from an EOA contract.
|
|
17
|
+
*
|
|
18
|
+
* This method allows requesting withdrawal of validator funds.
|
|
19
|
+
* The withdrawal request includes validator public key and corresponding withdrawal amount.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* **⚠️ Important:** If you're storing the private key in an `.env` file, ensure it is securely managed
|
|
23
|
+
* and not pushed to version control.
|
|
24
|
+
*
|
|
25
|
+
* @param {EOAWithdrawalPayload} payload - Data needed to request withdrawal
|
|
26
|
+
* @returns {Promise<{txHash: string}>} Transaction hash of the withdrawal request
|
|
27
|
+
* @throws Will throw an error if the signer is not provided or the request fails
|
|
28
|
+
*
|
|
29
|
+
* An example of how to use requestWithdrawal:
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const result = await client.eoa.requestWithdrawal({
|
|
32
|
+
* pubkey: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
|
|
33
|
+
* allocation: 32, // 32 ETH
|
|
34
|
+
* requiredFee: '1' // in wei
|
|
35
|
+
* });
|
|
36
|
+
* console.log('Withdrawal requested:', result.txHash);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
requestWithdrawal(payload: EOAWithdrawalPayload): Promise<{
|
|
40
|
+
txHash: string | null;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type SignerType, type ProviderType } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Helper function to submit withdrawal request for EOA
|
|
4
|
+
*/
|
|
5
|
+
export declare function submitEOAWithdrawalRequest({ pubkey, allocation, withdrawalAddress, withdrawalContractAddress, requiredFee, chainId, signer, provider, }: {
|
|
6
|
+
pubkey: string;
|
|
7
|
+
allocation: number;
|
|
8
|
+
withdrawalAddress: string;
|
|
9
|
+
withdrawalContractAddress: string;
|
|
10
|
+
requiredFee: string;
|
|
11
|
+
chainId: number;
|
|
12
|
+
signer: SignerType;
|
|
13
|
+
provider: ProviderType;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
txHash: string | null;
|
|
16
|
+
}>;
|
|
@@ -3,6 +3,7 @@ import { type RewardsSplitPayload, type ClusterDefinition, type ClusterLock, typ
|
|
|
3
3
|
import { Incentives } from './incentives/incentives.js';
|
|
4
4
|
import { Exit } from './exits/exit.js';
|
|
5
5
|
import { ObolSplits } from './splits/splits.js';
|
|
6
|
+
import { EOA } from './eoa/eoa.js';
|
|
6
7
|
export * from './types.js';
|
|
7
8
|
export * from './services.js';
|
|
8
9
|
export * from './verification/signature-validator.js';
|
|
@@ -11,6 +12,7 @@ export * from './constants.js';
|
|
|
11
12
|
export { Incentives } from './incentives/incentives.js';
|
|
12
13
|
export { Exit } from './exits/exit.js';
|
|
13
14
|
export { ObolSplits } from './splits/splits.js';
|
|
15
|
+
export { EOA } from './eoa/eoa.js';
|
|
14
16
|
/**
|
|
15
17
|
* Obol sdk Client can be used for creating, managing and activating distributed validators.
|
|
16
18
|
*/
|
|
@@ -34,6 +36,11 @@ export declare class Client extends Base {
|
|
|
34
36
|
* @type {ObolSplits}
|
|
35
37
|
*/
|
|
36
38
|
splits: ObolSplits;
|
|
39
|
+
/**
|
|
40
|
+
* The eoa module, responsible for managing EOA operations.
|
|
41
|
+
* @type {EOA}
|
|
42
|
+
*/
|
|
43
|
+
eoa: EOA;
|
|
37
44
|
/**
|
|
38
45
|
* The blockchain provider, used to interact with the network.
|
|
39
46
|
* It can be null, undefined, or a valid provider instance and defaults to the Signer provider if Signer is passed.
|
|
@@ -324,6 +324,10 @@ export declare const ovmTotalSplitPayloadSchema: {
|
|
|
324
324
|
export declare const ovmRequestWithdrawalPayloadSchema: {
|
|
325
325
|
type: string;
|
|
326
326
|
properties: {
|
|
327
|
+
withdrawalFees: {
|
|
328
|
+
type: string;
|
|
329
|
+
pattern: string;
|
|
330
|
+
};
|
|
327
331
|
ovmAddress: {
|
|
328
332
|
type: string;
|
|
329
333
|
pattern: string;
|
|
@@ -348,3 +352,21 @@ export declare const ovmRequestWithdrawalPayloadSchema: {
|
|
|
348
352
|
validateOVMRequestWithdrawalPayload: boolean;
|
|
349
353
|
required: string[];
|
|
350
354
|
};
|
|
355
|
+
export declare const eoaWithdrawalPayloadSchema: {
|
|
356
|
+
type: string;
|
|
357
|
+
properties: {
|
|
358
|
+
pubkey: {
|
|
359
|
+
type: string;
|
|
360
|
+
pattern: string;
|
|
361
|
+
};
|
|
362
|
+
allocation: {
|
|
363
|
+
type: string;
|
|
364
|
+
minimum: number;
|
|
365
|
+
};
|
|
366
|
+
requiredFee: {
|
|
367
|
+
type: string;
|
|
368
|
+
pattern: string;
|
|
369
|
+
};
|
|
370
|
+
};
|
|
371
|
+
required: string[];
|
|
372
|
+
};
|
|
@@ -107,10 +107,11 @@ export declare const deployOVMAndSplitV2: ({ ovmArgs, rewardRecipients, isReward
|
|
|
107
107
|
* @param signer - The signer to use for the transaction
|
|
108
108
|
* @returns Promise that resolves to the transaction hash
|
|
109
109
|
*/
|
|
110
|
-
export declare const requestWithdrawalFromOVM: ({ ovmAddress, pubKeys, amounts, signer, }: {
|
|
110
|
+
export declare const requestWithdrawalFromOVM: ({ ovmAddress, pubKeys, amounts, withdrawalFees, signer, }: {
|
|
111
111
|
ovmAddress: string;
|
|
112
112
|
pubKeys: string[];
|
|
113
113
|
amounts: string[];
|
|
114
|
+
withdrawalFees: string;
|
|
114
115
|
signer: SignerType;
|
|
115
116
|
}) => Promise<{
|
|
116
117
|
txHash: string;
|
|
@@ -149,7 +149,7 @@ export type OVMBaseSplitPayload = {
|
|
|
149
149
|
OVMOwnerAddress: string;
|
|
150
150
|
/** Owner address for the splitter contracts. */
|
|
151
151
|
splitOwnerAddress?: string;
|
|
152
|
-
/** Principal threshold for OVM contract. */
|
|
152
|
+
/** Principal threshold in ETH for OVM contract. */
|
|
153
153
|
principalThreshold?: number;
|
|
154
154
|
/** Distributor fee percentage (0-10). */
|
|
155
155
|
distributorFeePercent?: number;
|
|
@@ -439,7 +439,7 @@ export type OVMArgs = {
|
|
|
439
439
|
principalRecipient: string;
|
|
440
440
|
/** Rewards recipient of the cluster. */
|
|
441
441
|
rewardRecipient: string;
|
|
442
|
-
/** Principal threshold for OVM contract. */
|
|
442
|
+
/** Principal threshold in ETH for OVM contract. */
|
|
443
443
|
principalThreshold: number;
|
|
444
444
|
};
|
|
445
445
|
export type ChainConfig = {
|
|
@@ -471,11 +471,16 @@ export type ChainConfig = {
|
|
|
471
471
|
address: string;
|
|
472
472
|
bytecode: string;
|
|
473
473
|
};
|
|
474
|
+
EOA_WITHDRAWAL_CONTRACT?: {
|
|
475
|
+
address: string;
|
|
476
|
+
};
|
|
474
477
|
};
|
|
475
478
|
/**
|
|
476
479
|
* Payload for requesting withdrawal from OVM contract
|
|
477
480
|
*/
|
|
478
481
|
export type OVMRequestWithdrawalPayload = {
|
|
482
|
+
/** request withdrawal fees in wei */
|
|
483
|
+
withdrawalFees: string;
|
|
479
484
|
/** OVM contract address */
|
|
480
485
|
ovmAddress: string;
|
|
481
486
|
/** Array of validator public keys in bytes format */
|
|
@@ -483,3 +488,14 @@ export type OVMRequestWithdrawalPayload = {
|
|
|
483
488
|
/** Array of withdrawal amounts in gwei (uint64) as strings */
|
|
484
489
|
amounts: string[];
|
|
485
490
|
};
|
|
491
|
+
/**
|
|
492
|
+
* Payload for requesting withdrawal from EOA contract
|
|
493
|
+
*/
|
|
494
|
+
export type EOAWithdrawalPayload = {
|
|
495
|
+
/** Validator public key in hex format */
|
|
496
|
+
pubkey: string;
|
|
497
|
+
/** Withdrawal amount in ETH */
|
|
498
|
+
allocation: number;
|
|
499
|
+
/** Required fee in wei */
|
|
500
|
+
requiredFee: string;
|
|
501
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
package/src/ajv.ts
CHANGED
package/src/constants.ts
CHANGED
|
@@ -199,6 +199,9 @@ export const CHAIN_CONFIGURATION: Record<number, ChainConfig> = {
|
|
|
199
199
|
address: '0x5cbA88D55Cec83caD5A105Ad40C8c9aF20bE21d1',
|
|
200
200
|
bytecode: MAINNET_SPLIT_V2_FACTORY_BYTECODE,
|
|
201
201
|
},
|
|
202
|
+
EOA_WITHDRAWAL_CONTRACT: {
|
|
203
|
+
address: '0x00000961Ef480Eb55e80D19ad83579A64c007002',
|
|
204
|
+
},
|
|
202
205
|
},
|
|
203
206
|
[FORK_MAPPING['0x01017000']]: {
|
|
204
207
|
SPLITMAIN_CONTRACT: {
|
|
@@ -217,6 +220,9 @@ export const CHAIN_CONFIGURATION: Record<number, ChainConfig> = {
|
|
|
217
220
|
address: '0x43F641fA70e09f0326ac66b4Ef0C416EaEcBC6f5',
|
|
218
221
|
bytecode: '',
|
|
219
222
|
},
|
|
223
|
+
EOA_WITHDRAWAL_CONTRACT: {
|
|
224
|
+
address: '0x00000961Ef480Eb55e80D19ad83579A64c007002',
|
|
225
|
+
},
|
|
220
226
|
},
|
|
221
227
|
[FORK_MAPPING['0x10000910']]: {
|
|
222
228
|
SPLITMAIN_CONTRACT: {
|
|
@@ -248,6 +254,9 @@ export const CHAIN_CONFIGURATION: Record<number, ChainConfig> = {
|
|
|
248
254
|
address: '0x5cbA88D55Cec83caD5A105Ad40C8c9aF20bE21d1',
|
|
249
255
|
bytecode: HOODI_SPLIT_V2_FACTORY_BYTECODE,
|
|
250
256
|
},
|
|
257
|
+
EOA_WITHDRAWAL_CONTRACT: {
|
|
258
|
+
address: '0x00000961Ef480Eb55e80D19ad83579A64c007002',
|
|
259
|
+
},
|
|
251
260
|
},
|
|
252
261
|
};
|
|
253
262
|
|
package/src/eoa/eoa.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { submitEOAWithdrawalRequest } from './eoaHelpers';
|
|
2
|
+
import { validatePayload } from '../ajv';
|
|
3
|
+
import { eoaWithdrawalPayloadSchema } from '../schema';
|
|
4
|
+
import { CHAIN_CONFIGURATION } from '../constants';
|
|
5
|
+
import {
|
|
6
|
+
type ProviderType,
|
|
7
|
+
type SignerType,
|
|
8
|
+
type EOAWithdrawalPayload,
|
|
9
|
+
} from '../types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* EOA can be used for managing EOA (Externally Owned Account) operations like withdrawals.
|
|
13
|
+
* @class
|
|
14
|
+
* @internal Access it through Client.eoa.
|
|
15
|
+
* @example
|
|
16
|
+
* const obolClient = new Client(config);
|
|
17
|
+
* await obolClient.eoa.requestWithdrawal(EOAWithdrawalPayload);
|
|
18
|
+
*/
|
|
19
|
+
export class EOA {
|
|
20
|
+
private readonly signer: SignerType | undefined;
|
|
21
|
+
public readonly chainId: number;
|
|
22
|
+
public readonly provider: ProviderType | undefined | null;
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
signer: SignerType | undefined,
|
|
26
|
+
chainId: number,
|
|
27
|
+
provider: ProviderType | undefined | null,
|
|
28
|
+
) {
|
|
29
|
+
this.signer = signer;
|
|
30
|
+
this.chainId = chainId;
|
|
31
|
+
this.provider = provider;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Requests withdrawal from an EOA contract.
|
|
36
|
+
*
|
|
37
|
+
* This method allows requesting withdrawal of validator funds.
|
|
38
|
+
* The withdrawal request includes validator public key and corresponding withdrawal amount.
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* **⚠️ Important:** If you're storing the private key in an `.env` file, ensure it is securely managed
|
|
42
|
+
* and not pushed to version control.
|
|
43
|
+
*
|
|
44
|
+
* @param {EOAWithdrawalPayload} payload - Data needed to request withdrawal
|
|
45
|
+
* @returns {Promise<{txHash: string}>} Transaction hash of the withdrawal request
|
|
46
|
+
* @throws Will throw an error if the signer is not provided or the request fails
|
|
47
|
+
*
|
|
48
|
+
* An example of how to use requestWithdrawal:
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const result = await client.eoa.requestWithdrawal({
|
|
51
|
+
* pubkey: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
|
|
52
|
+
* allocation: 32, // 32 ETH
|
|
53
|
+
* requiredFee: '1' // in wei
|
|
54
|
+
* });
|
|
55
|
+
* console.log('Withdrawal requested:', result.txHash);
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
async requestWithdrawal(
|
|
59
|
+
payload: EOAWithdrawalPayload,
|
|
60
|
+
): Promise<{ txHash: string | null }> {
|
|
61
|
+
if (!this.signer) {
|
|
62
|
+
throw new Error('Signer is required in requestWithdrawal');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!this.provider) {
|
|
66
|
+
throw new Error('Provider is required in requestWithdrawal');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const chainConfig = CHAIN_CONFIGURATION[this.chainId];
|
|
70
|
+
if (!chainConfig?.EOA_WITHDRAWAL_CONTRACT?.address) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`EOA withdrawal contract is not configured for chain ${this.chainId}`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const validatedPayload = validatePayload<EOAWithdrawalPayload>(
|
|
77
|
+
payload,
|
|
78
|
+
eoaWithdrawalPayloadSchema,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const withdrawalAddress = await this.signer.getAddress();
|
|
82
|
+
|
|
83
|
+
return await submitEOAWithdrawalRequest({
|
|
84
|
+
pubkey: validatedPayload.pubkey,
|
|
85
|
+
allocation: validatedPayload.allocation,
|
|
86
|
+
withdrawalAddress,
|
|
87
|
+
withdrawalContractAddress: chainConfig.EOA_WITHDRAWAL_CONTRACT.address,
|
|
88
|
+
requiredFee: validatedPayload.requiredFee,
|
|
89
|
+
chainId: this.chainId,
|
|
90
|
+
signer: this.signer,
|
|
91
|
+
provider: this.provider,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ETHER_TO_GWEI } from '../constants';
|
|
2
|
+
import { type SignerType, type ProviderType } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Helper function to submit withdrawal request for EOA
|
|
6
|
+
*/
|
|
7
|
+
export async function submitEOAWithdrawalRequest({
|
|
8
|
+
pubkey,
|
|
9
|
+
allocation,
|
|
10
|
+
withdrawalAddress,
|
|
11
|
+
withdrawalContractAddress,
|
|
12
|
+
requiredFee,
|
|
13
|
+
chainId,
|
|
14
|
+
signer,
|
|
15
|
+
provider,
|
|
16
|
+
}: {
|
|
17
|
+
pubkey: string;
|
|
18
|
+
allocation: number;
|
|
19
|
+
withdrawalAddress: string;
|
|
20
|
+
withdrawalContractAddress: string;
|
|
21
|
+
requiredFee: string;
|
|
22
|
+
chainId: number;
|
|
23
|
+
signer: SignerType;
|
|
24
|
+
provider: ProviderType;
|
|
25
|
+
}): Promise<{ txHash: string | null }> {
|
|
26
|
+
if (!withdrawalAddress) throw new Error('No withdrawal address provided');
|
|
27
|
+
if (!allocation) throw new Error('No allocation provided');
|
|
28
|
+
|
|
29
|
+
const amountInGwei = BigInt(Math.floor(Number(allocation) * ETHER_TO_GWEI));
|
|
30
|
+
const data = `0x${pubkey.slice(2)}${amountInGwei.toString(16).padStart(16, '0')}`;
|
|
31
|
+
|
|
32
|
+
const { hash } = await signer.sendTransaction({
|
|
33
|
+
to: withdrawalContractAddress,
|
|
34
|
+
chainId,
|
|
35
|
+
value: BigInt(requiredFee),
|
|
36
|
+
data: data as `0x${string}`,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const txResult = await provider.getTransactionReceipt(hash);
|
|
40
|
+
if (!txResult) return { txHash: null };
|
|
41
|
+
|
|
42
|
+
return { txHash: txResult?.hash };
|
|
43
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -50,6 +50,7 @@ import { isContractAvailable } from './utils.js';
|
|
|
50
50
|
import { Incentives } from './incentives/incentives.js';
|
|
51
51
|
import { Exit } from './exits/exit.js';
|
|
52
52
|
import { ObolSplits } from './splits/splits.js';
|
|
53
|
+
import { EOA } from './eoa/eoa.js';
|
|
53
54
|
export * from './types.js';
|
|
54
55
|
export * from './services.js';
|
|
55
56
|
export * from './verification/signature-validator.js';
|
|
@@ -58,6 +59,7 @@ export * from './constants.js';
|
|
|
58
59
|
export { Incentives } from './incentives/incentives.js';
|
|
59
60
|
export { Exit } from './exits/exit.js';
|
|
60
61
|
export { ObolSplits } from './splits/splits.js';
|
|
62
|
+
export { EOA } from './eoa/eoa.js';
|
|
61
63
|
|
|
62
64
|
/**
|
|
63
65
|
* Obol sdk Client can be used for creating, managing and activating distributed validators.
|
|
@@ -86,6 +88,12 @@ export class Client extends Base {
|
|
|
86
88
|
*/
|
|
87
89
|
public splits: ObolSplits;
|
|
88
90
|
|
|
91
|
+
/**
|
|
92
|
+
* The eoa module, responsible for managing EOA operations.
|
|
93
|
+
* @type {EOA}
|
|
94
|
+
*/
|
|
95
|
+
public eoa: EOA;
|
|
96
|
+
|
|
89
97
|
/**
|
|
90
98
|
* The blockchain provider, used to interact with the network.
|
|
91
99
|
* It can be null, undefined, or a valid provider instance and defaults to the Signer provider if Signer is passed.
|
|
@@ -121,6 +129,7 @@ export class Client extends Base {
|
|
|
121
129
|
);
|
|
122
130
|
this.exit = new Exit(this.chainId, this.provider);
|
|
123
131
|
this.splits = new ObolSplits(this.signer, this.chainId, this.provider);
|
|
132
|
+
this.eoa = new EOA(this.signer, this.chainId, this.provider);
|
|
124
133
|
}
|
|
125
134
|
|
|
126
135
|
/**
|