@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.
Files changed (41) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/ajv.js +0 -1
  3. package/dist/cjs/src/constants.js +9 -0
  4. package/dist/cjs/src/eoa/eoa.js +83 -0
  5. package/dist/cjs/src/eoa/eoaHelpers.js +37 -0
  6. package/dist/cjs/src/index.js +5 -1
  7. package/dist/cjs/src/schema.js +24 -2
  8. package/dist/cjs/src/splits/splitHelpers.js +6 -4
  9. package/dist/cjs/src/splits/splits.js +1 -0
  10. package/dist/cjs/test/client/ajv.spec.js +20 -1
  11. package/dist/cjs/test/eoa/eoa.spec.js +87 -0
  12. package/dist/cjs/test/splits/splits.spec.js +5 -0
  13. package/dist/esm/package.json +1 -1
  14. package/dist/esm/src/ajv.js +0 -1
  15. package/dist/esm/src/constants.js +9 -0
  16. package/dist/esm/src/eoa/eoa.js +79 -0
  17. package/dist/esm/src/eoa/eoaHelpers.js +33 -0
  18. package/dist/esm/src/index.js +3 -0
  19. package/dist/esm/src/schema.js +23 -1
  20. package/dist/esm/src/splits/splitHelpers.js +7 -5
  21. package/dist/esm/src/splits/splits.js +1 -0
  22. package/dist/esm/test/client/ajv.spec.js +20 -1
  23. package/dist/esm/test/eoa/eoa.spec.js +85 -0
  24. package/dist/esm/test/splits/splits.spec.js +5 -0
  25. package/dist/types/src/eoa/eoa.d.ts +42 -0
  26. package/dist/types/src/eoa/eoaHelpers.d.ts +16 -0
  27. package/dist/types/src/index.d.ts +7 -0
  28. package/dist/types/src/schema.d.ts +22 -0
  29. package/dist/types/src/splits/splitHelpers.d.ts +2 -1
  30. package/dist/types/src/types.d.ts +18 -2
  31. package/dist/types/test/eoa/eoa.spec.d.ts +1 -0
  32. package/package.json +1 -1
  33. package/src/ajv.ts +0 -1
  34. package/src/constants.ts +9 -0
  35. package/src/eoa/eoa.ts +94 -0
  36. package/src/eoa/eoaHelpers.ts +43 -0
  37. package/src/index.ts +9 -0
  38. package/src/schema.ts +24 -1
  39. package/src/splits/splitHelpers.ts +8 -4
  40. package/src/splits/splits.ts +1 -0
  41. package/src/types.ts +22 -2
@@ -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
  }
@@ -311,6 +311,7 @@ export class ObolSplits {
311
311
  ovmAddress: validatedPayload.ovmAddress,
312
312
  pubKeys: validatedPayload.pubKeys,
313
313
  amounts: validatedPayload.amounts,
314
+ withdrawalFees: validatedPayload.withdrawalFees,
314
315
  signer: this.signer,
315
316
  });
316
317
  });
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obolnetwork/obol-sdk",
3
- "version": "2.9.0",
3
+ "version": "2.10.0",
4
4
  "description": "A package for creating Distributed Validators using the Obol API.",
5
5
  "bugs": {
6
6
  "url": "https://github.com/obolnetwork/obol-sdk/issues"
package/src/ajv.ts CHANGED
@@ -112,7 +112,6 @@ const validateOVMRequestWithdrawalPayload = (
112
112
  _: boolean,
113
113
  data: OVMRequestWithdrawalPayload,
114
114
  ): boolean => {
115
- console.log(data, 'hanaaan dataaaa');
116
115
  if (!data.pubKeys || !data.amounts) {
117
116
  return false;
118
117
  }
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
  /**