@obolnetwork/obol-sdk 2.4.6 → 2.5.1
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.
- package/dist/cjs/package.json +3 -1
- package/dist/cjs/src/ajv.js +67 -41
- package/dist/cjs/src/constants.js +1 -1
- package/dist/cjs/src/index.js +25 -27
- package/dist/cjs/src/schema.js +50 -23
- package/dist/cjs/src/verification/v1.8.0.js +18 -0
- package/dist/cjs/test/fixtures.js +125 -17
- package/dist/cjs/test/methods.test.js +36 -42
- package/dist/esm/package.json +3 -1
- package/dist/esm/src/ajv.js +66 -40
- package/dist/esm/src/constants.js +1 -1
- package/dist/esm/src/index.js +26 -28
- package/dist/esm/src/schema.js +50 -23
- package/dist/esm/src/verification/v1.8.0.js +18 -0
- package/dist/esm/test/fixtures.js +124 -16
- package/dist/esm/test/methods.test.js +37 -43
- package/dist/types/src/ajv.d.ts +3 -2
- package/dist/types/src/constants.d.ts +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/schema.d.ts +40 -8
- package/dist/types/src/types.d.ts +8 -2
- package/dist/types/test/fixtures.d.ts +78 -11
- package/package.json +3 -1
- package/src/ajv.ts +91 -52
- package/src/constants.ts +1 -1
- package/src/index.ts +37 -35
- package/src/schema.ts +45 -22
- package/src/types.ts +11 -2
- package/src/verification/v1.8.0.ts +24 -0
|
@@ -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.
|
|
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.
|
|
73
|
+
.mockReturnValue(Promise.resolve(fixtures_js_1.clusterLockV1X10.cluster_definition));
|
|
75
74
|
const clusterDefinition = yield clientInstance.acceptClusterDefinition({
|
|
76
|
-
enr: fixtures_js_1.
|
|
77
|
-
version: fixtures_js_1.
|
|
78
|
-
}, fixtures_js_1.
|
|
79
|
-
expect(clusterDefinition).toEqual(fixtures_js_1.
|
|
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.
|
|
85
|
+
yield clientInstance.createClusterDefinition(Object.assign(Object.assign({}, fixtures_js_1.clusterConfigV1X10), { operators: [] }));
|
|
87
86
|
}
|
|
88
87
|
catch (error) {
|
|
89
|
-
expect(error.message).toEqual("
|
|
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
|
|
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('
|
|
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.
|
|
124
|
-
const clusterDefinition = yield clientInstance.getClusterDefinition(fixtures_js_1.
|
|
125
|
-
expect(
|
|
126
|
-
expect(clusterDefinition.config_hash).toEqual(fixtures_js_1.
|
|
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.
|
|
132
|
-
const clusterLock = yield clientInstance.getClusterLock(fixtures_js_1.
|
|
133
|
-
expect(clusterLock.lock_hash).toEqual(fixtures_js_1.
|
|
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.
|
|
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.
|
|
183
|
-
version: fixtures_js_1.
|
|
184
|
-
}, fixtures_js_1.
|
|
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.
|
|
194
|
-
const clusterDefinition = yield clientInstance.getClusterDefinition(fixtures_js_1.
|
|
195
|
-
expect(clusterDefinition.config_hash).toEqual(fixtures_js_1.
|
|
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.
|
|
201
|
-
const clusterLock = yield clientInstance.getClusterLock(fixtures_js_1.
|
|
202
|
-
expect(clusterLock.lock_hash).toEqual(fixtures_js_1.
|
|
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('
|
|
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("
|
|
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('
|
|
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("
|
|
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.
|
|
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({
|
package/dist/esm/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@obolnetwork/obol-sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.1",
|
|
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",
|
package/dist/esm/src/ajv.js
CHANGED
|
@@ -1,53 +1,79 @@
|
|
|
1
|
-
import
|
|
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
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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 (
|
|
33
|
+
if (operators.length < 4) {
|
|
19
34
|
return false;
|
|
20
35
|
}
|
|
21
|
-
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
49
|
-
if (!
|
|
50
|
-
|
|
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
|
|
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.
|
|
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';
|
package/dist/esm/src/index.js
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
|
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({},
|
|
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:
|
|
315
|
-
const operatorData = Object.assign(Object.assign({},
|
|
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),
|
package/dist/esm/src/schema.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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: false,
|
|
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 =
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
}, principalRecipient: {
|
|
132
|
+
default: ZeroAddress,
|
|
133
|
+
}, etherAmount: { type: 'number' }, principalRecipient: {
|
|
110
134
|
type: 'string',
|
|
111
135
|
pattern: '^0x[a-fA-F0-9]{40}$',
|
|
112
|
-
} }),
|
|
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;
|