@obolnetwork/obol-sdk 2.4.6 → 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.
@@ -38,7 +38,6 @@ const index_1 = require("../src/index");
38
38
  const fixtures_js_1 = require("./fixtures.js");
39
39
  const constants_1 = require("../src/constants");
40
40
  const base_1 = require("../src/base");
41
- const ajv_1 = require("../src/ajv");
42
41
  const msw_1 = require("msw");
43
42
  const node_1 = require("msw/node");
44
43
  const termsAndConditions_1 = require("../src/verification/termsAndConditions");
@@ -65,31 +64,31 @@ describe('Cluster Client', () => {
65
64
  clientInstance['request'] = jest
66
65
  .fn()
67
66
  .mockReturnValue(Promise.resolve({ config_hash: mockConfigHash }));
68
- const configHash = yield clientInstance.createClusterDefinition(fixtures_js_1.clusterConfigV1X8);
67
+ const configHash = yield clientInstance.createClusterDefinition(fixtures_js_1.clusterConfigV1X10);
69
68
  expect(configHash).toEqual(mockConfigHash);
70
69
  }));
71
70
  test('acceptClusterDefinition should return cluster definition', () => __awaiter(void 0, void 0, void 0, function* () {
72
71
  clientInstance['request'] = jest
73
72
  .fn()
74
- .mockReturnValue(Promise.resolve(fixtures_js_1.clusterLockV1X8.cluster_definition));
73
+ .mockReturnValue(Promise.resolve(fixtures_js_1.clusterLockV1X10.cluster_definition));
75
74
  const clusterDefinition = yield clientInstance.acceptClusterDefinition({
76
- enr: fixtures_js_1.clusterLockV1X8.cluster_definition.operators[0].enr,
77
- version: fixtures_js_1.clusterLockV1X8.cluster_definition.version,
78
- }, fixtures_js_1.clusterLockV1X8.cluster_definition.config_hash);
79
- expect(clusterDefinition).toEqual(fixtures_js_1.clusterLockV1X8.cluster_definition);
75
+ enr: fixtures_js_1.clusterLockV1X10.cluster_definition.operators[0].enr,
76
+ version: fixtures_js_1.clusterLockV1X10.cluster_definition.version,
77
+ }, fixtures_js_1.clusterLockV1X10.cluster_definition.config_hash);
78
+ expect(clusterDefinition).toEqual(fixtures_js_1.clusterLockV1X10.cluster_definition);
80
79
  }));
81
80
  test('createClusterDefinition should throw an error on invalid operators', () => __awaiter(void 0, void 0, void 0, function* () {
82
81
  clientInstance['request'] = jest
83
82
  .fn()
84
83
  .mockReturnValue(Promise.resolve({ config_hash: mockConfigHash }));
85
84
  try {
86
- yield clientInstance.createClusterDefinition(Object.assign(Object.assign({}, fixtures_js_1.clusterConfigV1X8), { operators: [] }));
85
+ yield clientInstance.createClusterDefinition(Object.assign(Object.assign({}, fixtures_js_1.clusterConfigV1X10), { operators: [] }));
87
86
  }
88
87
  catch (error) {
89
- expect(error.message).toEqual("Schema compilation errors', must NOT have fewer than 4 items");
88
+ expect(error.message).toEqual('Validation failed: /operators must pass "validateUniqueAddresses" keyword validation, /operators must NOT have fewer than 4 items');
90
89
  }
91
90
  }));
92
- // cause we default to 32000000000
91
+ // cause we default to null
93
92
  test('createClusterDefinition should accept a configuration without deposit_amounts', () => __awaiter(void 0, void 0, void 0, function* () {
94
93
  clientInstance['request'] = jest
95
94
  .fn()
@@ -105,32 +104,27 @@ describe('Cluster Client', () => {
105
104
  yield clientInstance.createClusterDefinition(Object.assign(Object.assign({}, fixtures_js_1.clusterConfigV1X7), { deposit_amounts: ['34000000'] }));
106
105
  }
107
106
  catch (error) {
108
- expect(error.message).toEqual('Schema compilation errors\', must pass "validDepositAmounts" keyword validation');
109
- }
110
- }));
111
- test('validatePayload should throw an error on empty schema', () => __awaiter(void 0, void 0, void 0, function* () {
112
- try {
113
- (0, ajv_1.validatePayload)(Object.assign(Object.assign({}, fixtures_js_1.clusterConfigV1X8), { operators: [] }), '');
114
- }
115
- catch (error) {
116
- expect(error.message).toEqual('schema must be object or boolean');
107
+ expect(error.message).toEqual('Validation failed: /deposit_amounts/0 must be equal to one of the allowed values, /deposit_amounts must match "then" schema');
117
108
  }
118
109
  }));
119
110
  test('getClusterdefinition should return cluster definition if config hash exist', () => __awaiter(void 0, void 0, void 0, function* () {
120
- var _a;
121
111
  clientInstance['request'] = jest
122
112
  .fn()
123
- .mockReturnValue(Promise.resolve(fixtures_js_1.clusterLockV1X8.cluster_definition));
124
- const clusterDefinition = yield clientInstance.getClusterDefinition(fixtures_js_1.clusterLockV1X8.cluster_definition.config_hash);
125
- expect((_a = clusterDefinition.deposit_amounts) === null || _a === void 0 ? void 0 : _a.length).toEqual(fixtures_js_1.clusterLockV1X8.cluster_definition.deposit_amounts.length);
126
- expect(clusterDefinition.config_hash).toEqual(fixtures_js_1.clusterLockV1X8.cluster_definition.config_hash);
113
+ .mockReturnValue(Promise.resolve(fixtures_js_1.clusterLockV1X10.cluster_definition));
114
+ const clusterDefinition = yield clientInstance.getClusterDefinition(fixtures_js_1.clusterLockV1X10.cluster_definition.config_hash);
115
+ expect(clusterDefinition.deposit_amounts).toBeDefined();
116
+ expect(clusterDefinition.config_hash).toEqual(fixtures_js_1.clusterLockV1X10.cluster_definition.config_hash);
117
+ // Test for new fields
118
+ expect(clusterDefinition.compounding).toBeDefined();
119
+ expect(clusterDefinition.target_gas_limit).toBeDefined();
120
+ expect(clusterDefinition.consensus_protocol).toBeDefined();
127
121
  }));
128
122
  test('getClusterLock should return lockFile if exist', () => __awaiter(void 0, void 0, void 0, function* () {
129
123
  clientInstance['request'] = jest
130
124
  .fn()
131
- .mockReturnValue(Promise.resolve(fixtures_js_1.clusterLockV1X8));
132
- const clusterLock = yield clientInstance.getClusterLock(fixtures_js_1.clusterLockV1X8.cluster_definition.config_hash);
133
- expect(clusterLock.lock_hash).toEqual(fixtures_js_1.clusterLockV1X8.lock_hash);
125
+ .mockReturnValue(Promise.resolve(fixtures_js_1.clusterLockV1X10));
126
+ const clusterLock = yield clientInstance.getClusterLock(fixtures_js_1.clusterLockV1X10.cluster_definition.config_hash);
127
+ expect(clusterLock.lock_hash).toEqual(fixtures_js_1.clusterLockV1X10.lock_hash);
134
128
  }));
135
129
  test('request method should set user agent header', () => __awaiter(void 0, void 0, void 0, function* () {
136
130
  const server = (0, node_1.setupServer)(msw_1.http.get('http://testexample.com/test', ({ request }) => {
@@ -170,7 +164,7 @@ describe('Cluster Client without a signer', () => {
170
164
  });
171
165
  test('createClusterDefinition should throw an error without signer', () => __awaiter(void 0, void 0, void 0, function* () {
172
166
  try {
173
- yield clientInstance.createClusterDefinition(fixtures_js_1.clusterConfigV1X8);
167
+ yield clientInstance.createClusterDefinition(fixtures_js_1.clusterConfigV1X10);
174
168
  }
175
169
  catch (err) {
176
170
  expect(err.message).toEqual('Signer is required in createClusterDefinition');
@@ -179,9 +173,9 @@ describe('Cluster Client without a signer', () => {
179
173
  test('acceptClusterDefinition should throw an error without signer', () => __awaiter(void 0, void 0, void 0, function* () {
180
174
  try {
181
175
  yield clientInstance.acceptClusterDefinition({
182
- enr: fixtures_js_1.clusterLockV1X8.cluster_definition.operators[0].enr,
183
- version: fixtures_js_1.clusterLockV1X8.cluster_definition.version,
184
- }, fixtures_js_1.clusterLockV1X8.cluster_definition.config_hash);
176
+ enr: fixtures_js_1.clusterLockV1X10.cluster_definition.operators[0].enr,
177
+ version: fixtures_js_1.clusterLockV1X10.cluster_definition.version,
178
+ }, fixtures_js_1.clusterLockV1X10.cluster_definition.config_hash);
185
179
  }
186
180
  catch (err) {
187
181
  expect(err.message).toEqual('Signer is required in acceptClusterDefinition');
@@ -190,16 +184,16 @@ describe('Cluster Client without a signer', () => {
190
184
  test('getClusterdefinition should return cluster definition if config hash exist', () => __awaiter(void 0, void 0, void 0, function* () {
191
185
  clientInstance['request'] = jest
192
186
  .fn()
193
- .mockReturnValue(Promise.resolve(fixtures_js_1.clusterLockV1X8.cluster_definition));
194
- const clusterDefinition = yield clientInstance.getClusterDefinition(fixtures_js_1.clusterLockV1X8.cluster_definition.config_hash);
195
- expect(clusterDefinition.config_hash).toEqual(fixtures_js_1.clusterLockV1X8.cluster_definition.config_hash);
187
+ .mockReturnValue(Promise.resolve(fixtures_js_1.clusterLockV1X10.cluster_definition));
188
+ const clusterDefinition = yield clientInstance.getClusterDefinition(fixtures_js_1.clusterLockV1X10.cluster_definition.config_hash);
189
+ expect(clusterDefinition.config_hash).toEqual(fixtures_js_1.clusterLockV1X10.cluster_definition.config_hash);
196
190
  }));
197
191
  test('getClusterLock should return lockFile if exist', () => __awaiter(void 0, void 0, void 0, function* () {
198
192
  clientInstance['request'] = jest
199
193
  .fn()
200
- .mockReturnValue(Promise.resolve(fixtures_js_1.clusterLockV1X8));
201
- const clusterLock = yield clientInstance.getClusterLock(fixtures_js_1.clusterLockV1X8.cluster_definition.config_hash);
202
- expect(clusterLock.lock_hash).toEqual(fixtures_js_1.clusterLockV1X8.lock_hash);
194
+ .mockReturnValue(Promise.resolve(fixtures_js_1.clusterLockV1X10));
195
+ const clusterLock = yield clientInstance.getClusterLock(fixtures_js_1.clusterLockV1X10.cluster_definition.config_hash);
196
+ expect(clusterLock.lock_hash).toEqual(fixtures_js_1.clusterLockV1X10.lock_hash);
203
197
  }));
204
198
  test.each([
205
199
  { version: 'v1.6.0', clusterLock: fixtures_js_1.clusterLockV1X6 },
@@ -306,7 +300,7 @@ describe('createObolRewardsSplit', () => {
306
300
  });
307
301
  }
308
302
  catch (error) {
309
- expect(error.message).toEqual('Schema compilation errors\', must pass "validateSplitRecipients" keyword validation');
303
+ expect(error.message).toEqual('Validation failed: must pass "validateRewardsSplitRecipients" keyword validation');
310
304
  }
311
305
  }));
312
306
  test('should throw an error if ObolRAFSplit is less than 1', () => __awaiter(void 0, void 0, void 0, function* () {
@@ -319,7 +313,7 @@ describe('createObolRewardsSplit', () => {
319
313
  });
320
314
  }
321
315
  catch (error) {
322
- expect(error.message).toEqual("Schema compilation errors', must be >= 1");
316
+ expect(error.message).toEqual('Validation failed: must pass "validateRewardsSplitRecipients" keyword validation, /ObolRAFSplit must be >= 1');
323
317
  }
324
318
  }));
325
319
  it('should return the correct withdrawal and fee recipient addresses', () => __awaiter(void 0, void 0, void 0, function* () {
@@ -386,7 +380,7 @@ describe('createObolTotalSplit', () => {
386
380
  });
387
381
  }
388
382
  catch (error) {
389
- expect(error.message).toEqual('Schema compilation errors\', must pass "validateSplitRecipients" keyword validation');
383
+ expect(error.message).toEqual('Validation failed: must pass "validateTotalSplitRecipients" keyword validation');
390
384
  }
391
385
  }));
392
386
  test('should throw an error if ObolRAFSplit is less than 0.1', () => __awaiter(void 0, void 0, void 0, function* () {
@@ -397,13 +391,13 @@ describe('createObolTotalSplit', () => {
397
391
  });
398
392
  }
399
393
  catch (error) {
400
- expect(error.message).toEqual("Schema compilation errors', must be >= 0.1");
394
+ expect(error.message).toEqual('Validation failed: must pass "validateTotalSplitRecipients" keyword validation, /ObolRAFSplit must be >= 0.1');
401
395
  }
402
396
  }));
403
397
  it('should return the correct withdrawal and fee recipient addresses and ObolRAFSplit', () => __awaiter(void 0, void 0, void 0, function* () {
404
398
  const result = yield clientInstance.createObolTotalSplit({
405
399
  splitRecipients: mockSplitRecipients,
406
- ObolRAFSplit: 0.5,
400
+ ObolRAFSplit: 0.1,
407
401
  });
408
402
  // 0xPredictedAddress and not 0xSplitterAddress since were mocking isContractAvailable response to be true
409
403
  expect(result).toEqual({
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obolnetwork/obol-sdk",
3
- "version": "2.4.6",
3
+ "version": "2.5.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"
@@ -49,6 +49,8 @@
49
49
  "@safe-global/safe-core-sdk-types": "5.1.0",
50
50
  "@types/pdf-parse": "^1.1.4",
51
51
  "ajv": "^8.12.0",
52
+ "ajv-formats": "^3.0.1",
53
+ "ajv-keywords": "^5.1.0",
52
54
  "cross-fetch": "^3.1.5",
53
55
  "dotenv": "^16.4.7",
54
56
  "elliptic": "^6.5.4",
@@ -1,53 +1,79 @@
1
- import Ajv from 'ajv';
1
+ import addFormats from 'ajv-formats';
2
+ import addKeywords from 'ajv-keywords';
2
3
  import { parseUnits } from 'ethers';
4
+ import Ajv from 'ajv';
3
5
  import { DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT, DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT, } from './constants';
4
- const validDepositAmounts = (data, deposits) => {
5
- let sum = 0;
6
- // from ether togwei is same as from gwei to wei
7
- const maxDeposit = Number(parseUnits('32', 'gwei'));
8
- const minDeposit = Number(parseUnits('1', 'gwei'));
9
- for (const element of deposits) {
10
- const amountInGWei = Number(element);
11
- if (!Number.isInteger(amountInGWei) ||
12
- amountInGWei > maxDeposit ||
13
- amountInGWei < minDeposit) {
14
- return false;
15
- }
16
- sum += amountInGWei;
6
+ export const VALID_DEPOSIT_AMOUNTS = [
7
+ parseUnits('1', 'gwei').toString(),
8
+ parseUnits('32', 'gwei').toString(),
9
+ parseUnits('8', 'gwei').toString(),
10
+ parseUnits('256', 'gwei').toString(),
11
+ ];
12
+ export const VALID_NON_COMPOUNDING_AMOUNTS = [
13
+ parseUnits('1', 'gwei').toString(),
14
+ parseUnits('32', 'gwei').toString(),
15
+ ];
16
+ // They dont see defaults set in schema
17
+ const validateRewardsSplitRecipients = (_, data) => {
18
+ var _a;
19
+ const obolRAFSplit = (_a = data.ObolRAFSplit) !== null && _a !== void 0 ? _a : DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT;
20
+ const splitPercentage = data.splitRecipients.reduce((acc, curr) => acc + curr.percentAllocation, 0);
21
+ return splitPercentage + obolRAFSplit === 100;
22
+ };
23
+ const validateTotalSplitRecipients = (_, data) => {
24
+ var _a;
25
+ const obolRAFSplit = (_a = data.ObolRAFSplit) !== null && _a !== void 0 ? _a : DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT;
26
+ const splitPercentage = data.splitRecipients.reduce((acc, curr) => acc + curr.percentAllocation, 0);
27
+ return splitPercentage + obolRAFSplit === 100;
28
+ };
29
+ const validateUniqueAddresses = (_, operators) => {
30
+ if (!operators) {
31
+ return false;
17
32
  }
18
- if (sum / minDeposit !== 32) {
33
+ if (operators.length < 4) {
19
34
  return false;
20
35
  }
21
- else {
36
+ if (operators.every(op => op.address === '')) {
22
37
  return true;
23
38
  }
39
+ if (operators.some(op => op.address.length !== 42)) {
40
+ return false;
41
+ }
42
+ const addresses = operators.map(op => op.address);
43
+ const uniqueAddresses = new Set(addresses);
44
+ const isUnique = uniqueAddresses.size === addresses.length;
45
+ return isUnique;
24
46
  };
25
- const validateSplitRecipients = (_, data) => {
26
- const splitPercentage = data.splitRecipients.reduce((acc, curr) => acc + curr.percentAllocation, 0);
27
- const ObolRAFSplitParam = data.ObolRAFSplit
28
- ? data.ObolRAFSplit
29
- : 'principalRecipient' in data
30
- ? DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT
31
- : DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT;
32
- return splitPercentage + ObolRAFSplitParam === 100;
33
- };
47
+ const ajv = new Ajv({
48
+ allErrors: true,
49
+ useDefaults: true,
50
+ strict: false,
51
+ $data: true,
52
+ });
53
+ addFormats(ajv);
54
+ addKeywords(ajv, ['patternRequired']);
55
+ ajv.addKeyword({
56
+ keyword: 'validateRewardsSplitRecipients',
57
+ validate: validateRewardsSplitRecipients,
58
+ schemaType: 'boolean',
59
+ });
60
+ ajv.addKeyword({
61
+ keyword: 'validateTotalSplitRecipients',
62
+ validate: validateTotalSplitRecipients,
63
+ schemaType: 'boolean',
64
+ });
65
+ ajv.addKeyword({
66
+ keyword: 'validateUniqueAddresses',
67
+ validate: validateUniqueAddresses,
68
+ schemaType: 'boolean',
69
+ });
34
70
  export function validatePayload(data, schema) {
35
71
  var _a;
36
- const ajv = new Ajv();
37
- ajv.addKeyword({
38
- keyword: 'validDepositAmounts',
39
- validate: validDepositAmounts,
40
- errors: true,
41
- });
42
- ajv.addKeyword({
43
- keyword: 'validateSplitRecipients',
44
- validate: validateSplitRecipients,
45
- errors: true,
46
- });
47
72
  const validate = ajv.compile(schema);
48
- const isValid = validate(data);
49
- if (!isValid) {
50
- throw new Error(`Schema compilation errors', ${(_a = validate.errors) === null || _a === void 0 ? void 0 : _a[0].message}`);
73
+ const valid = validate(data);
74
+ if (!valid) {
75
+ const errors = (_a = validate.errors) === null || _a === void 0 ? void 0 : _a.map(e => `${e.instancePath} ${e.message}`).join(', ');
76
+ throw new Error(`Validation failed: ${errors}`);
51
77
  }
52
- return isValid;
78
+ return data;
53
79
  }
@@ -77,7 +77,7 @@ export const signEnrPayload = (payload, chainId) => {
77
77
  };
78
78
  };
79
79
  export const DKG_ALGORITHM = 'default';
80
- export const CONFIG_VERSION = 'v1.8.0';
80
+ export const CONFIG_VERSION = 'v1.10.0';
81
81
  export const SDK_VERSION = pjson.version;
82
82
  export const DOMAIN_APPLICATION_BUILDER = '00000001';
83
83
  export const DOMAIN_DEPOSIT = '03000000';
@@ -7,10 +7,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { ZeroAddress } from 'ethers';
11
10
  import { v4 as uuidv4 } from 'uuid';
12
11
  import { Base } from './base.js';
13
- import { CONFLICT_ERROR_MSG, CreatorConfigHashSigningTypes, Domain, DKG_ALGORITHM, CONFIG_VERSION, OperatorConfigHashSigningTypes, EnrSigningTypes, TERMS_AND_CONDITIONS_VERSION, TermsAndConditionsSigningTypes, DEFAULT_BASE_VERSION, TERMS_AND_CONDITIONS_HASH, AVAILABLE_SPLITTER_CHAINS, CHAIN_CONFIGURATION, DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT, DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT, OBOL_SDK_EMAIL, } from './constants.js';
12
+ import { CONFLICT_ERROR_MSG, CreatorConfigHashSigningTypes, Domain, DKG_ALGORITHM, CONFIG_VERSION, OperatorConfigHashSigningTypes, EnrSigningTypes, TERMS_AND_CONDITIONS_VERSION, TermsAndConditionsSigningTypes, DEFAULT_BASE_VERSION, TERMS_AND_CONDITIONS_HASH, AVAILABLE_SPLITTER_CHAINS, CHAIN_CONFIGURATION, DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT, OBOL_SDK_EMAIL, } from './constants.js';
14
13
  import { ConflictError } from './errors.js';
15
14
  import { clusterConfigOrDefinitionHash } from './verification/common.js';
16
15
  import { validatePayload } from './ajv.js';
@@ -22,6 +21,7 @@ export * from './types.js';
22
21
  export * from './services.js';
23
22
  export * from './verification/signature-validator.js';
24
23
  export * from './verification/common.js';
24
+ export * from './constants.js';
25
25
  export { Incentives } from './incentives.js';
26
26
  /**
27
27
  * Obol sdk Client can be used for creating, managing and activating distributed validators.
@@ -102,13 +102,13 @@ export class Client extends Base {
102
102
  * [createObolRewardsSplit](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L141)
103
103
  */
104
104
  // add the example reference
105
- createObolRewardsSplit({ splitRecipients, principalRecipient, etherAmount, ObolRAFSplit = DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT, distributorFee = 0, controllerAddress = ZeroAddress, recoveryAddress = ZeroAddress, }) {
105
+ createObolRewardsSplit({ splitRecipients, principalRecipient, etherAmount, ObolRAFSplit = DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT, distributorFee, controllerAddress, recoveryAddress, }) {
106
106
  return __awaiter(this, void 0, void 0, function* () {
107
107
  // This method doesnt require T&C signature
108
108
  if (!this.signer) {
109
109
  throw new Error('Signer is required in createObolRewardsSplit');
110
110
  }
111
- validatePayload({
111
+ const validatedPayload = validatePayload({
112
112
  splitRecipients,
113
113
  principalRecipient,
114
114
  etherAmount,
@@ -131,9 +131,9 @@ export class Client extends Base {
131
131
  }
132
132
  const retroActiveFundingRecipient = {
133
133
  account: CHAIN_CONFIGURATION[this.chainId].RETROACTIVE_FUNDING_ADDRESS.address,
134
- percentAllocation: ObolRAFSplit,
134
+ percentAllocation: validatedPayload.ObolRAFSplit,
135
135
  };
136
- const copiedSplitRecipients = [...splitRecipients];
136
+ const copiedSplitRecipients = [...validatedPayload.splitRecipients];
137
137
  copiedSplitRecipients.push(retroActiveFundingRecipient);
138
138
  const { accounts, percentAllocations } = formatSplitRecipients(copiedSplitRecipients);
139
139
  const predictedSplitterAddress = yield predictSplitterAddress({
@@ -141,8 +141,8 @@ export class Client extends Base {
141
141
  accounts,
142
142
  percentAllocations,
143
143
  chainId: this.chainId,
144
- distributorFee,
145
- controllerAddress,
144
+ distributorFee: validatedPayload.distributorFee,
145
+ controllerAddress: validatedPayload.controllerAddress,
146
146
  });
147
147
  const isSplitterDeployed = yield isContractAvailable(predictedSplitterAddress, this.signer.provider);
148
148
  const { withdrawal_address, fee_recipient_address } = yield handleDeployOWRAndSplitter({
@@ -151,12 +151,12 @@ export class Client extends Base {
151
151
  predictedSplitterAddress,
152
152
  accounts,
153
153
  percentAllocations,
154
- principalRecipient,
155
- etherAmount,
154
+ principalRecipient: validatedPayload.principalRecipient,
155
+ etherAmount: validatedPayload.etherAmount,
156
156
  chainId: this.chainId,
157
- distributorFee,
158
- controllerAddress,
159
- recoveryAddress,
157
+ distributorFee: validatedPayload.distributorFee,
158
+ controllerAddress: validatedPayload.controllerAddress,
159
+ recoveryAddress: validatedPayload.recoveryAddress,
160
160
  });
161
161
  return { withdrawal_address, fee_recipient_address };
162
162
  });
@@ -175,13 +175,13 @@ export class Client extends Base {
175
175
  * [createObolTotalSplit](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L168)
176
176
  */
177
177
  // add the example reference
178
- createObolTotalSplit({ splitRecipients, ObolRAFSplit = DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT, distributorFee = 0, controllerAddress = ZeroAddress, }) {
178
+ createObolTotalSplit({ splitRecipients, ObolRAFSplit, distributorFee, controllerAddress, }) {
179
179
  return __awaiter(this, void 0, void 0, function* () {
180
180
  // This method doesnt require T&C signature
181
181
  if (!this.signer) {
182
182
  throw new Error('Signer is required in createObolTotalSplit');
183
183
  }
184
- validatePayload({
184
+ const validatedPayload = validatePayload({
185
185
  splitRecipients,
186
186
  ObolRAFSplit,
187
187
  distributorFee,
@@ -197,9 +197,9 @@ export class Client extends Base {
197
197
  }
198
198
  const retroActiveFundingRecipient = {
199
199
  account: CHAIN_CONFIGURATION[this.chainId].RETROACTIVE_FUNDING_ADDRESS.address,
200
- percentAllocation: ObolRAFSplit,
200
+ percentAllocation: validatedPayload.ObolRAFSplit,
201
201
  };
202
- const copiedSplitRecipients = [...splitRecipients];
202
+ const copiedSplitRecipients = [...validatedPayload.splitRecipients];
203
203
  copiedSplitRecipients.push(retroActiveFundingRecipient);
204
204
  const { accounts, percentAllocations } = formatSplitRecipients(copiedSplitRecipients);
205
205
  const predictedSplitterAddress = yield predictSplitterAddress({
@@ -207,8 +207,8 @@ export class Client extends Base {
207
207
  accounts,
208
208
  percentAllocations,
209
209
  chainId: this.chainId,
210
- distributorFee,
211
- controllerAddress,
210
+ distributorFee: validatedPayload.distributorFee,
211
+ controllerAddress: validatedPayload.controllerAddress,
212
212
  });
213
213
  const isSplitterDeployed = yield isContractAvailable(predictedSplitterAddress, this.signer.provider);
214
214
  if (!isSplitterDeployed) {
@@ -217,8 +217,8 @@ export class Client extends Base {
217
217
  accounts,
218
218
  percentAllocations,
219
219
  chainId: this.chainId,
220
- distributorFee,
221
- controllerAddress,
220
+ distributorFee: validatedPayload.distributorFee,
221
+ controllerAddress: validatedPayload.controllerAddress,
222
222
  });
223
223
  return {
224
224
  withdrawal_address: splitterAddress,
@@ -265,10 +265,8 @@ export class Client extends Base {
265
265
  if (!this.signer) {
266
266
  throw new Error('Signer is required in createClusterDefinition');
267
267
  }
268
- validatePayload(newCluster, definitionSchema);
269
- const clusterConfig = Object.assign(Object.assign({}, newCluster), { fork_version: this.fork_version, dkg_algorithm: DKG_ALGORITHM, version: CONFIG_VERSION, uuid: uuidv4(), timestamp: new Date().toISOString(), threshold: Math.ceil((2 * newCluster.operators.length) / 3), num_validators: newCluster.validators.length, deposit_amounts: newCluster.deposit_amounts
270
- ? newCluster.deposit_amounts
271
- : ['32000000000'] });
268
+ const validatedCluster = validatePayload(newCluster, definitionSchema);
269
+ const clusterConfig = Object.assign(Object.assign({}, validatedCluster), { fork_version: this.fork_version, dkg_algorithm: DKG_ALGORITHM, version: CONFIG_VERSION, uuid: uuidv4(), timestamp: new Date().toISOString(), threshold: Math.ceil((2 * validatedCluster.operators.length) / 3), num_validators: validatedCluster.validators.length });
272
270
  try {
273
271
  const address = yield this.signer.getAddress();
274
272
  clusterConfig.creator = { address };
@@ -307,12 +305,12 @@ export class Client extends Base {
307
305
  if (!this.signer) {
308
306
  throw new Error('Signer is required in acceptClusterDefinition');
309
307
  }
310
- validatePayload(operatorPayload, operatorPayloadSchema);
308
+ const validatedPayload = validatePayload(operatorPayload, operatorPayloadSchema);
311
309
  try {
312
310
  const address = yield this.signer.getAddress();
313
311
  const operatorConfigSignature = yield this.signer.signTypedData(Domain(this.chainId), OperatorConfigHashSigningTypes, { operator_config_hash: configHash });
314
- const operatorENRSignature = yield this.signer.signTypedData(Domain(this.chainId), EnrSigningTypes, { enr: operatorPayload.enr });
315
- const operatorData = Object.assign(Object.assign({}, operatorPayload), { address, enr_signature: operatorENRSignature, fork_version: this.fork_version });
312
+ const operatorENRSignature = yield this.signer.signTypedData(Domain(this.chainId), EnrSigningTypes, { enr: validatedPayload.enr });
313
+ const operatorData = Object.assign(Object.assign({}, validatedPayload), { address, enr_signature: operatorENRSignature, fork_version: this.fork_version });
316
314
  const clusterDefinition = yield this.request(`/${DEFAULT_BASE_VERSION}/definition/${configHash}`, {
317
315
  method: 'PUT',
318
316
  body: JSON.stringify(operatorData),
@@ -1,37 +1,31 @@
1
+ import { ZeroAddress } from 'ethers';
1
2
  import { DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT, DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT, } from './constants';
3
+ import { VALID_DEPOSIT_AMOUNTS, VALID_NON_COMPOUNDING_AMOUNTS } from './ajv';
2
4
  export const operatorPayloadSchema = {
3
5
  type: 'object',
4
6
  properties: {
5
- version: {
6
- type: 'string',
7
- },
8
- enr: {
9
- type: 'string',
10
- },
7
+ version: { type: 'string' },
8
+ enr: { type: 'string' },
11
9
  },
12
10
  required: ['version', 'enr'],
13
11
  };
14
12
  export const definitionSchema = {
15
13
  type: 'object',
16
14
  properties: {
17
- name: {
18
- type: 'string',
19
- },
15
+ name: { type: 'string' },
20
16
  operators: {
21
17
  type: 'array',
22
18
  minItems: 4,
23
- uniqueItems: true,
24
19
  items: {
25
20
  type: 'object',
26
21
  properties: {
27
22
  address: {
28
23
  type: 'string',
29
- minLength: 42,
30
- maxLength: 42,
31
24
  },
32
25
  },
33
26
  required: ['address'],
34
27
  },
28
+ validateUniqueAddresses: true,
35
29
  },
36
30
  validators: {
37
31
  type: 'array',
@@ -52,12 +46,39 @@ export const definitionSchema = {
52
46
  },
53
47
  },
54
48
  deposit_amounts: {
55
- type: 'array',
49
+ type: ['array', 'null'],
56
50
  items: {
57
51
  type: 'string',
58
52
  pattern: '^[0-9]+$',
59
53
  },
60
- validDepositAmounts: true,
54
+ if: {
55
+ $data: '1/compounding',
56
+ },
57
+ then: {
58
+ items: {
59
+ enum: VALID_DEPOSIT_AMOUNTS,
60
+ },
61
+ },
62
+ else: {
63
+ items: {
64
+ enum: VALID_NON_COMPOUNDING_AMOUNTS,
65
+ },
66
+ },
67
+ default: null,
68
+ },
69
+ compounding: {
70
+ type: 'boolean',
71
+ default: true,
72
+ },
73
+ target_gas_limit: {
74
+ type: 'number',
75
+ minimum: 1,
76
+ default: 36000000,
77
+ },
78
+ consensus_protocol: {
79
+ type: 'string',
80
+ enum: ['qbft', ''],
81
+ default: '',
61
82
  },
62
83
  },
63
84
  required: ['name', 'operators', 'validators'],
@@ -74,9 +95,7 @@ export const totalSplitterPayloadSchema = {
74
95
  type: 'string',
75
96
  pattern: '^0x[a-fA-F0-9]{40}$',
76
97
  },
77
- percentAllocation: {
78
- type: 'number',
79
- },
98
+ percentAllocation: { type: 'number' },
80
99
  },
81
100
  required: ['account', 'percentAllocation'],
82
101
  },
@@ -84,29 +103,37 @@ export const totalSplitterPayloadSchema = {
84
103
  ObolRAFSplit: {
85
104
  type: 'number',
86
105
  minimum: DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT,
106
+ default: DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT,
87
107
  },
88
108
  distributorFee: {
89
109
  type: 'number',
90
110
  maximum: 10,
91
111
  multipleOf: 0.01,
112
+ default: 0,
92
113
  },
93
114
  controllerAddress: {
94
115
  type: 'string',
95
116
  pattern: '^0x[a-fA-F0-9]{40}$',
117
+ default: ZeroAddress,
96
118
  },
97
- validateSplitRecipients: true,
98
119
  },
120
+ validateTotalSplitRecipients: true,
99
121
  required: ['splitRecipients'],
100
122
  };
101
- export const rewardsSplitterPayloadSchema = Object.assign(Object.assign({}, totalSplitterPayloadSchema), { properties: Object.assign(Object.assign({}, totalSplitterPayloadSchema.properties), { ObolRAFSplit: {
123
+ export const rewardsSplitterPayloadSchema = {
124
+ type: 'object',
125
+ properties: Object.assign(Object.assign({}, totalSplitterPayloadSchema.properties), { ObolRAFSplit: {
102
126
  type: 'number',
103
127
  minimum: DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT,
128
+ default: DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT,
104
129
  }, recoveryAddress: {
105
130
  type: 'string',
106
131
  pattern: '^0x[a-fA-F0-9]{40}$',
107
- }, etherAmount: {
108
- type: 'number',
109
- }, principalRecipient: {
132
+ default: ZeroAddress,
133
+ }, etherAmount: { type: 'number' }, principalRecipient: {
110
134
  type: 'string',
111
135
  pattern: '^0x[a-fA-F0-9]{40}$',
112
- } }), required: ['splitRecipients', 'principalRecipient', 'etherAmount'] });
136
+ } }),
137
+ validateRewardsSplitRecipients: true,
138
+ required: ['splitRecipients', 'principalRecipient', 'etherAmount'],
139
+ };
@@ -141,6 +141,24 @@ export const verifyDVV1X8 = (clusterLock) => {
141
141
  for (const element of validatorPublicShares) {
142
142
  pubShares.push(fromHexString(element));
143
143
  }
144
+ // Check deposit amounts match exactly if they are defined
145
+ const depositAmounts = clusterLock.cluster_definition.deposit_amounts;
146
+ if (depositAmounts !== null) {
147
+ const partialDepositAmounts = validator.partial_deposit_data.map(d => d.amount);
148
+ if ((depositAmounts === null || depositAmounts === void 0 ? void 0 : depositAmounts.length) !== partialDepositAmounts.length) {
149
+ return false;
150
+ }
151
+ // Check that both arrays contain exactly the same elements
152
+ const sortedDepositAmounts = [...depositAmounts]
153
+ .map(Number)
154
+ .sort((a, b) => a - b);
155
+ const sortedPartialAmounts = [...partialDepositAmounts]
156
+ .map(Number)
157
+ .sort((a, b) => a - b);
158
+ if (!sortedDepositAmounts.every((amount, index) => amount === sortedPartialAmounts[index])) {
159
+ return false;
160
+ }
161
+ }
144
162
  // Deposit Data Verification
145
163
  for (const element of validator.partial_deposit_data) {
146
164
  const depositData = element;