@obolnetwork/obol-sdk 2.2.3 → 2.3.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.
@@ -7,9 +7,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
+ var _a, _b;
10
11
  import { ethers, JsonRpcProvider } from 'ethers';
11
12
  import { Client, validateClusterLock } from '../src/index';
12
- import { clusterConfigV1X7, clusterConfigV1X8, clusterLockV1X10, clusterLockV1X6, clusterLockV1X7, clusterLockV1X8, clusterLockWithSafe, nullDepositAmountsClusterLockV1X8, } from './fixtures.js';
13
+ import { clusterConfigV1X7, clusterConfigV1X8, clusterLockV1X10, clusterLockV1X6, clusterLockV1X7, clusterLockV1X8, clusterLockWithCompoundingWithdrawals, clusterLockWithSafe, nullDepositAmountsClusterLockV1X8, } from './fixtures.js';
13
14
  import { SDK_VERSION } from '../src/constants';
14
15
  import { Base } from '../src/base';
15
16
  import { validatePayload } from '../src/ajv';
@@ -19,15 +20,14 @@ import { hashTermsAndConditions } from '../src/verification/termsAndConditions';
19
20
  import * as utils from '../src/utils';
20
21
  import * as splitsHelpers from '../src/splitHelpers';
21
22
  jest.setTimeout(20000);
23
+ const mnemonic = (_b = (_a = ethers.Wallet.createRandom().mnemonic) === null || _a === void 0 ? void 0 : _a.phrase) !== null && _b !== void 0 ? _b : '';
24
+ const privateKey = ethers.Wallet.fromPhrase(mnemonic).privateKey;
25
+ const provider = new JsonRpcProvider('https://ethereum-holesky.publicnode.com');
26
+ const wallet = new ethers.Wallet(privateKey, provider);
27
+ const mockSigner = wallet.connect(provider);
22
28
  /* eslint no-new: 0 */
23
29
  describe('Cluster Client', () => {
24
- var _a, _b;
25
30
  const mockConfigHash = '0x1f6c94e6c070393a68c1aa6073a21cb1fd57f0e14d2a475a2958990ab728c2fd';
26
- const mnemonic = (_b = (_a = ethers.Wallet.createRandom().mnemonic) === null || _a === void 0 ? void 0 : _a.phrase) !== null && _b !== void 0 ? _b : '';
27
- const privateKey = ethers.Wallet.fromPhrase(mnemonic).privateKey;
28
- const provider = new JsonRpcProvider('https://ethereum-holesky.publicnode.com');
29
- const wallet = new ethers.Wallet(privateKey, provider);
30
- const mockSigner = wallet.connect(provider);
31
31
  const clientInstance = new Client({ baseUrl: 'https://obol-api-dev.gcp.obol.tech', chainId: 17000 }, mockSigner);
32
32
  test('createTermsAndConditions should return "successful authorization"', () => __awaiter(void 0, void 0, void 0, function* () {
33
33
  clientInstance['request'] = jest
@@ -92,12 +92,12 @@ describe('Cluster Client', () => {
92
92
  }
93
93
  }));
94
94
  test('getClusterdefinition should return cluster definition if config hash exist', () => __awaiter(void 0, void 0, void 0, function* () {
95
- var _c;
95
+ var _a;
96
96
  clientInstance['request'] = jest
97
97
  .fn()
98
98
  .mockReturnValue(Promise.resolve(clusterLockV1X8.cluster_definition));
99
99
  const clusterDefinition = yield clientInstance.getClusterDefinition(clusterLockV1X8.cluster_definition.config_hash);
100
- expect((_c = clusterDefinition.deposit_amounts) === null || _c === void 0 ? void 0 : _c.length).toEqual(clusterLockV1X8.cluster_definition.deposit_amounts.length);
100
+ expect((_a = clusterDefinition.deposit_amounts) === null || _a === void 0 ? void 0 : _a.length).toEqual(clusterLockV1X8.cluster_definition.deposit_amounts.length);
101
101
  expect(clusterDefinition.config_hash).toEqual(clusterLockV1X8.cluster_definition.config_hash);
102
102
  }));
103
103
  test('getClusterLock should return lockFile if exist', () => __awaiter(void 0, void 0, void 0, function* () {
@@ -186,6 +186,10 @@ describe('Cluster Client without a signer', () => {
186
186
  clusterLock: clusterLockWithSafe,
187
187
  },
188
188
  { version: 'v1.10.0', clusterLock: clusterLockV1X10 },
189
+ {
190
+ version: 'v1.10.0 with compunding withdrawals',
191
+ clusterLock: clusterLockWithCompoundingWithdrawals,
192
+ },
189
193
  ])("$version: 'should return true on verified cluster lock'", ({ clusterLock }) => __awaiter(void 0, void 0, void 0, function* () {
190
194
  const isValidLock = yield validateClusterLock(clusterLock);
191
195
  expect(isValidLock).toEqual(true);
@@ -204,9 +208,8 @@ describe('Cluster Client without a signer', () => {
204
208
  }));
205
209
  });
206
210
  describe('createObolRewardsSplit', () => {
207
- let clientInstance, clientInstanceWithourSigner, mockSplitRecipients, mockPrincipalRecipient, mockEtherAmount, mockSigner;
211
+ let clientInstance, clientInstanceWithourSigner, mockSplitRecipients, mockPrincipalRecipient, mockEtherAmount;
208
212
  beforeAll(() => {
209
- var _a, _b;
210
213
  jest
211
214
  .spyOn(utils, 'isContractAvailable')
212
215
  .mockImplementation(() => __awaiter(void 0, void 0, void 0, function* () { return yield Promise.resolve(true); }));
@@ -219,11 +222,6 @@ describe('createObolRewardsSplit', () => {
219
222
  fee_recipient_address: '0xFeeRecipientAddress',
220
223
  });
221
224
  }));
222
- const mnemonic = (_b = (_a = ethers.Wallet.createRandom().mnemonic) === null || _a === void 0 ? void 0 : _a.phrase) !== null && _b !== void 0 ? _b : '';
223
- const privateKey = ethers.Wallet.fromPhrase(mnemonic).privateKey;
224
- const provider = new JsonRpcProvider('https://ethereum-holesky.publicnode.com');
225
- const wallet = new ethers.Wallet(privateKey, provider);
226
- mockSigner = wallet.connect(provider);
227
225
  clientInstance = new Client({ baseUrl: 'https://obol-api-dev.gcp.obol.tech', chainId: 17000 }, mockSigner);
228
226
  clientInstanceWithourSigner = new Client({
229
227
  baseUrl: 'https://obol-api-dev.gcp.obol.tech',
@@ -301,9 +299,8 @@ describe('createObolRewardsSplit', () => {
301
299
  }));
302
300
  });
303
301
  describe('createObolTotalSplit', () => {
304
- let clientInstanceWithourSigner, mockSplitRecipients, mockSigner, clientInstance;
302
+ let clientInstanceWithourSigner, mockSplitRecipients, clientInstance;
305
303
  beforeAll(() => {
306
- var _a, _b;
307
304
  jest
308
305
  .spyOn(utils, 'isContractAvailable')
309
306
  .mockImplementation(() => __awaiter(void 0, void 0, void 0, function* () { return yield Promise.resolve(true); }));
@@ -313,11 +310,6 @@ describe('createObolTotalSplit', () => {
313
310
  jest
314
311
  .spyOn(splitsHelpers, 'deploySplitterContract')
315
312
  .mockImplementation(() => __awaiter(void 0, void 0, void 0, function* () { return yield Promise.resolve('0xSplitterAddress'); }));
316
- const mnemonic = (_b = (_a = ethers.Wallet.createRandom().mnemonic) === null || _a === void 0 ? void 0 : _a.phrase) !== null && _b !== void 0 ? _b : '';
317
- const privateKey = ethers.Wallet.fromPhrase(mnemonic).privateKey;
318
- const provider = new JsonRpcProvider('https://ethereum-holesky.publicnode.com');
319
- const wallet = new ethers.Wallet(privateKey, provider);
320
- mockSigner = wallet.connect(provider);
321
313
  clientInstance = new Client({ baseUrl: 'https://obol-api-dev.gcp.obol.tech', chainId: 17000 }, mockSigner);
322
314
  clientInstanceWithourSigner = new Client({
323
315
  baseUrl: 'https://obol-api-dev.gcp.obol.tech',
@@ -394,3 +386,47 @@ describe('createObolTotalSplit', () => {
394
386
  });
395
387
  }));
396
388
  });
389
+ describe('getIncentivesByAddress', () => {
390
+ let clientInstance;
391
+ beforeAll(() => {
392
+ jest
393
+ .spyOn(utils, 'isContractAvailable')
394
+ .mockImplementation(() => __awaiter(void 0, void 0, void 0, function* () { return yield Promise.resolve(true); }));
395
+ jest
396
+ .spyOn(splitsHelpers, 'predictSplitterAddress')
397
+ .mockImplementation(() => __awaiter(void 0, void 0, void 0, function* () { return yield Promise.resolve('0xPredictedAddress'); }));
398
+ jest
399
+ .spyOn(splitsHelpers, 'deploySplitterContract')
400
+ .mockImplementation(() => __awaiter(void 0, void 0, void 0, function* () { return yield Promise.resolve('0xSplitterAddress'); }));
401
+ clientInstance = new Client({ baseUrl: 'https://obol-api-dev.gcp.obol.tech', chainId: 17000 }, mockSigner);
402
+ });
403
+ test('should return incentives for a valid address', () => __awaiter(void 0, void 0, void 0, function* () {
404
+ const mockAddress = '0x1234567890abcdef1234567890abcdef12345678';
405
+ const mockIncentives = {
406
+ operator_address: '0x8c00157cae72c4ed6a1f8bfb60205601f0252e26',
407
+ amount: '100',
408
+ index: 1,
409
+ merkle_proof: ['hash1', 'hash2'],
410
+ contract_address: '0xContract',
411
+ };
412
+ clientInstance['request'] = jest
413
+ .fn()
414
+ .mockReturnValue(Promise.resolve(mockIncentives));
415
+ const incentives = yield clientInstance.getIncentivesByAddress(mockAddress);
416
+ expect(incentives).toEqual(mockIncentives);
417
+ }));
418
+ test('should throw an error if address is not found', () => __awaiter(void 0, void 0, void 0, function* () {
419
+ const invalidAddress = '0x0000000000000000000000000000000000000000';
420
+ clientInstance['request'] = jest
421
+ .fn()
422
+ .mockRejectedValue(new Error('Not found'));
423
+ yield expect(clientInstance.getIncentivesByAddress(invalidAddress)).rejects.toThrow('Not found');
424
+ }));
425
+ test('should throw an error if request fails', () => __awaiter(void 0, void 0, void 0, function* () {
426
+ const mockAddress = '0x1234567890abcdef1234567890abcdef12345678';
427
+ clientInstance['request'] = jest
428
+ .fn()
429
+ .mockRejectedValue(new Error('Network error'));
430
+ yield expect(clientInstance.getIncentivesByAddress(mockAddress)).rejects.toThrow('Network error');
431
+ }));
432
+ });
@@ -1,6 +1,6 @@
1
1
  import { type Signer } from 'ethers';
2
2
  import { Base } from './base.js';
3
- import { type RewardsSplitPayload, type ClusterDefinition, type ClusterLock, type ClusterPayload, type OperatorPayload, type TotalSplitPayload, type ClusterValidator, type ETH_ADDRESS, type OWRTranches } from './types.js';
3
+ import { type RewardsSplitPayload, type ClusterDefinition, type ClusterLock, type ClusterPayload, type OperatorPayload, type TotalSplitPayload, type ClusterValidator, type ETH_ADDRESS, type OWRTranches, type Incentives } from './types.js';
4
4
  export * from './types.js';
5
5
  export * from './services.js';
6
6
  export * from './verification/signature-validator.js';
@@ -112,4 +112,10 @@ export declare class Client extends Base {
112
112
  * [getObolClusterLock](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L89)
113
113
  */
114
114
  getClusterLock(configHash: string): Promise<ClusterLock>;
115
+ /**
116
+ * @param address - Operator address
117
+ * @returns {Promise<Incentives>} The matched incentives from DB
118
+ * @throws On not found if address not found.
119
+ */
120
+ getIncentivesByAddress(address: string): Promise<Incentives>;
115
121
  }
@@ -97,6 +97,8 @@ export interface ClusterDefinition extends ClusterPayload {
97
97
  consensus_protocol?: string;
98
98
  /** The target gas limit where default is 30M. */
99
99
  target_gas_limit?: number;
100
+ /** A withdrawal mechanism with 0x02 withdrawal credentials. */
101
+ compounding?: boolean;
100
102
  }
101
103
  /**
102
104
  * Split Recipient Keys
@@ -209,6 +211,21 @@ export type ClusterLock = {
209
211
  /** Node Signature for the lock hash by the node secp256k1 key. */
210
212
  node_signatures?: string[];
211
213
  };
214
+ /**
215
+ * Incentives
216
+ */
217
+ export type Incentives = {
218
+ /** Operator Address. */
219
+ operator_address: string;
220
+ /** The amount the recipient is entitled to. */
221
+ amount: string;
222
+ /** The recipient’s index in the Merkle tree. */
223
+ index: number;
224
+ /** The Merkle proof (an array of hashes) generated for the recipient. */
225
+ merkle_proof: string[];
226
+ /** The MerkleDistributor contract address. */
227
+ contract_address: string;
228
+ };
212
229
  /**
213
230
  * String expected to be Ethereum Address
214
231
  */
@@ -19,7 +19,7 @@ export declare const clusterLockHash: (clusterLock: ClusterLock) => string;
19
19
  * @param {string} withdrawalAddress - withdrawal address in definition file.
20
20
  * @returns {boolean} - return if deposit data is valid.
21
21
  */
22
- export declare const verifyDepositData: (distributedPublicKey: string, depositData: Partial<DepositData>, withdrawalAddress: string, forkVersion: string) => {
22
+ export declare const verifyDepositData: (distributedPublicKey: string, depositData: Partial<DepositData>, withdrawalAddress: string, forkVersion: string, compounding?: boolean) => {
23
23
  isValidDepositData: boolean;
24
24
  depositDataMsg: Uint8Array;
25
25
  };
@@ -1,6 +1,6 @@
1
1
  import { UintNumberType } from '@chainsafe/ssz/lib/type/uint';
2
2
  import { type creatorAddressWrapperType, type creatorContainerType, type operatorAddressWrapperType, type operatorContainerType, validatorsContainerType } from './sszTypes';
3
- import { ByteListType, ByteVectorType, ContainerType, ListBasicType, ListCompositeType } from '@chainsafe/ssz';
3
+ import { ByteListType, ByteVectorType, ContainerType, ListBasicType, ListCompositeType, BooleanType } from '@chainsafe/ssz';
4
4
  import { type ValueOfFields } from '@chainsafe/ssz/lib/view/container';
5
5
  import { type ClusterDefinition, type ClusterLock } from '../types';
6
6
  type DefinitionFieldsV1X10 = {
@@ -18,6 +18,7 @@ type DefinitionFieldsV1X10 = {
18
18
  deposit_amounts: ListBasicType<UintNumberType>;
19
19
  consensus_protocol: ByteListType;
20
20
  target_gas_limit: UintNumberType;
21
+ compounding: BooleanType;
21
22
  config_hash?: ByteVectorType;
22
23
  };
23
24
  type DefinitionContainerTypeV1X10 = ContainerType<DefinitionFieldsV1X10>;
@@ -290,6 +290,61 @@ export declare const clusterLockV1X10: {
290
290
  deposit_amounts: null;
291
291
  consensus_protocol: string;
292
292
  target_gas_limit: number;
293
+ compounding: boolean;
294
+ config_hash: string;
295
+ definition_hash: string;
296
+ };
297
+ distributed_validators: {
298
+ distributed_public_key: string;
299
+ public_shares: string[];
300
+ builder_registration: {
301
+ message: {
302
+ fee_recipient: string;
303
+ gas_limit: number;
304
+ timestamp: number;
305
+ pubkey: string;
306
+ };
307
+ signature: string;
308
+ };
309
+ partial_deposit_data: {
310
+ pubkey: string;
311
+ withdrawal_credentials: string;
312
+ amount: string;
313
+ signature: string;
314
+ }[];
315
+ }[];
316
+ signature_aggregate: string;
317
+ lock_hash: string;
318
+ node_signatures: string[];
319
+ };
320
+ export declare const clusterLockWithCompoundingWithdrawals: {
321
+ cluster_definition: {
322
+ name: string;
323
+ creator: {
324
+ address: string;
325
+ config_signature: string;
326
+ };
327
+ operators: {
328
+ address: string;
329
+ enr: string;
330
+ config_signature: string;
331
+ enr_signature: string;
332
+ }[];
333
+ uuid: string;
334
+ version: string;
335
+ timestamp: string;
336
+ num_validators: number;
337
+ threshold: number;
338
+ validators: {
339
+ fee_recipient_address: string;
340
+ withdrawal_address: string;
341
+ }[];
342
+ dkg_algorithm: string;
343
+ fork_version: string;
344
+ deposit_amounts: null;
345
+ consensus_protocol: string;
346
+ target_gas_limit: number;
347
+ compounding: boolean;
293
348
  config_hash: string;
294
349
  definition_hash: string;
295
350
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obolnetwork/obol-sdk",
3
- "version": "2.2.3",
3
+ "version": "2.3.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/index.ts CHANGED
@@ -30,6 +30,7 @@ import {
30
30
  type ClusterValidator,
31
31
  type ETH_ADDRESS,
32
32
  type OWRTranches,
33
+ type Incentives,
33
34
  } from './types.js';
34
35
  import { clusterConfigOrDefinitionHash } from './verification/common.js';
35
36
  import { validatePayload } from './ajv.js';
@@ -520,4 +521,19 @@ export class Client extends Base {
520
521
  );
521
522
  return lock;
522
523
  }
524
+
525
+ /**
526
+ * @param address - Operator address
527
+ * @returns {Promise<Incentives>} The matched incentives from DB
528
+ * @throws On not found if address not found.
529
+ */
530
+ async getIncentivesByAddress(address: string): Promise<Incentives> {
531
+ const incentives: Incentives = await this.request(
532
+ `/${DEFAULT_BASE_VERSION}/address/incentives/${address}`,
533
+ {
534
+ method: 'GET',
535
+ },
536
+ );
537
+ return incentives;
538
+ }
523
539
  }
package/src/types.ts CHANGED
@@ -144,6 +144,9 @@ export interface ClusterDefinition extends ClusterPayload {
144
144
 
145
145
  /** The target gas limit where default is 30M. */
146
146
  target_gas_limit?: number;
147
+
148
+ /** A withdrawal mechanism with 0x02 withdrawal credentials. */
149
+ compounding?: boolean;
147
150
  }
148
151
 
149
152
  /**
@@ -290,6 +293,26 @@ export type ClusterLock = {
290
293
  node_signatures?: string[];
291
294
  };
292
295
 
296
+ /**
297
+ * Incentives
298
+ */
299
+ export type Incentives = {
300
+ /** Operator Address. */
301
+ operator_address: string;
302
+
303
+ /** The amount the recipient is entitled to. */
304
+ amount: string;
305
+
306
+ /** The recipient’s index in the Merkle tree. */
307
+ index: number;
308
+
309
+ /** The Merkle proof (an array of hashes) generated for the recipient. */
310
+ merkle_proof: string[];
311
+
312
+ /** The MerkleDistributor contract address. */
313
+ contract_address: string;
314
+ };
315
+
293
316
  /**
294
317
  * String expected to be Ethereum Address
295
318
  */
@@ -354,23 +354,19 @@ export const verifyDepositData = (
354
354
  depositData: Partial<DepositData>,
355
355
  withdrawalAddress: string,
356
356
  forkVersion: string,
357
+ compounding?: boolean,
357
358
  ): { isValidDepositData: boolean; depositDataMsg: Uint8Array } => {
358
359
  const depositDomain = computeDomain(
359
360
  fromHexString(DOMAIN_DEPOSIT),
360
361
  forkVersion,
361
362
  );
362
- const eth1AddressWithdrawalPrefix = '0x01';
363
- const compoundingWithdrawalPrefix = '0x02';
364
- if (
365
- eth1AddressWithdrawalPrefix +
366
- '0'.repeat(22) +
367
- withdrawalAddress.toLowerCase().slice(2) !==
368
- depositData.withdrawal_credentials &&
369
- compoundingWithdrawalPrefix +
370
- '0'.repeat(22) +
371
- withdrawalAddress.toLowerCase().slice(2) !==
372
- depositData.withdrawal_credentials
373
- ) {
363
+ const withdrawalPrefix = compounding ? '0x02' : '0x01';
364
+ const expectedWithdrawalCredentials =
365
+ withdrawalPrefix +
366
+ '0'.repeat(22) +
367
+ withdrawalAddress.toLowerCase().slice(2);
368
+
369
+ if (expectedWithdrawalCredentials !== depositData.withdrawal_credentials) {
374
370
  return { isValidDepositData: false, depositDataMsg: new Uint8Array(0) };
375
371
  }
376
372
 
@@ -21,6 +21,7 @@ import {
21
21
  ListBasicType,
22
22
  ListCompositeType,
23
23
  fromHexString,
24
+ BooleanType,
24
25
  } from '@chainsafe/ssz';
25
26
  import { type ValueOfFields } from '@chainsafe/ssz/lib/view/container';
26
27
  import {
@@ -48,6 +49,7 @@ type DefinitionFieldsV1X10 = {
48
49
  deposit_amounts: ListBasicType<UintNumberType>;
49
50
  consensus_protocol: ByteListType;
50
51
  target_gas_limit: UintNumberType;
52
+ compounding: BooleanType;
51
53
  config_hash?: ByteVectorType;
52
54
  };
53
55
 
@@ -79,6 +81,7 @@ export const clusterDefinitionContainerTypeV1X10 = (
79
81
  ),
80
82
  consensus_protocol: new ByteListType(256),
81
83
  target_gas_limit: new UintNumberType(8 as UintNumberByteLen),
84
+ compounding: new BooleanType(),
82
85
  };
83
86
 
84
87
  if (!configOnly) {
@@ -144,6 +147,10 @@ export const hashClusterDefinitionV1X10 = (
144
147
  val.target_gas_limit = cluster.target_gas_limit;
145
148
  }
146
149
 
150
+ if (cluster.compounding) {
151
+ val.compounding = cluster.compounding;
152
+ }
153
+
147
154
  if (!configOnly) {
148
155
  val.config_hash = fromHexString(cluster.config_hash);
149
156
  }
@@ -259,6 +259,7 @@ export const verifyDVV1X8 = (clusterLock: ClusterLock): boolean => {
259
259
  depositData as Partial<DepositData>,
260
260
  clusterLock.cluster_definition.validators[i].withdrawal_address,
261
261
  clusterLock.cluster_definition.fork_version,
262
+ clusterLock.cluster_definition.compounding,
262
263
  );
263
264
 
264
265
  if (!isValidDepositData) {