@obolnetwork/obol-sdk 2.4.5 → 2.5.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 (56) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/package.json +4 -2
  3. package/dist/cjs/src/ajv.js +67 -41
  4. package/dist/cjs/src/constants.js +1 -1
  5. package/dist/cjs/src/incentiveHelpers.js +2 -4
  6. package/dist/cjs/src/incentives.js +1 -1
  7. package/dist/cjs/src/index.js +25 -27
  8. package/dist/cjs/src/schema.js +50 -23
  9. package/dist/cjs/src/services.js +3 -2
  10. package/dist/cjs/src/utils.js +4 -4
  11. package/dist/cjs/src/verification/common.js +12 -9
  12. package/dist/cjs/src/verification/signature-validator.js +6 -5
  13. package/dist/cjs/src/verification/v1.8.0.js +18 -0
  14. package/dist/cjs/test/fixtures.js +125 -17
  15. package/dist/cjs/test/incentives.test.js +2 -2
  16. package/dist/cjs/test/methods.test.js +47 -42
  17. package/dist/esm/package.json +4 -2
  18. package/dist/esm/src/ajv.js +66 -40
  19. package/dist/esm/src/constants.js +1 -1
  20. package/dist/esm/src/incentiveHelpers.js +2 -4
  21. package/dist/esm/src/incentives.js +1 -1
  22. package/dist/esm/src/index.js +26 -28
  23. package/dist/esm/src/schema.js +50 -23
  24. package/dist/esm/src/services.js +3 -2
  25. package/dist/esm/src/utils.js +4 -4
  26. package/dist/esm/src/verification/common.js +12 -9
  27. package/dist/esm/src/verification/signature-validator.js +6 -5
  28. package/dist/esm/src/verification/v1.8.0.js +18 -0
  29. package/dist/esm/test/fixtures.js +124 -16
  30. package/dist/esm/test/incentives.test.js +2 -2
  31. package/dist/esm/test/methods.test.js +48 -43
  32. package/dist/types/src/ajv.d.ts +3 -2
  33. package/dist/types/src/constants.d.ts +1 -1
  34. package/dist/types/src/incentiveHelpers.d.ts +1 -1
  35. package/dist/types/src/incentives.d.ts +2 -2
  36. package/dist/types/src/index.d.ts +1 -0
  37. package/dist/types/src/schema.d.ts +40 -8
  38. package/dist/types/src/services.d.ts +3 -2
  39. package/dist/types/src/types.d.ts +16 -6
  40. package/dist/types/src/utils.d.ts +1 -1
  41. package/dist/types/src/verification/common.d.ts +2 -2
  42. package/dist/types/src/verification/signature-validator.d.ts +5 -2
  43. package/dist/types/test/fixtures.d.ts +78 -11
  44. package/package.json +4 -2
  45. package/src/ajv.ts +91 -52
  46. package/src/constants.ts +1 -1
  47. package/src/incentiveHelpers.ts +1 -5
  48. package/src/incentives.ts +3 -4
  49. package/src/index.ts +37 -35
  50. package/src/schema.ts +45 -22
  51. package/src/services.ts +4 -2
  52. package/src/types.ts +20 -5
  53. package/src/utils.ts +7 -4
  54. package/src/verification/common.ts +13 -1
  55. package/src/verification/signature-validator.ts +10 -3
  56. package/src/verification/v1.8.0.ts +24 -0
package/src/ajv.ts CHANGED
@@ -1,78 +1,117 @@
1
- import Ajv, { type ErrorObject } from 'ajv';
1
+ import addFormats from 'ajv-formats';
2
+ import addKeywords from 'ajv-keywords';
2
3
  import { parseUnits } from 'ethers';
3
4
  import {
4
5
  type RewardsSplitPayload,
5
6
  type SplitRecipient,
6
7
  type TotalSplitPayload,
7
8
  } from './types';
9
+ import Ajv from 'ajv';
8
10
  import {
9
11
  DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT,
10
12
  DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT,
11
13
  } from './constants';
12
14
 
13
- const validDepositAmounts = (data: boolean, deposits: string[]): boolean => {
14
- let sum = 0;
15
- // from ether togwei is same as from gwei to wei
16
- const maxDeposit = Number(parseUnits('32', 'gwei'));
17
- const minDeposit = Number(parseUnits('1', 'gwei'));
15
+ export const VALID_DEPOSIT_AMOUNTS = [
16
+ parseUnits('1', 'gwei').toString(),
17
+ parseUnits('32', 'gwei').toString(),
18
+ parseUnits('8', 'gwei').toString(),
19
+ parseUnits('256', 'gwei').toString(),
20
+ ];
18
21
 
19
- for (const element of deposits) {
20
- const amountInGWei = Number(element);
22
+ export const VALID_NON_COMPOUNDING_AMOUNTS = [
23
+ parseUnits('1', 'gwei').toString(),
24
+ parseUnits('32', 'gwei').toString(),
25
+ ];
21
26
 
22
- if (
23
- !Number.isInteger(amountInGWei) ||
24
- amountInGWei > maxDeposit ||
25
- amountInGWei < minDeposit
26
- ) {
27
- return false;
28
- }
29
- sum += amountInGWei;
30
- }
31
- if (sum / minDeposit !== 32) {
32
- return false;
33
- } else {
34
- return true;
35
- }
27
+ // They dont see defaults set in schema
28
+ const validateRewardsSplitRecipients = (
29
+ _: boolean,
30
+ data: RewardsSplitPayload,
31
+ ): boolean => {
32
+ const obolRAFSplit =
33
+ data.ObolRAFSplit ?? DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT;
34
+ const splitPercentage = data.splitRecipients.reduce(
35
+ (acc: number, curr: SplitRecipient) => acc + curr.percentAllocation,
36
+ 0,
37
+ );
38
+ return splitPercentage + obolRAFSplit === 100;
36
39
  };
37
40
 
38
- const validateSplitRecipients = (
41
+ const validateTotalSplitRecipients = (
39
42
  _: boolean,
40
- data: RewardsSplitPayload | TotalSplitPayload,
43
+ data: TotalSplitPayload,
41
44
  ): boolean => {
45
+ const obolRAFSplit =
46
+ data.ObolRAFSplit ?? DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT;
42
47
  const splitPercentage = data.splitRecipients.reduce(
43
48
  (acc: number, curr: SplitRecipient) => acc + curr.percentAllocation,
44
49
  0,
45
50
  );
46
- const ObolRAFSplitParam = data.ObolRAFSplit
47
- ? data.ObolRAFSplit
48
- : 'principalRecipient' in data
49
- ? DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT
50
- : DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT;
51
- return splitPercentage + ObolRAFSplitParam === 100;
51
+ return splitPercentage + obolRAFSplit === 100;
52
52
  };
53
53
 
54
- export function validatePayload(
55
- data: any,
56
- schema: any,
57
- ): ErrorObject[] | undefined | null | boolean {
58
- const ajv = new Ajv();
59
- ajv.addKeyword({
60
- keyword: 'validDepositAmounts',
61
- validate: validDepositAmounts,
62
- errors: true,
63
- });
54
+ const validateUniqueAddresses = (
55
+ _: boolean,
56
+ operators: Array<{ address: string }>,
57
+ ): boolean => {
58
+ if (!operators) {
59
+ return false;
60
+ }
61
+
62
+ if (operators.length < 4) {
63
+ return false;
64
+ }
65
+
66
+ if (operators.every(op => op.address === '')) {
67
+ return true;
68
+ }
69
+
70
+ if (operators.some(op => op.address.length !== 42)) {
71
+ return false;
72
+ }
73
+
74
+ const addresses = operators.map(op => op.address);
75
+ const uniqueAddresses = new Set(addresses);
76
+ const isUnique = uniqueAddresses.size === addresses.length;
77
+ return isUnique;
78
+ };
79
+
80
+ const ajv = new Ajv({
81
+ allErrors: true,
82
+ useDefaults: true,
83
+ strict: false,
84
+ $data: true,
85
+ });
86
+ addFormats(ajv);
87
+ addKeywords(ajv, ['patternRequired']);
88
+
89
+ ajv.addKeyword({
90
+ keyword: 'validateRewardsSplitRecipients',
91
+ validate: validateRewardsSplitRecipients,
92
+ schemaType: 'boolean',
93
+ });
94
+
95
+ ajv.addKeyword({
96
+ keyword: 'validateTotalSplitRecipients',
97
+ validate: validateTotalSplitRecipients,
98
+ schemaType: 'boolean',
99
+ });
100
+
101
+ ajv.addKeyword({
102
+ keyword: 'validateUniqueAddresses',
103
+ validate: validateUniqueAddresses,
104
+ schemaType: 'boolean',
105
+ });
64
106
 
65
- ajv.addKeyword({
66
- keyword: 'validateSplitRecipients',
67
- validate: validateSplitRecipients,
68
- errors: true,
69
- });
70
- const validate = ajv.compile(schema);
71
- const isValid = validate(data);
72
- if (!isValid) {
73
- throw new Error(
74
- `Schema compilation errors', ${validate.errors?.[0].message}`,
75
- );
107
+ export function validatePayload<T>(data: unknown, schema: object): T {
108
+ const validate = ajv.compile<T>(schema);
109
+ const valid = validate(data);
110
+ if (!valid) {
111
+ const errors = validate.errors
112
+ ?.map(e => `${e.instancePath} ${e.message}`)
113
+ .join(', ');
114
+ throw new Error(`Validation failed: ${errors}`);
76
115
  }
77
- return isValid;
116
+ return data;
78
117
  }
package/src/constants.ts CHANGED
@@ -119,7 +119,7 @@ export const signEnrPayload = (
119
119
 
120
120
  export const DKG_ALGORITHM = 'default';
121
121
 
122
- export const CONFIG_VERSION = 'v1.8.0';
122
+ export const CONFIG_VERSION = 'v1.10.0';
123
123
 
124
124
  export const SDK_VERSION = pjson.version;
125
125
 
@@ -1,7 +1,6 @@
1
1
  import { type ProviderType, type SignerType, type ETH_ADDRESS } from './types';
2
2
  import { Contract } from 'ethers';
3
3
  import { MerkleDistributorABI } from './abi/MerkleDistributorWithDeadline';
4
- import { getProvider } from './utils';
5
4
 
6
5
  export const claimIncentivesFromMerkleDistributor = async (incentivesData: {
7
6
  signer: SignerType;
@@ -35,18 +34,15 @@ export const claimIncentivesFromMerkleDistributor = async (incentivesData: {
35
34
  };
36
35
 
37
36
  export const isClaimedFromMerkleDistributor = async (
38
- chainId: number,
39
37
  contractAddress: ETH_ADDRESS,
40
38
  index: number,
41
39
  provider: ProviderType | undefined | null,
42
40
  ): Promise<boolean> => {
43
41
  try {
44
- const clientProvider = provider ?? getProvider(chainId);
45
-
46
42
  const contract = new Contract(
47
43
  contractAddress,
48
44
  MerkleDistributorABI.abi,
49
- clientProvider,
45
+ provider,
50
46
  );
51
47
 
52
48
  const claimed = await contract.isClaimed(BigInt(index));
package/src/incentives.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { isContractAvailable } from './utils';
2
2
  import {
3
- type Incentives as IncentivesType,
3
+ type ClaimableIncentives,
4
4
  type ETH_ADDRESS,
5
5
  FORK_NAMES,
6
6
  type ProviderType,
@@ -126,7 +126,6 @@ export class Incentives {
126
126
  index: number,
127
127
  ): Promise<boolean> {
128
128
  return await isClaimedFromMerkleDistributor(
129
- this.chainId,
130
129
  contractAddress,
131
130
  index,
132
131
  this.provider,
@@ -141,9 +140,9 @@ export class Incentives {
141
140
  * An example of how to use getIncentivesByAddress:
142
141
  * [obolClient](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L250)
143
142
  */
144
- async getIncentivesByAddress(address: string): Promise<IncentivesType> {
143
+ async getIncentivesByAddress(address: string): Promise<ClaimableIncentives> {
145
144
  const network = FORK_NAMES[this.chainId];
146
- const incentives: IncentivesType = await this.request(
145
+ const incentives: ClaimableIncentives = await this.request(
147
146
  `/${DEFAULT_BASE_VERSION}/address/incentives/${network}/${address}`,
148
147
  {
149
148
  method: 'GET',
package/src/index.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { ZeroAddress } from 'ethers';
2
1
  import { v4 as uuidv4 } from 'uuid';
3
2
  import { Base } from './base.js';
4
3
  import {
@@ -16,7 +15,6 @@ import {
16
15
  AVAILABLE_SPLITTER_CHAINS,
17
16
  CHAIN_CONFIGURATION,
18
17
  DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT,
19
- DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT,
20
18
  OBOL_SDK_EMAIL,
21
19
  } from './constants.js';
22
20
  import { ConflictError } from './errors.js';
@@ -54,6 +52,7 @@ export * from './types.js';
54
52
  export * from './services.js';
55
53
  export * from './verification/signature-validator.js';
56
54
  export * from './verification/common.js';
55
+ export * from './constants.js';
57
56
  export { Incentives } from './incentives.js';
58
57
 
59
58
  /**
@@ -174,16 +173,16 @@ export class Client extends Base {
174
173
  principalRecipient,
175
174
  etherAmount,
176
175
  ObolRAFSplit = DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT,
177
- distributorFee = 0,
178
- controllerAddress = ZeroAddress,
179
- recoveryAddress = ZeroAddress,
176
+ distributorFee,
177
+ controllerAddress,
178
+ recoveryAddress,
180
179
  }: RewardsSplitPayload): Promise<ClusterValidator> {
181
180
  // This method doesnt require T&C signature
182
181
  if (!this.signer) {
183
182
  throw new Error('Signer is required in createObolRewardsSplit');
184
183
  }
185
184
 
186
- validatePayload(
185
+ const validatedPayload = validatePayload<Required<RewardsSplitPayload>>(
187
186
  {
188
187
  splitRecipients,
189
188
  principalRecipient,
@@ -234,10 +233,10 @@ export class Client extends Base {
234
233
  const retroActiveFundingRecipient = {
235
234
  account:
236
235
  CHAIN_CONFIGURATION[this.chainId].RETROACTIVE_FUNDING_ADDRESS.address,
237
- percentAllocation: ObolRAFSplit,
236
+ percentAllocation: validatedPayload.ObolRAFSplit,
238
237
  };
239
238
 
240
- const copiedSplitRecipients = [...splitRecipients];
239
+ const copiedSplitRecipients = [...validatedPayload.splitRecipients];
241
240
  copiedSplitRecipients.push(retroActiveFundingRecipient);
242
241
 
243
242
  const { accounts, percentAllocations } = formatSplitRecipients(
@@ -249,8 +248,8 @@ export class Client extends Base {
249
248
  accounts,
250
249
  percentAllocations,
251
250
  chainId: this.chainId,
252
- distributorFee,
253
- controllerAddress,
251
+ distributorFee: validatedPayload.distributorFee,
252
+ controllerAddress: validatedPayload.controllerAddress,
254
253
  });
255
254
 
256
255
  const isSplitterDeployed = await isContractAvailable(
@@ -265,12 +264,12 @@ export class Client extends Base {
265
264
  predictedSplitterAddress,
266
265
  accounts,
267
266
  percentAllocations,
268
- principalRecipient,
269
- etherAmount,
267
+ principalRecipient: validatedPayload.principalRecipient,
268
+ etherAmount: validatedPayload.etherAmount,
270
269
  chainId: this.chainId,
271
- distributorFee,
272
- controllerAddress,
273
- recoveryAddress,
270
+ distributorFee: validatedPayload.distributorFee,
271
+ controllerAddress: validatedPayload.controllerAddress,
272
+ recoveryAddress: validatedPayload.recoveryAddress,
274
273
  });
275
274
 
276
275
  return { withdrawal_address, fee_recipient_address };
@@ -292,16 +291,16 @@ export class Client extends Base {
292
291
  // add the example reference
293
292
  async createObolTotalSplit({
294
293
  splitRecipients,
295
- ObolRAFSplit = DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT,
296
- distributorFee = 0,
297
- controllerAddress = ZeroAddress,
294
+ ObolRAFSplit,
295
+ distributorFee,
296
+ controllerAddress,
298
297
  }: TotalSplitPayload): Promise<ClusterValidator> {
299
298
  // This method doesnt require T&C signature
300
299
  if (!this.signer) {
301
300
  throw new Error('Signer is required in createObolTotalSplit');
302
301
  }
303
302
 
304
- validatePayload(
303
+ const validatedPayload = validatePayload<Required<TotalSplitPayload>>(
305
304
  {
306
305
  splitRecipients,
307
306
  ObolRAFSplit,
@@ -333,10 +332,10 @@ export class Client extends Base {
333
332
  const retroActiveFundingRecipient = {
334
333
  account:
335
334
  CHAIN_CONFIGURATION[this.chainId].RETROACTIVE_FUNDING_ADDRESS.address,
336
- percentAllocation: ObolRAFSplit,
335
+ percentAllocation: validatedPayload.ObolRAFSplit,
337
336
  };
338
337
 
339
- const copiedSplitRecipients = [...splitRecipients];
338
+ const copiedSplitRecipients = [...validatedPayload.splitRecipients];
340
339
  copiedSplitRecipients.push(retroActiveFundingRecipient);
341
340
 
342
341
  const { accounts, percentAllocations } = formatSplitRecipients(
@@ -347,8 +346,8 @@ export class Client extends Base {
347
346
  accounts,
348
347
  percentAllocations,
349
348
  chainId: this.chainId,
350
- distributorFee,
351
- controllerAddress,
349
+ distributorFee: validatedPayload.distributorFee,
350
+ controllerAddress: validatedPayload.controllerAddress,
352
351
  });
353
352
 
354
353
  const isSplitterDeployed = await isContractAvailable(
@@ -362,8 +361,8 @@ export class Client extends Base {
362
361
  accounts,
363
362
  percentAllocations,
364
363
  chainId: this.chainId,
365
- distributorFee,
366
- controllerAddress,
364
+ distributorFee: validatedPayload.distributorFee,
365
+ controllerAddress: validatedPayload.controllerAddress,
367
366
  });
368
367
  return {
369
368
  withdrawal_address: splitterAddress,
@@ -411,20 +410,20 @@ export class Client extends Base {
411
410
  throw new Error('Signer is required in createClusterDefinition');
412
411
  }
413
412
 
414
- validatePayload(newCluster, definitionSchema);
413
+ const validatedCluster = validatePayload<ClusterPayload>(
414
+ newCluster,
415
+ definitionSchema,
416
+ );
415
417
 
416
418
  const clusterConfig: Partial<ClusterDefinition> = {
417
- ...newCluster,
419
+ ...validatedCluster,
418
420
  fork_version: this.fork_version,
419
421
  dkg_algorithm: DKG_ALGORITHM,
420
422
  version: CONFIG_VERSION,
421
423
  uuid: uuidv4(),
422
424
  timestamp: new Date().toISOString(),
423
- threshold: Math.ceil((2 * newCluster.operators.length) / 3),
424
- num_validators: newCluster.validators.length,
425
- deposit_amounts: newCluster.deposit_amounts
426
- ? newCluster.deposit_amounts
427
- : ['32000000000'],
425
+ threshold: Math.ceil((2 * validatedCluster.operators.length) / 3),
426
+ num_validators: validatedCluster.validators.length,
428
427
  };
429
428
  try {
430
429
  const address = await this.signer.getAddress();
@@ -479,7 +478,10 @@ export class Client extends Base {
479
478
  throw new Error('Signer is required in acceptClusterDefinition');
480
479
  }
481
480
 
482
- validatePayload(operatorPayload, operatorPayloadSchema);
481
+ const validatedPayload = validatePayload<OperatorPayload>(
482
+ operatorPayload,
483
+ operatorPayloadSchema,
484
+ );
483
485
 
484
486
  try {
485
487
  const address = await this.signer.getAddress();
@@ -492,11 +494,11 @@ export class Client extends Base {
492
494
  const operatorENRSignature = await this.signer.signTypedData(
493
495
  Domain(this.chainId),
494
496
  EnrSigningTypes,
495
- { enr: operatorPayload.enr },
497
+ { enr: validatedPayload.enr },
496
498
  );
497
499
 
498
500
  const operatorData: OperatorPayload = {
499
- ...operatorPayload,
501
+ ...validatedPayload,
500
502
  address,
501
503
  enr_signature: operatorENRSignature,
502
504
  fork_version: this.fork_version,
package/src/schema.ts CHANGED
@@ -1,17 +1,15 @@
1
+ import { ZeroAddress } from 'ethers';
1
2
  import {
2
3
  DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT,
3
4
  DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT,
4
5
  } from './constants';
6
+ import { VALID_DEPOSIT_AMOUNTS, VALID_NON_COMPOUNDING_AMOUNTS } from './ajv';
5
7
 
6
8
  export const operatorPayloadSchema = {
7
9
  type: 'object',
8
10
  properties: {
9
- version: {
10
- type: 'string',
11
- },
12
- enr: {
13
- type: 'string',
14
- },
11
+ version: { type: 'string' },
12
+ enr: { type: 'string' },
15
13
  },
16
14
  required: ['version', 'enr'],
17
15
  };
@@ -19,24 +17,20 @@ export const operatorPayloadSchema = {
19
17
  export const definitionSchema = {
20
18
  type: 'object',
21
19
  properties: {
22
- name: {
23
- type: 'string',
24
- },
20
+ name: { type: 'string' },
25
21
  operators: {
26
22
  type: 'array',
27
23
  minItems: 4,
28
- uniqueItems: true,
29
24
  items: {
30
25
  type: 'object',
31
26
  properties: {
32
27
  address: {
33
28
  type: 'string',
34
- minLength: 42,
35
- maxLength: 42,
36
29
  },
37
30
  },
38
31
  required: ['address'],
39
32
  },
33
+ validateUniqueAddresses: true,
40
34
  },
41
35
  validators: {
42
36
  type: 'array',
@@ -57,12 +51,39 @@ export const definitionSchema = {
57
51
  },
58
52
  },
59
53
  deposit_amounts: {
60
- type: 'array',
54
+ type: ['array', 'null'],
61
55
  items: {
62
56
  type: 'string',
63
57
  pattern: '^[0-9]+$',
64
58
  },
65
- validDepositAmounts: true,
59
+ if: {
60
+ $data: '1/compounding',
61
+ },
62
+ then: {
63
+ items: {
64
+ enum: VALID_DEPOSIT_AMOUNTS,
65
+ },
66
+ },
67
+ else: {
68
+ items: {
69
+ enum: VALID_NON_COMPOUNDING_AMOUNTS,
70
+ },
71
+ },
72
+ default: null,
73
+ },
74
+ compounding: {
75
+ type: 'boolean',
76
+ default: true,
77
+ },
78
+ target_gas_limit: {
79
+ type: 'number',
80
+ minimum: 1,
81
+ default: 36000000,
82
+ },
83
+ consensus_protocol: {
84
+ type: 'string',
85
+ enum: ['qbft', ''],
86
+ default: '',
66
87
  },
67
88
  },
68
89
  required: ['name', 'operators', 'validators'],
@@ -80,9 +101,7 @@ export const totalSplitterPayloadSchema = {
80
101
  type: 'string',
81
102
  pattern: '^0x[a-fA-F0-9]{40}$',
82
103
  },
83
- percentAllocation: {
84
- type: 'number',
85
- },
104
+ percentAllocation: { type: 'number' },
86
105
  },
87
106
  required: ['account', 'percentAllocation'],
88
107
  },
@@ -90,40 +109,44 @@ export const totalSplitterPayloadSchema = {
90
109
  ObolRAFSplit: {
91
110
  type: 'number',
92
111
  minimum: DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT,
112
+ default: DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT,
93
113
  },
94
114
  distributorFee: {
95
115
  type: 'number',
96
116
  maximum: 10,
97
117
  multipleOf: 0.01,
118
+ default: 0,
98
119
  },
99
120
  controllerAddress: {
100
121
  type: 'string',
101
122
  pattern: '^0x[a-fA-F0-9]{40}$',
123
+ default: ZeroAddress,
102
124
  },
103
- validateSplitRecipients: true,
104
125
  },
126
+ validateTotalSplitRecipients: true,
105
127
  required: ['splitRecipients'],
106
128
  };
107
129
 
108
130
  export const rewardsSplitterPayloadSchema = {
109
- ...totalSplitterPayloadSchema,
131
+ type: 'object',
110
132
  properties: {
111
133
  ...totalSplitterPayloadSchema.properties,
112
134
  ObolRAFSplit: {
113
135
  type: 'number',
114
136
  minimum: DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT,
137
+ default: DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT,
115
138
  },
116
139
  recoveryAddress: {
117
140
  type: 'string',
118
141
  pattern: '^0x[a-fA-F0-9]{40}$',
142
+ default: ZeroAddress,
119
143
  },
120
- etherAmount: {
121
- type: 'number',
122
- },
144
+ etherAmount: { type: 'number' },
123
145
  principalRecipient: {
124
146
  type: 'string',
125
147
  pattern: '^0x[a-fA-F0-9]{40}$',
126
148
  },
127
149
  },
150
+ validateRewardsSplitRecipients: true,
128
151
  required: ['splitRecipients', 'principalRecipient', 'etherAmount'],
129
152
  };
package/src/services.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { type ClusterLock } from './types.js';
1
+ import { type SafeRpcUrl, type ClusterLock } from './types.js';
2
2
  import { isValidClusterLock } from './verification/common.js';
3
3
 
4
4
  /**
5
5
  * Verifies Cluster Lock's validity.
6
6
  * @param lock - cluster lock
7
+ * @param safeRpcUrl - optional safeRpcUrl for safe wallet verification
7
8
  * @returns {Promise<{ result: boolean }> } boolean result to indicate if lock is valid
8
9
  * @throws on missing keys or values.
9
10
  *
@@ -12,9 +13,10 @@ import { isValidClusterLock } from './verification/common.js';
12
13
  */
13
14
  export const validateClusterLock = async (
14
15
  lock: ClusterLock,
16
+ safeRpcUrl?: SafeRpcUrl,
15
17
  ): Promise<boolean> => {
16
18
  try {
17
- const isLockValid = await isValidClusterLock(lock);
19
+ const isLockValid = await isValidClusterLock(lock, safeRpcUrl);
18
20
  return isLockValid;
19
21
  } catch (err: any) {
20
22
  throw err;
package/src/types.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ type Wallet,
2
3
  type ethers,
3
4
  type JsonRpcApiProvider,
4
5
  type JsonRpcProvider,
@@ -104,6 +105,15 @@ export type ClusterPayload = {
104
105
 
105
106
  /** The cluster partial deposits in gwei or 32000000000. */
106
107
  deposit_amounts?: string[] | null;
108
+
109
+ /** A withdrawal mechanism with 0x02 withdrawal credentials. */
110
+ compounding?: boolean;
111
+
112
+ /** The target gas limit where default is 36M. */
113
+ target_gas_limit?: number;
114
+
115
+ /** The consensus protocol e.g qbft. */
116
+ consensus_protocol?: string;
107
117
  };
108
118
 
109
119
  /**
@@ -143,7 +153,7 @@ export interface ClusterDefinition extends ClusterPayload {
143
153
  /** The consensus protocol e.g qbft. */
144
154
  consensus_protocol?: string;
145
155
 
146
- /** The target gas limit where default is 30M. */
156
+ /** The target gas limit where default is 36M. */
147
157
  target_gas_limit?: number;
148
158
 
149
159
  /** A withdrawal mechanism with 0x02 withdrawal credentials. */
@@ -295,16 +305,16 @@ export type ClusterLock = {
295
305
  };
296
306
 
297
307
  /**
298
- * Incentives
308
+ * Claimable Obol Incentives
299
309
  */
300
- export type Incentives = {
310
+ export type ClaimableIncentives = {
301
311
  /** Operator Address. */
302
312
  operator_address: string;
303
313
 
304
314
  /** The amount the recipient is entitled to. */
305
315
  amount: string;
306
316
 
307
- /** The recipients index in the Merkle tree. */
317
+ /** The recipient's index in the Merkle tree. */
308
318
  index: number;
309
319
 
310
320
  /** The Merkle proof (an array of hashes) generated for the recipient. */
@@ -328,10 +338,15 @@ export type ProviderType =
328
338
  | JsonRpcApiProvider
329
339
  | ethers.BrowserProvider;
330
340
 
341
+ /**
342
+ * Safe Wallet Provider Types
343
+ */
344
+ export type SafeRpcUrl = string;
345
+
331
346
  /**
332
347
  * Signer Types
333
348
  */
334
- export type SignerType = Signer | JsonRpcSigner;
349
+ export type SignerType = Signer | JsonRpcSigner | Wallet;
335
350
 
336
351
  /**
337
352
  * claimIncentives Response
package/src/utils.ts CHANGED
@@ -76,10 +76,13 @@ export const isContractAvailable = async (
76
76
  return !!code && code !== '0x' && code !== '0x0';
77
77
  };
78
78
 
79
- export const getProvider = (chainId: number): ethers.JsonRpcProvider => {
80
- const rpcUrl = PROVIDER_MAP[chainId];
81
- if (!rpcUrl || rpcUrl === 'undefined') {
79
+ export const getProvider = (
80
+ chainId: number,
81
+ rpcUrl?: string,
82
+ ): ethers.JsonRpcProvider => {
83
+ const resolvedRpcUrl = rpcUrl ?? PROVIDER_MAP[chainId];
84
+ if (chainId && (!resolvedRpcUrl || resolvedRpcUrl === 'undefined')) {
82
85
  throw new Error(`No provider configured for ${FORK_NAMES[chainId]}`);
83
86
  }
84
- return new ethers.JsonRpcProvider(rpcUrl);
87
+ return new ethers.JsonRpcProvider(resolvedRpcUrl);
85
88
  };