@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
@@ -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"
@@ -68,7 +68,6 @@ const validateOVMTotalSplitRecipients = (_, data) => {
68
68
  return validateTotalPercentage(splitPercentage);
69
69
  };
70
70
  const validateOVMRequestWithdrawalPayload = (_, data) => {
71
- console.log(data, 'hanaaan dataaaa');
72
71
  if (!data.pubKeys || !data.amounts) {
73
72
  return false;
74
73
  }
@@ -171,6 +171,9 @@ exports.CHAIN_CONFIGURATION = {
171
171
  address: '0x5cbA88D55Cec83caD5A105Ad40C8c9aF20bE21d1',
172
172
  bytecode: bytecodes_1.MAINNET_SPLIT_V2_FACTORY_BYTECODE,
173
173
  },
174
+ EOA_WITHDRAWAL_CONTRACT: {
175
+ address: '0x00000961Ef480Eb55e80D19ad83579A64c007002',
176
+ },
174
177
  },
175
178
  [types_1.FORK_MAPPING['0x01017000']]: {
176
179
  SPLITMAIN_CONTRACT: {
@@ -189,6 +192,9 @@ exports.CHAIN_CONFIGURATION = {
189
192
  address: '0x43F641fA70e09f0326ac66b4Ef0C416EaEcBC6f5',
190
193
  bytecode: '',
191
194
  },
195
+ EOA_WITHDRAWAL_CONTRACT: {
196
+ address: '0x00000961Ef480Eb55e80D19ad83579A64c007002',
197
+ },
192
198
  },
193
199
  [types_1.FORK_MAPPING['0x10000910']]: {
194
200
  SPLITMAIN_CONTRACT: {
@@ -220,6 +226,9 @@ exports.CHAIN_CONFIGURATION = {
220
226
  address: '0x5cbA88D55Cec83caD5A105Ad40C8c9aF20bE21d1',
221
227
  bytecode: bytecodes_1.HOODI_SPLIT_V2_FACTORY_BYTECODE,
222
228
  },
229
+ EOA_WITHDRAWAL_CONTRACT: {
230
+ address: '0x00000961Ef480Eb55e80D19ad83579A64c007002',
231
+ },
223
232
  },
224
233
  };
225
234
  exports.DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT = 1;
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.EOA = void 0;
13
+ const eoaHelpers_1 = require("./eoaHelpers");
14
+ const ajv_1 = require("../ajv");
15
+ const schema_1 = require("../schema");
16
+ const constants_1 = require("../constants");
17
+ /**
18
+ * EOA can be used for managing EOA (Externally Owned Account) operations like withdrawals.
19
+ * @class
20
+ * @internal Access it through Client.eoa.
21
+ * @example
22
+ * const obolClient = new Client(config);
23
+ * await obolClient.eoa.requestWithdrawal(EOAWithdrawalPayload);
24
+ */
25
+ class EOA {
26
+ constructor(signer, chainId, provider) {
27
+ this.signer = signer;
28
+ this.chainId = chainId;
29
+ this.provider = provider;
30
+ }
31
+ /**
32
+ * Requests withdrawal from an EOA contract.
33
+ *
34
+ * This method allows requesting withdrawal of validator funds.
35
+ * The withdrawal request includes validator public key and corresponding withdrawal amount.
36
+ *
37
+ * @remarks
38
+ * **⚠️ Important:** If you're storing the private key in an `.env` file, ensure it is securely managed
39
+ * and not pushed to version control.
40
+ *
41
+ * @param {EOAWithdrawalPayload} payload - Data needed to request withdrawal
42
+ * @returns {Promise<{txHash: string}>} Transaction hash of the withdrawal request
43
+ * @throws Will throw an error if the signer is not provided or the request fails
44
+ *
45
+ * An example of how to use requestWithdrawal:
46
+ * ```typescript
47
+ * const result = await client.eoa.requestWithdrawal({
48
+ * pubkey: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
49
+ * allocation: 32, // 32 ETH
50
+ * requiredFee: '1' // in wei
51
+ * });
52
+ * console.log('Withdrawal requested:', result.txHash);
53
+ * ```
54
+ */
55
+ requestWithdrawal(payload) {
56
+ var _a;
57
+ return __awaiter(this, void 0, void 0, function* () {
58
+ if (!this.signer) {
59
+ throw new Error('Signer is required in requestWithdrawal');
60
+ }
61
+ if (!this.provider) {
62
+ throw new Error('Provider is required in requestWithdrawal');
63
+ }
64
+ const chainConfig = constants_1.CHAIN_CONFIGURATION[this.chainId];
65
+ if (!((_a = chainConfig === null || chainConfig === void 0 ? void 0 : chainConfig.EOA_WITHDRAWAL_CONTRACT) === null || _a === void 0 ? void 0 : _a.address)) {
66
+ throw new Error(`EOA withdrawal contract is not configured for chain ${this.chainId}`);
67
+ }
68
+ const validatedPayload = (0, ajv_1.validatePayload)(payload, schema_1.eoaWithdrawalPayloadSchema);
69
+ const withdrawalAddress = yield this.signer.getAddress();
70
+ return yield (0, eoaHelpers_1.submitEOAWithdrawalRequest)({
71
+ pubkey: validatedPayload.pubkey,
72
+ allocation: validatedPayload.allocation,
73
+ withdrawalAddress,
74
+ withdrawalContractAddress: chainConfig.EOA_WITHDRAWAL_CONTRACT.address,
75
+ requiredFee: validatedPayload.requiredFee,
76
+ chainId: this.chainId,
77
+ signer: this.signer,
78
+ provider: this.provider,
79
+ });
80
+ });
81
+ }
82
+ }
83
+ exports.EOA = EOA;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.submitEOAWithdrawalRequest = void 0;
13
+ const constants_1 = require("../constants");
14
+ /**
15
+ * Helper function to submit withdrawal request for EOA
16
+ */
17
+ function submitEOAWithdrawalRequest({ pubkey, allocation, withdrawalAddress, withdrawalContractAddress, requiredFee, chainId, signer, provider, }) {
18
+ return __awaiter(this, void 0, void 0, function* () {
19
+ if (!withdrawalAddress)
20
+ throw new Error('No withdrawal address provided');
21
+ if (!allocation)
22
+ throw new Error('No allocation provided');
23
+ const amountInGwei = BigInt(Math.floor(Number(allocation) * constants_1.ETHER_TO_GWEI));
24
+ const data = `0x${pubkey.slice(2)}${amountInGwei.toString(16).padStart(16, '0')}`;
25
+ const { hash } = yield signer.sendTransaction({
26
+ to: withdrawalContractAddress,
27
+ chainId,
28
+ value: BigInt(requiredFee),
29
+ data: data,
30
+ });
31
+ const txResult = yield provider.getTransactionReceipt(hash);
32
+ if (!txResult)
33
+ return { txHash: null };
34
+ return { txHash: txResult === null || txResult === void 0 ? void 0 : txResult.hash };
35
+ });
36
+ }
37
+ exports.submitEOAWithdrawalRequest = submitEOAWithdrawalRequest;
@@ -23,7 +23,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
23
23
  });
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.Client = exports.ObolSplits = exports.Exit = exports.Incentives = void 0;
26
+ exports.Client = exports.EOA = exports.ObolSplits = exports.Exit = exports.Incentives = void 0;
27
27
  const uuid_1 = require("uuid");
28
28
  const base_js_1 = require("./base.js");
29
29
  const constants_js_1 = require("./constants.js");
@@ -36,6 +36,7 @@ const utils_js_1 = require("./utils.js");
36
36
  const incentives_js_1 = require("./incentives/incentives.js");
37
37
  const exit_js_1 = require("./exits/exit.js");
38
38
  const splits_js_1 = require("./splits/splits.js");
39
+ const eoa_js_1 = require("./eoa/eoa.js");
39
40
  __exportStar(require("./types.js"), exports);
40
41
  __exportStar(require("./services.js"), exports);
41
42
  __exportStar(require("./verification/signature-validator.js"), exports);
@@ -47,6 +48,8 @@ var exit_js_2 = require("./exits/exit.js");
47
48
  Object.defineProperty(exports, "Exit", { enumerable: true, get: function () { return exit_js_2.Exit; } });
48
49
  var splits_js_2 = require("./splits/splits.js");
49
50
  Object.defineProperty(exports, "ObolSplits", { enumerable: true, get: function () { return splits_js_2.ObolSplits; } });
51
+ var eoa_js_2 = require("./eoa/eoa.js");
52
+ Object.defineProperty(exports, "EOA", { enumerable: true, get: function () { return eoa_js_2.EOA; } });
50
53
  /**
51
54
  * Obol sdk Client can be used for creating, managing and activating distributed validators.
52
55
  */
@@ -70,6 +73,7 @@ class Client extends base_js_1.Base {
70
73
  this.incentives = new incentives_js_1.Incentives(this.signer, this.chainId, this.request.bind(this), this.provider);
71
74
  this.exit = new exit_js_1.Exit(this.chainId, this.provider);
72
75
  this.splits = new splits_js_1.ObolSplits(this.signer, this.chainId, this.provider);
76
+ this.eoa = new eoa_js_1.EOA(this.signer, this.chainId, this.provider);
73
77
  }
74
78
  /**
75
79
  * Accepts Obol terms and conditions to be able to create or update data.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ovmRequestWithdrawalPayloadSchema = exports.ovmTotalSplitPayloadSchema = exports.ovmRewardsSplitPayloadSchema = exports.ovmBaseSplitPayload = exports.rewardsSplitterPayloadSchema = exports.totalSplitterPayloadSchema = exports.definitionSchema = exports.operatorPayloadSchema = void 0;
3
+ exports.eoaWithdrawalPayloadSchema = exports.ovmRequestWithdrawalPayloadSchema = exports.ovmTotalSplitPayloadSchema = exports.ovmRewardsSplitPayloadSchema = exports.ovmBaseSplitPayload = exports.rewardsSplitterPayloadSchema = exports.totalSplitterPayloadSchema = exports.definitionSchema = exports.operatorPayloadSchema = void 0;
4
4
  const ethers_1 = require("ethers");
5
5
  const constants_1 = require("./constants");
6
6
  const ajv_1 = require("./ajv");
@@ -212,6 +212,10 @@ exports.ovmTotalSplitPayloadSchema = {
212
212
  exports.ovmRequestWithdrawalPayloadSchema = {
213
213
  type: 'object',
214
214
  properties: {
215
+ withdrawalFees: {
216
+ type: 'string',
217
+ pattern: '^[0-9]+$',
218
+ },
215
219
  ovmAddress: {
216
220
  type: 'string',
217
221
  pattern: '^0x[a-fA-F0-9]{40}$',
@@ -234,5 +238,23 @@ exports.ovmRequestWithdrawalPayloadSchema = {
234
238
  },
235
239
  },
236
240
  validateOVMRequestWithdrawalPayload: true,
237
- required: ['ovmAddress', 'pubKeys', 'amounts'],
241
+ required: ['ovmAddress', 'pubKeys', 'amounts', 'withdrawalFees'],
242
+ };
243
+ exports.eoaWithdrawalPayloadSchema = {
244
+ type: 'object',
245
+ properties: {
246
+ pubkey: {
247
+ type: 'string',
248
+ pattern: '^0x[a-fA-F0-9]{96}$', // 48 bytes = 96 hex chars + 0x prefix
249
+ },
250
+ allocation: {
251
+ type: 'number',
252
+ minimum: 0,
253
+ },
254
+ requiredFee: {
255
+ type: 'string',
256
+ pattern: '^[0-9]+$',
257
+ },
258
+ },
259
+ required: ['pubkey', 'allocation', 'requiredFee'],
238
260
  };
@@ -410,7 +410,7 @@ const deployOVMContract = ({ OVMOwnerAddress, principalRecipient, rewardRecipien
410
410
  throw new Error(`OVM Factory not configured for chain ${chainId}`);
411
411
  }
412
412
  const ovmFactoryContract = new ethers_1.Contract(chainConfig.OVM_FACTORY_CONTRACT.address, OVMFactory_1.OVMFactoryContract.abi, signer);
413
- const tx = yield ovmFactoryContract.createObolValidatorManager(OVMOwnerAddress, principalRecipient, rewardRecipient, principalThreshold);
413
+ const tx = yield ovmFactoryContract.createObolValidatorManager(OVMOwnerAddress, principalRecipient, rewardRecipient, principalThreshold * constants_1.ETHER_TO_GWEI);
414
414
  const receipt = yield tx.wait();
415
415
  // Extract OVM address from logs
416
416
  const ovmAddressLog = (_3 = receipt === null || receipt === void 0 ? void 0 : receipt.logs[1]) === null || _3 === void 0 ? void 0 : _3.topics[1];
@@ -455,7 +455,7 @@ const deployOVMAndSplitV2 = ({ ovmArgs, rewardRecipients, isRewardsSplitterDeplo
455
455
  });
456
456
  }
457
457
  // Create OVM call data
458
- const ovmTxData = encodeCreateOVMTxData(ovmArgs.OVMOwnerAddress, ovmArgs.principalRecipient, ovmArgs.rewardRecipient, ovmArgs.principalThreshold);
458
+ const ovmTxData = encodeCreateOVMTxData(ovmArgs.OVMOwnerAddress, ovmArgs.principalRecipient, ovmArgs.rewardRecipient, ovmArgs.principalThreshold * constants_1.ETHER_TO_GWEI);
459
459
  executeCalls.push({
460
460
  target: chainConfig.OVM_FACTORY_CONTRACT.address,
461
461
  callData: ovmTxData,
@@ -507,13 +507,15 @@ const getChainConfig = (chainId) => {
507
507
  * @param signer - The signer to use for the transaction
508
508
  * @returns Promise that resolves to the transaction hash
509
509
  */
510
- const requestWithdrawalFromOVM = ({ ovmAddress, pubKeys, amounts, signer, }) => __awaiter(void 0, void 0, void 0, function* () {
510
+ const requestWithdrawalFromOVM = ({ ovmAddress, pubKeys, amounts, withdrawalFees, signer, }) => __awaiter(void 0, void 0, void 0, function* () {
511
511
  var _15;
512
512
  try {
513
513
  // Convert string amounts to bigint
514
514
  const bigintAmounts = amounts.map(amount => BigInt(amount));
515
515
  const ovmContract = new ethers_1.Contract(ovmAddress, OVMFactory_1.OVMContract.abi, signer);
516
- const tx = yield ovmContract.requestWithdrawal(pubKeys, bigintAmounts);
516
+ const tx = yield ovmContract.requestWithdrawal(pubKeys, bigintAmounts, {
517
+ value: BigInt(withdrawalFees),
518
+ });
517
519
  const receipt = yield tx.wait();
518
520
  return { txHash: receipt.hash };
519
521
  }
@@ -314,6 +314,7 @@ class ObolSplits {
314
314
  ovmAddress: validatedPayload.ovmAddress,
315
315
  pubKeys: validatedPayload.pubKeys,
316
316
  amounts: validatedPayload.amounts,
317
+ withdrawalFees: validatedPayload.withdrawalFees,
317
318
  signer: this.signer,
318
319
  });
319
320
  });
@@ -274,6 +274,7 @@ describe('ovmRequestWithdrawalPayloadSchema', () => {
274
274
  '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
275
275
  ],
276
276
  amounts: ['32000000000'],
277
+ withdrawalFees: '1',
277
278
  };
278
279
  it('should throw error when OVM address is missing', () => {
279
280
  const payload = {
@@ -288,14 +289,32 @@ describe('ovmRequestWithdrawalPayloadSchema', () => {
288
289
  pubKeys: validPayload.pubKeys,
289
290
  amounts: validPayload.amounts,
290
291
  };
291
- expect(() => (0, ajv_1.validatePayload)(payload, schema_1.ovmRequestWithdrawalPayloadSchema)).toThrow('Validation failed: /ovmAddress must match pattern');
292
+ expect(() => (0, ajv_1.validatePayload)(payload, schema_1.ovmRequestWithdrawalPayloadSchema)).toThrow('Validation failed: must have required property \'withdrawalFees\', /ovmAddress must match pattern "^0x[a-fA-F0-9]{40}$"');
292
293
  });
293
294
  it('should throw error when number of public keys does not match number of amounts', () => {
294
295
  const payload = {
295
296
  ovmAddress: validPayload.ovmAddress,
296
297
  pubKeys: validPayload.pubKeys,
297
298
  amounts: ['32000000000', '16000000000'], // 2 amounts, 1 pubKey
299
+ withdrawalFees: validPayload.withdrawalFees,
298
300
  };
299
301
  expect(() => (0, ajv_1.validatePayload)(payload, schema_1.ovmRequestWithdrawalPayloadSchema)).toThrow('Validation failed: must pass "validateOVMRequestWithdrawalPayload" keyword validation');
300
302
  });
303
+ it('should throw error when withdrawalFees is missing', () => {
304
+ const payload = {
305
+ ovmAddress: validPayload.ovmAddress,
306
+ pubKeys: validPayload.pubKeys,
307
+ amounts: validPayload.amounts,
308
+ };
309
+ expect(() => (0, ajv_1.validatePayload)(payload, schema_1.ovmRequestWithdrawalPayloadSchema)).toThrow("Validation failed: must have required property 'withdrawalFees'");
310
+ });
311
+ it('should throw error when withdrawalFees is invalid', () => {
312
+ const payload = {
313
+ ovmAddress: validPayload.ovmAddress,
314
+ pubKeys: validPayload.pubKeys,
315
+ amounts: validPayload.amounts,
316
+ withdrawalFees: 'invalid',
317
+ };
318
+ expect(() => (0, ajv_1.validatePayload)(payload, schema_1.ovmRequestWithdrawalPayloadSchema)).toThrow('Validation failed: /withdrawalFees must match pattern "^[0-9]+$"');
319
+ });
301
320
  });
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const eoa_1 = require("../../src/eoa/eoa");
13
+ const eoaHelpers_1 = require("../../src/eoa/eoaHelpers");
14
+ // Mock the helper function
15
+ jest.mock('../../src/eoa/eoaHelpers', () => ({
16
+ submitEOAWithdrawalRequest: jest.fn(),
17
+ }));
18
+ describe('EOA', () => {
19
+ let eoa;
20
+ let mockSigner;
21
+ let mockProvider;
22
+ beforeEach(() => {
23
+ // Clear all mocks before each test
24
+ jest.clearAllMocks();
25
+ mockSigner = {
26
+ getAddress: jest
27
+ .fn()
28
+ .mockResolvedValue('0x1234567890123456789012345678901234567890'),
29
+ };
30
+ mockProvider = {
31
+ getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }),
32
+ };
33
+ eoa = new eoa_1.EOA(mockSigner, 1, mockProvider);
34
+ });
35
+ describe('requestWithdrawal', () => {
36
+ it('should successfully request withdrawal', () => __awaiter(void 0, void 0, void 0, function* () {
37
+ const mockPayload = {
38
+ pubkey: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
39
+ allocation: 32,
40
+ requiredFee: '1',
41
+ };
42
+ const mockResult = {
43
+ txHash: '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
44
+ };
45
+ eoaHelpers_1.submitEOAWithdrawalRequest.mockResolvedValue(mockResult);
46
+ const result = yield eoa.requestWithdrawal(mockPayload);
47
+ expect(eoaHelpers_1.submitEOAWithdrawalRequest).toHaveBeenCalledWith({
48
+ pubkey: mockPayload.pubkey,
49
+ allocation: mockPayload.allocation,
50
+ withdrawalAddress: '0x1234567890123456789012345678901234567890',
51
+ withdrawalContractAddress: '0x00000961Ef480Eb55e80D19ad83579A64c007002',
52
+ requiredFee: mockPayload.requiredFee,
53
+ chainId: 1,
54
+ signer: mockSigner,
55
+ provider: mockProvider,
56
+ });
57
+ expect(result).toEqual(mockResult);
58
+ }));
59
+ it('should throw error when signer is not provided', () => __awaiter(void 0, void 0, void 0, function* () {
60
+ const eoaWithoutSigner = new eoa_1.EOA(undefined, 1, mockProvider);
61
+ const mockPayload = {
62
+ pubkey: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
63
+ allocation: 32,
64
+ requiredFee: '1',
65
+ };
66
+ yield expect(eoaWithoutSigner.requestWithdrawal(mockPayload)).rejects.toThrow('Signer is required in requestWithdrawal');
67
+ }));
68
+ it('should throw error when provider is not provided', () => __awaiter(void 0, void 0, void 0, function* () {
69
+ const eoaWithoutProvider = new eoa_1.EOA(mockSigner, 1, null);
70
+ const mockPayload = {
71
+ pubkey: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
72
+ allocation: 32,
73
+ requiredFee: '1',
74
+ };
75
+ yield expect(eoaWithoutProvider.requestWithdrawal(mockPayload)).rejects.toThrow('Provider is required in requestWithdrawal');
76
+ }));
77
+ it('should throw error when EOA withdrawal contract is not configured for chain', () => __awaiter(void 0, void 0, void 0, function* () {
78
+ const eoaUnsupportedChain = new eoa_1.EOA(mockSigner, 999, mockProvider);
79
+ const mockPayload = {
80
+ pubkey: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
81
+ allocation: 32,
82
+ requiredFee: '1',
83
+ };
84
+ yield expect(eoaUnsupportedChain.requestWithdrawal(mockPayload)).rejects.toThrow('EOA withdrawal contract is not configured for chain 999');
85
+ }));
86
+ });
87
+ });
@@ -246,6 +246,7 @@ describe('ObolSplits', () => {
246
246
  ovmAddress: mockOVMAddress,
247
247
  pubKeys: mockPubKeys,
248
248
  amounts: mockAmounts,
249
+ withdrawalFees: '1',
249
250
  });
250
251
  expect(result).toEqual({
251
252
  txHash: '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
@@ -254,6 +255,7 @@ describe('ObolSplits', () => {
254
255
  ovmAddress: mockOVMAddress,
255
256
  pubKeys: mockPubKeys,
256
257
  amounts: mockAmounts,
258
+ withdrawalFees: '1',
257
259
  signer: mockSigner,
258
260
  });
259
261
  }));
@@ -263,6 +265,7 @@ describe('ObolSplits', () => {
263
265
  ovmAddress: mockOVMAddress,
264
266
  pubKeys: mockPubKeys,
265
267
  amounts: mockAmounts,
268
+ withdrawalFees: '1',
266
269
  })).rejects.toThrow('Signer is required in requestWithdrawal');
267
270
  }));
268
271
  // it('should throw error when amount is below minimum (1,000,000 gwei)', async () => {
@@ -290,6 +293,7 @@ describe('ObolSplits', () => {
290
293
  ovmAddress: mockOVMAddress,
291
294
  pubKeys: multiplePubKeys,
292
295
  amounts: multipleAmounts,
296
+ withdrawalFees: '1',
293
297
  });
294
298
  expect(result).toEqual({
295
299
  txHash: '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
@@ -298,6 +302,7 @@ describe('ObolSplits', () => {
298
302
  ovmAddress: mockOVMAddress,
299
303
  pubKeys: multiplePubKeys,
300
304
  amounts: multipleAmounts,
305
+ withdrawalFees: '1',
301
306
  signer: mockSigner,
302
307
  });
303
308
  }));
@@ -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"
@@ -62,7 +62,6 @@ const validateOVMTotalSplitRecipients = (_, data) => {
62
62
  return validateTotalPercentage(splitPercentage);
63
63
  };
64
64
  const validateOVMRequestWithdrawalPayload = (_, data) => {
65
- console.log(data, 'hanaaan dataaaa');
66
65
  if (!data.pubKeys || !data.amounts) {
67
66
  return false;
68
67
  }
@@ -137,6 +137,9 @@ export const CHAIN_CONFIGURATION = {
137
137
  address: '0x5cbA88D55Cec83caD5A105Ad40C8c9aF20bE21d1',
138
138
  bytecode: MAINNET_SPLIT_V2_FACTORY_BYTECODE,
139
139
  },
140
+ EOA_WITHDRAWAL_CONTRACT: {
141
+ address: '0x00000961Ef480Eb55e80D19ad83579A64c007002',
142
+ },
140
143
  },
141
144
  [FORK_MAPPING['0x01017000']]: {
142
145
  SPLITMAIN_CONTRACT: {
@@ -155,6 +158,9 @@ export const CHAIN_CONFIGURATION = {
155
158
  address: '0x43F641fA70e09f0326ac66b4Ef0C416EaEcBC6f5',
156
159
  bytecode: '',
157
160
  },
161
+ EOA_WITHDRAWAL_CONTRACT: {
162
+ address: '0x00000961Ef480Eb55e80D19ad83579A64c007002',
163
+ },
158
164
  },
159
165
  [FORK_MAPPING['0x10000910']]: {
160
166
  SPLITMAIN_CONTRACT: {
@@ -186,6 +192,9 @@ export const CHAIN_CONFIGURATION = {
186
192
  address: '0x5cbA88D55Cec83caD5A105Ad40C8c9aF20bE21d1',
187
193
  bytecode: HOODI_SPLIT_V2_FACTORY_BYTECODE,
188
194
  },
195
+ EOA_WITHDRAWAL_CONTRACT: {
196
+ address: '0x00000961Ef480Eb55e80D19ad83579A64c007002',
197
+ },
189
198
  },
190
199
  };
191
200
  export const DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT = 1;
@@ -0,0 +1,79 @@
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 { submitEOAWithdrawalRequest } from './eoaHelpers';
11
+ import { validatePayload } from '../ajv';
12
+ import { eoaWithdrawalPayloadSchema } from '../schema';
13
+ import { CHAIN_CONFIGURATION } from '../constants';
14
+ /**
15
+ * EOA can be used for managing EOA (Externally Owned Account) operations like withdrawals.
16
+ * @class
17
+ * @internal Access it through Client.eoa.
18
+ * @example
19
+ * const obolClient = new Client(config);
20
+ * await obolClient.eoa.requestWithdrawal(EOAWithdrawalPayload);
21
+ */
22
+ export class EOA {
23
+ constructor(signer, chainId, provider) {
24
+ this.signer = signer;
25
+ this.chainId = chainId;
26
+ this.provider = provider;
27
+ }
28
+ /**
29
+ * Requests withdrawal from an EOA contract.
30
+ *
31
+ * This method allows requesting withdrawal of validator funds.
32
+ * The withdrawal request includes validator public key and corresponding withdrawal amount.
33
+ *
34
+ * @remarks
35
+ * **⚠️ Important:** If you're storing the private key in an `.env` file, ensure it is securely managed
36
+ * and not pushed to version control.
37
+ *
38
+ * @param {EOAWithdrawalPayload} payload - Data needed to request withdrawal
39
+ * @returns {Promise<{txHash: string}>} Transaction hash of the withdrawal request
40
+ * @throws Will throw an error if the signer is not provided or the request fails
41
+ *
42
+ * An example of how to use requestWithdrawal:
43
+ * ```typescript
44
+ * const result = await client.eoa.requestWithdrawal({
45
+ * pubkey: '0x123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456',
46
+ * allocation: 32, // 32 ETH
47
+ * requiredFee: '1' // in wei
48
+ * });
49
+ * console.log('Withdrawal requested:', result.txHash);
50
+ * ```
51
+ */
52
+ requestWithdrawal(payload) {
53
+ var _a;
54
+ return __awaiter(this, void 0, void 0, function* () {
55
+ if (!this.signer) {
56
+ throw new Error('Signer is required in requestWithdrawal');
57
+ }
58
+ if (!this.provider) {
59
+ throw new Error('Provider is required in requestWithdrawal');
60
+ }
61
+ const chainConfig = CHAIN_CONFIGURATION[this.chainId];
62
+ if (!((_a = chainConfig === null || chainConfig === void 0 ? void 0 : chainConfig.EOA_WITHDRAWAL_CONTRACT) === null || _a === void 0 ? void 0 : _a.address)) {
63
+ throw new Error(`EOA withdrawal contract is not configured for chain ${this.chainId}`);
64
+ }
65
+ const validatedPayload = validatePayload(payload, eoaWithdrawalPayloadSchema);
66
+ const withdrawalAddress = yield this.signer.getAddress();
67
+ return yield submitEOAWithdrawalRequest({
68
+ pubkey: validatedPayload.pubkey,
69
+ allocation: validatedPayload.allocation,
70
+ withdrawalAddress,
71
+ withdrawalContractAddress: chainConfig.EOA_WITHDRAWAL_CONTRACT.address,
72
+ requiredFee: validatedPayload.requiredFee,
73
+ chainId: this.chainId,
74
+ signer: this.signer,
75
+ provider: this.provider,
76
+ });
77
+ });
78
+ }
79
+ }
@@ -0,0 +1,33 @@
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 { ETHER_TO_GWEI } from '../constants';
11
+ /**
12
+ * Helper function to submit withdrawal request for EOA
13
+ */
14
+ export function submitEOAWithdrawalRequest({ pubkey, allocation, withdrawalAddress, withdrawalContractAddress, requiredFee, chainId, signer, provider, }) {
15
+ return __awaiter(this, void 0, void 0, function* () {
16
+ if (!withdrawalAddress)
17
+ throw new Error('No withdrawal address provided');
18
+ if (!allocation)
19
+ throw new Error('No allocation provided');
20
+ const amountInGwei = BigInt(Math.floor(Number(allocation) * ETHER_TO_GWEI));
21
+ const data = `0x${pubkey.slice(2)}${amountInGwei.toString(16).padStart(16, '0')}`;
22
+ const { hash } = yield signer.sendTransaction({
23
+ to: withdrawalContractAddress,
24
+ chainId,
25
+ value: BigInt(requiredFee),
26
+ data: data,
27
+ });
28
+ const txResult = yield provider.getTransactionReceipt(hash);
29
+ if (!txResult)
30
+ return { txHash: null };
31
+ return { txHash: txResult === null || txResult === void 0 ? void 0 : txResult.hash };
32
+ });
33
+ }
@@ -19,6 +19,7 @@ import { isContractAvailable } from './utils.js';
19
19
  import { Incentives } from './incentives/incentives.js';
20
20
  import { Exit } from './exits/exit.js';
21
21
  import { ObolSplits } from './splits/splits.js';
22
+ import { EOA } from './eoa/eoa.js';
22
23
  export * from './types.js';
23
24
  export * from './services.js';
24
25
  export * from './verification/signature-validator.js';
@@ -27,6 +28,7 @@ export * from './constants.js';
27
28
  export { Incentives } from './incentives/incentives.js';
28
29
  export { Exit } from './exits/exit.js';
29
30
  export { ObolSplits } from './splits/splits.js';
31
+ export { EOA } from './eoa/eoa.js';
30
32
  /**
31
33
  * Obol sdk Client can be used for creating, managing and activating distributed validators.
32
34
  */
@@ -50,6 +52,7 @@ export class Client extends Base {
50
52
  this.incentives = new Incentives(this.signer, this.chainId, this.request.bind(this), this.provider);
51
53
  this.exit = new Exit(this.chainId, this.provider);
52
54
  this.splits = new ObolSplits(this.signer, this.chainId, this.provider);
55
+ this.eoa = new EOA(this.signer, this.chainId, this.provider);
53
56
  }
54
57
  /**
55
58
  * Accepts Obol terms and conditions to be able to create or update data.