@obolnetwork/obol-sdk 1.0.14 → 1.0.15

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obolnetwork/obol-sdk",
3
- "version": "1.0.14",
3
+ "version": "1.0.15",
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"
@@ -42,6 +42,7 @@
42
42
  "@chainsafe/discv5": "^0.5.1",
43
43
  "@chainsafe/ssz": "^0.14.0",
44
44
  "@metamask/eth-sig-util": "^7.0.1",
45
+ "@types/pdf-parse": "^1.1.4",
45
46
  "ajv": "^8.12.0",
46
47
  "cross-fetch": "^3.1.5",
47
48
  "elliptic": "^6.5.4",
@@ -49,9 +50,9 @@
49
50
  "eslint-plugin-import": "^2.29.1",
50
51
  "eslint-plugin-n": "^16.6.2",
51
52
  "eslint-plugin-promise": "^6.1.1",
52
- "ethers": "^6.4.0",
53
+ "ethers": "6.4.0",
53
54
  "nock": "^13.5.3",
54
- "release-it": "^17.1.1",
55
+ "pdf-parse": "^1.1.1",
55
56
  "semver": "^7.6.0",
56
57
  "typescript-eslint": "^7.1.0",
57
58
  "uuid": "^9.0.0"
@@ -61,11 +62,13 @@
61
62
  "@types/elliptic": "^6.4.18",
62
63
  "@types/jest": "^28.1.8",
63
64
  "@types/node": "^20.2.5",
65
+ "@types/semver": "^7.5.8",
64
66
  "@types/uuid": "^9.0.1",
65
67
  "eslint": "^8.57.0",
66
68
  "jest": "^28.1.3",
67
69
  "msw": "^2.2.1",
68
70
  "npm-run-all": "^4.1.5",
71
+ "release-it": "^17.2.1",
69
72
  "ts-jest": "^28.0.8",
70
73
  "tsup": "^6.7.0",
71
74
  "typedoc": "^0.25.7",
@@ -28,7 +28,10 @@ class Base {
28
28
  if (response.ok) {
29
29
  return yield response.json();
30
30
  }
31
- throw new Error(response.statusText);
31
+ else {
32
+ const errorResponse = yield response.json();
33
+ throw errorResponse;
34
+ }
32
35
  }
33
36
  catch (e) {
34
37
  throw e;
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.ETHER_TO_GWEI = exports.DEFAULT_CHAIN_ID = exports.DEFAULT_BASE_URL = exports.DefinitionFlow = exports.GENESIS_VALIDATOR_ROOT = exports.DOMAIN_DEPOSIT = exports.DOMAIN_APPLICATION_BUILDER = exports.SDK_VERSION = exports.CONFIG_VERSION = exports.DKG_ALGORITHM = exports.signEnrPayload = exports.signOperatorConfigHashPayload = exports.signCreatorConfigHashPayload = exports.ENRTypedMessage = exports.OperatorTypedMessage = exports.OperatorConfigHashSigningTypes = exports.EnrSigningTypes = exports.CreatorTypedMessage = exports.Domain = exports.CreatorConfigHashSigningTypes = exports.EIP712_DOMAIN_VERSION = exports.EIP712_DOMAIN_NAME = exports.CONFLICT_ERROR_MSG = void 0;
26
+ exports.TERMS_AND_CONDITIONS_URL = exports.TERMS_AND_CONDITIONS_VERSION = exports.ETHER_TO_GWEI = exports.DEFAULT_CHAIN_ID = exports.DEFAULT_BASE_URL = exports.DefinitionFlow = exports.GENESIS_VALIDATOR_ROOT = exports.DOMAIN_DEPOSIT = exports.DOMAIN_APPLICATION_BUILDER = exports.SDK_VERSION = exports.CONFIG_VERSION = exports.DKG_ALGORITHM = exports.signEnrPayload = exports.signOperatorConfigHashPayload = exports.signCreatorConfigHashPayload = exports.ENRTypedMessage = exports.OperatorTypedMessage = exports.OperatorConfigHashSigningTypes = exports.EnrSigningTypes = exports.CreatorTypedMessage = exports.Domain = exports.TermsAndConditionsSigningTypes = exports.CreatorConfigHashSigningTypes = exports.EIP712_DOMAIN_VERSION = exports.EIP712_DOMAIN_NAME = exports.CONFLICT_ERROR_MSG = void 0;
27
27
  const pjson = __importStar(require("../package.json"));
28
28
  exports.CONFLICT_ERROR_MSG = 'Conflict';
29
29
  exports.EIP712_DOMAIN_NAME = 'Obol';
@@ -31,17 +31,26 @@ exports.EIP712_DOMAIN_VERSION = '1';
31
31
  exports.CreatorConfigHashSigningTypes = {
32
32
  CreatorConfigHash: [{ name: 'creator_config_hash', type: 'string' }],
33
33
  };
34
+ exports.TermsAndConditionsSigningTypes = {
35
+ TermsAndConditions: [
36
+ { name: 'terms_and_conditions_hash', type: 'string' },
37
+ { name: 'version', type: 'uint256' },
38
+ ]
39
+ };
34
40
  const EIP712Domain = [
35
41
  { name: 'name', type: 'string' },
36
42
  { name: 'version', type: 'string' },
37
43
  { name: 'chainId', type: 'uint256' },
38
44
  ];
39
45
  const Domain = (chainId) => {
40
- return {
46
+ const typeDataDomain = {
41
47
  name: exports.EIP712_DOMAIN_NAME,
42
48
  version: exports.EIP712_DOMAIN_VERSION,
43
- chainId,
44
49
  };
50
+ if (chainId) {
51
+ typeDataDomain.chainId = chainId;
52
+ }
53
+ return typeDataDomain;
45
54
  };
46
55
  exports.Domain = Domain;
47
56
  exports.CreatorTypedMessage = Object.assign({ EIP712Domain }, exports.CreatorConfigHashSigningTypes);
@@ -99,7 +108,7 @@ exports.SDK_VERSION = pjson.version;
99
108
  exports.DOMAIN_APPLICATION_BUILDER = '00000001';
100
109
  exports.DOMAIN_DEPOSIT = '03000000';
101
110
  exports.GENESIS_VALIDATOR_ROOT = '0000000000000000000000000000000000000000000000000000000000000000';
102
- // Flow used to create defintion
111
+ // Flow used to create definition
103
112
  var DefinitionFlow;
104
113
  (function (DefinitionFlow) {
105
114
  DefinitionFlow["Group"] = "LP-Group";
@@ -109,3 +118,5 @@ var DefinitionFlow;
109
118
  exports.DEFAULT_BASE_URL = 'https://api.obol.tech';
110
119
  exports.DEFAULT_CHAIN_ID = 17000;
111
120
  exports.ETHER_TO_GWEI = 10 ** 9;
121
+ exports.TERMS_AND_CONDITIONS_VERSION = 1;
122
+ exports.TERMS_AND_CONDITIONS_URL = (exports.TERMS_AND_CONDITIONS_VERSION === 1) ? 'https://obol.tech/terms.pdf' : `https://obol.tech/${exports.TERMS_AND_CONDITIONS_VERSION}/terms.pdf`;
@@ -31,6 +31,7 @@ const errors_js_1 = require("./errors.js");
31
31
  const common_js_1 = require("./verification/common.js");
32
32
  const ajv_js_1 = require("./ajv.js");
33
33
  const schema_js_1 = require("./schema.js");
34
+ const termsAndConditions_js_1 = require("./verification/termsAndConditions.js");
34
35
  __exportStar(require("./types.js"), exports);
35
36
  __exportStar(require("./services.js"), exports);
36
37
  /**
@@ -51,6 +52,46 @@ class Client extends base_js_1.Base {
51
52
  super(config);
52
53
  this.signer = signer;
53
54
  }
55
+ /**
56
+ * Accepts Obol terms and conditions to be able to create or update data.
57
+ * @returns {Promise<string>} terms and conditions acceptance success message.
58
+ * @throws On unverified signature or wrong hash.
59
+ *
60
+ */
61
+ acceptObolLatestTermsAndConditions() {
62
+ return __awaiter(this, void 0, void 0, function* () {
63
+ if (!this.signer) {
64
+ throw new Error('Signer is required in acceptObolTermsAndConditions');
65
+ }
66
+ try {
67
+ const termsAndConditionsHash = yield (0, termsAndConditions_js_1.hashTermsAndConditions)();
68
+ const address = yield this.signer.getAddress();
69
+ const termsAndConditionsPayload = {
70
+ address,
71
+ version: constants_js_1.TERMS_AND_CONDITIONS_VERSION,
72
+ terms_and_conditions_hash: termsAndConditionsHash
73
+ };
74
+ const termsAndConditionsSignature = yield this.signer.signTypedData((0, constants_js_1.Domain)(), constants_js_1.TermsAndConditionsSigningTypes, {
75
+ terms_and_conditions_hash: termsAndConditionsHash,
76
+ version: constants_js_1.TERMS_AND_CONDITIONS_VERSION,
77
+ });
78
+ const termsAndConditionsResponse = yield this.request('/termsAndConditions', {
79
+ method: 'POST',
80
+ body: JSON.stringify(termsAndConditionsPayload),
81
+ headers: {
82
+ Authorization: `Bearer ${termsAndConditionsSignature}`,
83
+ },
84
+ });
85
+ return termsAndConditionsResponse === null || termsAndConditionsResponse === void 0 ? void 0 : termsAndConditionsResponse.message;
86
+ }
87
+ catch (err) {
88
+ if ((err === null || err === void 0 ? void 0 : err.message) === constants_js_1.CONFLICT_ERROR_MSG) {
89
+ throw new errors_js_1.ConflictError();
90
+ }
91
+ throw err;
92
+ }
93
+ });
94
+ }
54
95
  /**
55
96
  * Creates a cluster definition which contains cluster configuration.
56
97
  * @param {ClusterPayload} newCluster - The new unique cluster.
@@ -72,7 +113,7 @@ class Client extends base_js_1.Base {
72
113
  clusterConfig.creator = { address };
73
114
  clusterConfig.config_hash = (0, common_js_1.clusterConfigOrDefinitionHash)(clusterConfig, true);
74
115
  const creatorConfigSignature = yield this.signer.signTypedData((0, constants_js_1.Domain)(this.chainId), constants_js_1.CreatorConfigHashSigningTypes, { creator_config_hash: clusterConfig.config_hash });
75
- const clusterDefinition = yield this.request('/dv', {
116
+ const clusterDefinition = yield this.request('/v1/definition', {
76
117
  method: 'POST',
77
118
  body: JSON.stringify(clusterConfig),
78
119
  headers: {
@@ -94,7 +135,7 @@ class Client extends base_js_1.Base {
94
135
  * Approves joining a cluster with specific configuration.
95
136
  * @param {OperatorPayload} operatorPayload - The operator data including signatures.
96
137
  * @param {string} configHash - The config hash of the cluster which the operator confirms joining to.
97
- * @returns {Promise<ClusterDefintion>} The cluster definition.
138
+ * @returns {Promise<ClusterDefinition>} The cluster definition.
98
139
  * @throws On unauthorized, duplicate entries, missing keys, not found cluster or invalid data.
99
140
  *
100
141
  * An example of how to use acceptClusterDefinition:
@@ -111,7 +152,7 @@ class Client extends base_js_1.Base {
111
152
  const operatorConfigSignature = yield this.signer.signTypedData((0, constants_js_1.Domain)(this.chainId), constants_js_1.OperatorConfigHashSigningTypes, { operator_config_hash: configHash });
112
153
  const operatorENRSignature = yield this.signer.signTypedData((0, constants_js_1.Domain)(this.chainId), constants_js_1.EnrSigningTypes, { enr: operatorPayload.enr });
113
154
  const operatorData = Object.assign(Object.assign({}, operatorPayload), { address, enr_signature: operatorENRSignature, fork_version: this.fork_version });
114
- const clusterDefinition = yield this.request(`/dv/${configHash}`, {
155
+ const clusterDefinition = yield this.request(`/v1/definition/${configHash}`, {
115
156
  method: 'PUT',
116
157
  body: JSON.stringify(operatorData),
117
158
  headers: {
@@ -127,7 +168,7 @@ class Client extends base_js_1.Base {
127
168
  }
128
169
  /**
129
170
  * @param configHash - The configuration hash returned in createClusterDefinition
130
- * @returns {Promise<ClusterDefintion>} The cluster definition for config hash
171
+ * @returns {Promise<ClusterDefinition>} The cluster definition for config hash
131
172
  * @throws On not found config hash.
132
173
  *
133
174
  * An example of how to use getClusterDefinition:
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.hashTermsAndConditions = void 0;
16
+ const pdf_parse_1 = __importDefault(require("pdf-parse"));
17
+ const ssz_1 = require("@chainsafe/ssz");
18
+ const constants_1 = require("../constants");
19
+ const utils_1 = require("../utils");
20
+ const hashTermsAndConditions = () => __awaiter(void 0, void 0, void 0, function* () {
21
+ try {
22
+ // read the pdf
23
+ const response = yield fetch(constants_1.TERMS_AND_CONDITIONS_URL);
24
+ const pdfBuffarrayBuffer = yield response.arrayBuffer();
25
+ const pdfBuffer = Buffer.from(pdfBuffarrayBuffer);
26
+ const data = yield (0, pdf_parse_1.default)(pdfBuffer);
27
+ // ssz hash
28
+ const termsType = new ssz_1.ContainerType({
29
+ terms_and_conditions_hash: new ssz_1.ByteListType(Number.MAX_SAFE_INTEGER),
30
+ });
31
+ const termsHasVal = termsType.defaultValue();
32
+ termsHasVal.terms_and_conditions_hash = (0, utils_1.strToUint8Array)(data === null || data === void 0 ? void 0 : data.text);
33
+ return ('0x' +
34
+ Buffer.from(termsType.hashTreeRoot(termsHasVal).buffer).toString('hex'));
35
+ }
36
+ catch (err) {
37
+ return null;
38
+ }
39
+ });
40
+ exports.hashTermsAndConditions = hashTermsAndConditions;
@@ -17,6 +17,7 @@ const base_1 = require("../src/base");
17
17
  const ajv_1 = require("../src/ajv");
18
18
  const msw_1 = require("msw");
19
19
  const node_1 = require("msw/node");
20
+ const termsAndConditions_1 = require("../src/verification/termsAndConditions");
20
21
  /* eslint no-new: 0 */
21
22
  describe('Cluster Client', () => {
22
23
  var _a, _b;
@@ -33,6 +34,13 @@ describe('Cluster Client', () => {
33
34
  // expect(error.message).toBe('Obol-SDK is in Beta phase, mainnet is not yet supported')
34
35
  // }
35
36
  // })
37
+ test('createTermsAndConditions should return "successful authorization"', () => __awaiter(void 0, void 0, void 0, function* () {
38
+ clientInstance['request'] = jest
39
+ .fn()
40
+ .mockReturnValue(Promise.resolve({ message: 'successful authorization' }));
41
+ const isAuthorized = yield clientInstance.acceptObolLatestTermsAndConditions();
42
+ expect(isAuthorized).toEqual('successful authorization');
43
+ }));
36
44
  test('createClusterDefinition should return config_hash', () => __awaiter(void 0, void 0, void 0, function* () {
37
45
  clientInstance['request'] = jest
38
46
  .fn()
@@ -148,4 +156,8 @@ describe('Cluster Client without a signer', () => {
148
156
  const isValidLock = yield (0, index_1.validateClusterLock)(clusterLock);
149
157
  expect(isValidLock).toEqual(true);
150
158
  }));
159
+ test('Finds the hash of the latest version of terms and conditions', () => __awaiter(void 0, void 0, void 0, function* () {
160
+ const termsAndConditionsHash = yield (0, termsAndConditions_1.hashTermsAndConditions)();
161
+ expect(termsAndConditionsHash).toEqual('0x0015d01710b4d864004320a4826f6846c2db12a7f056a126fa5ae16c0b5e2b65');
162
+ }));
151
163
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obolnetwork/obol-sdk",
3
- "version": "1.0.14",
3
+ "version": "1.0.15",
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"
@@ -42,6 +42,7 @@
42
42
  "@chainsafe/discv5": "^0.5.1",
43
43
  "@chainsafe/ssz": "^0.14.0",
44
44
  "@metamask/eth-sig-util": "^7.0.1",
45
+ "@types/pdf-parse": "^1.1.4",
45
46
  "ajv": "^8.12.0",
46
47
  "cross-fetch": "^3.1.5",
47
48
  "elliptic": "^6.5.4",
@@ -49,9 +50,9 @@
49
50
  "eslint-plugin-import": "^2.29.1",
50
51
  "eslint-plugin-n": "^16.6.2",
51
52
  "eslint-plugin-promise": "^6.1.1",
52
- "ethers": "^6.4.0",
53
+ "ethers": "6.4.0",
53
54
  "nock": "^13.5.3",
54
- "release-it": "^17.1.1",
55
+ "pdf-parse": "^1.1.1",
55
56
  "semver": "^7.6.0",
56
57
  "typescript-eslint": "^7.1.0",
57
58
  "uuid": "^9.0.0"
@@ -61,11 +62,13 @@
61
62
  "@types/elliptic": "^6.4.18",
62
63
  "@types/jest": "^28.1.8",
63
64
  "@types/node": "^20.2.5",
65
+ "@types/semver": "^7.5.8",
64
66
  "@types/uuid": "^9.0.1",
65
67
  "eslint": "^8.57.0",
66
68
  "jest": "^28.1.3",
67
69
  "msw": "^2.2.1",
68
70
  "npm-run-all": "^4.1.5",
71
+ "release-it": "^17.2.1",
69
72
  "ts-jest": "^28.0.8",
70
73
  "tsup": "^6.7.0",
71
74
  "typedoc": "^0.25.7",
@@ -25,7 +25,10 @@ export class Base {
25
25
  if (response.ok) {
26
26
  return yield response.json();
27
27
  }
28
- throw new Error(response.statusText);
28
+ else {
29
+ const errorResponse = yield response.json();
30
+ throw errorResponse;
31
+ }
29
32
  }
30
33
  catch (e) {
31
34
  throw e;
@@ -5,17 +5,26 @@ export const EIP712_DOMAIN_VERSION = '1';
5
5
  export const CreatorConfigHashSigningTypes = {
6
6
  CreatorConfigHash: [{ name: 'creator_config_hash', type: 'string' }],
7
7
  };
8
+ export const TermsAndConditionsSigningTypes = {
9
+ TermsAndConditions: [
10
+ { name: 'terms_and_conditions_hash', type: 'string' },
11
+ { name: 'version', type: 'uint256' },
12
+ ]
13
+ };
8
14
  const EIP712Domain = [
9
15
  { name: 'name', type: 'string' },
10
16
  { name: 'version', type: 'string' },
11
17
  { name: 'chainId', type: 'uint256' },
12
18
  ];
13
19
  export const Domain = (chainId) => {
14
- return {
20
+ const typeDataDomain = {
15
21
  name: EIP712_DOMAIN_NAME,
16
22
  version: EIP712_DOMAIN_VERSION,
17
- chainId,
18
23
  };
24
+ if (chainId) {
25
+ typeDataDomain.chainId = chainId;
26
+ }
27
+ return typeDataDomain;
19
28
  };
20
29
  export const CreatorTypedMessage = Object.assign({ EIP712Domain }, CreatorConfigHashSigningTypes);
21
30
  // A conflict once updateDefinition is merged
@@ -69,7 +78,7 @@ export const SDK_VERSION = pjson.version;
69
78
  export const DOMAIN_APPLICATION_BUILDER = '00000001';
70
79
  export const DOMAIN_DEPOSIT = '03000000';
71
80
  export const GENESIS_VALIDATOR_ROOT = '0000000000000000000000000000000000000000000000000000000000000000';
72
- // Flow used to create defintion
81
+ // Flow used to create definition
73
82
  export var DefinitionFlow;
74
83
  (function (DefinitionFlow) {
75
84
  DefinitionFlow["Group"] = "LP-Group";
@@ -79,3 +88,5 @@ export var DefinitionFlow;
79
88
  export const DEFAULT_BASE_URL = 'https://api.obol.tech';
80
89
  export const DEFAULT_CHAIN_ID = 17000;
81
90
  export const ETHER_TO_GWEI = 10 ** 9;
91
+ export const TERMS_AND_CONDITIONS_VERSION = 1;
92
+ export const TERMS_AND_CONDITIONS_URL = (TERMS_AND_CONDITIONS_VERSION === 1) ? 'https://obol.tech/terms.pdf' : `https://obol.tech/${TERMS_AND_CONDITIONS_VERSION}/terms.pdf`;
@@ -9,11 +9,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { v4 as uuidv4 } from 'uuid';
11
11
  import { Base } from './base.js';
12
- import { CONFLICT_ERROR_MSG, CreatorConfigHashSigningTypes, Domain, DKG_ALGORITHM, CONFIG_VERSION, OperatorConfigHashSigningTypes, EnrSigningTypes, } from './constants.js';
12
+ import { CONFLICT_ERROR_MSG, CreatorConfigHashSigningTypes, Domain, DKG_ALGORITHM, CONFIG_VERSION, OperatorConfigHashSigningTypes, EnrSigningTypes, TERMS_AND_CONDITIONS_VERSION, TermsAndConditionsSigningTypes, } from './constants.js';
13
13
  import { ConflictError } from './errors.js';
14
14
  import { clusterConfigOrDefinitionHash } from './verification/common.js';
15
15
  import { validatePayload } from './ajv.js';
16
16
  import { definitionSchema, operatorPayloadSchema } from './schema.js';
17
+ import { hashTermsAndConditions } from './verification/termsAndConditions.js';
17
18
  export * from './types.js';
18
19
  export * from './services.js';
19
20
  /**
@@ -34,6 +35,46 @@ export class Client extends Base {
34
35
  super(config);
35
36
  this.signer = signer;
36
37
  }
38
+ /**
39
+ * Accepts Obol terms and conditions to be able to create or update data.
40
+ * @returns {Promise<string>} terms and conditions acceptance success message.
41
+ * @throws On unverified signature or wrong hash.
42
+ *
43
+ */
44
+ acceptObolLatestTermsAndConditions() {
45
+ return __awaiter(this, void 0, void 0, function* () {
46
+ if (!this.signer) {
47
+ throw new Error('Signer is required in acceptObolTermsAndConditions');
48
+ }
49
+ try {
50
+ const termsAndConditionsHash = yield hashTermsAndConditions();
51
+ const address = yield this.signer.getAddress();
52
+ const termsAndConditionsPayload = {
53
+ address,
54
+ version: TERMS_AND_CONDITIONS_VERSION,
55
+ terms_and_conditions_hash: termsAndConditionsHash
56
+ };
57
+ const termsAndConditionsSignature = yield this.signer.signTypedData(Domain(), TermsAndConditionsSigningTypes, {
58
+ terms_and_conditions_hash: termsAndConditionsHash,
59
+ version: TERMS_AND_CONDITIONS_VERSION,
60
+ });
61
+ const termsAndConditionsResponse = yield this.request('/termsAndConditions', {
62
+ method: 'POST',
63
+ body: JSON.stringify(termsAndConditionsPayload),
64
+ headers: {
65
+ Authorization: `Bearer ${termsAndConditionsSignature}`,
66
+ },
67
+ });
68
+ return termsAndConditionsResponse === null || termsAndConditionsResponse === void 0 ? void 0 : termsAndConditionsResponse.message;
69
+ }
70
+ catch (err) {
71
+ if ((err === null || err === void 0 ? void 0 : err.message) === CONFLICT_ERROR_MSG) {
72
+ throw new ConflictError();
73
+ }
74
+ throw err;
75
+ }
76
+ });
77
+ }
37
78
  /**
38
79
  * Creates a cluster definition which contains cluster configuration.
39
80
  * @param {ClusterPayload} newCluster - The new unique cluster.
@@ -55,7 +96,7 @@ export class Client extends Base {
55
96
  clusterConfig.creator = { address };
56
97
  clusterConfig.config_hash = clusterConfigOrDefinitionHash(clusterConfig, true);
57
98
  const creatorConfigSignature = yield this.signer.signTypedData(Domain(this.chainId), CreatorConfigHashSigningTypes, { creator_config_hash: clusterConfig.config_hash });
58
- const clusterDefinition = yield this.request('/dv', {
99
+ const clusterDefinition = yield this.request('/v1/definition', {
59
100
  method: 'POST',
60
101
  body: JSON.stringify(clusterConfig),
61
102
  headers: {
@@ -77,7 +118,7 @@ export class Client extends Base {
77
118
  * Approves joining a cluster with specific configuration.
78
119
  * @param {OperatorPayload} operatorPayload - The operator data including signatures.
79
120
  * @param {string} configHash - The config hash of the cluster which the operator confirms joining to.
80
- * @returns {Promise<ClusterDefintion>} The cluster definition.
121
+ * @returns {Promise<ClusterDefinition>} The cluster definition.
81
122
  * @throws On unauthorized, duplicate entries, missing keys, not found cluster or invalid data.
82
123
  *
83
124
  * An example of how to use acceptClusterDefinition:
@@ -94,7 +135,7 @@ export class Client extends Base {
94
135
  const operatorConfigSignature = yield this.signer.signTypedData(Domain(this.chainId), OperatorConfigHashSigningTypes, { operator_config_hash: configHash });
95
136
  const operatorENRSignature = yield this.signer.signTypedData(Domain(this.chainId), EnrSigningTypes, { enr: operatorPayload.enr });
96
137
  const operatorData = Object.assign(Object.assign({}, operatorPayload), { address, enr_signature: operatorENRSignature, fork_version: this.fork_version });
97
- const clusterDefinition = yield this.request(`/dv/${configHash}`, {
138
+ const clusterDefinition = yield this.request(`/v1/definition/${configHash}`, {
98
139
  method: 'PUT',
99
140
  body: JSON.stringify(operatorData),
100
141
  headers: {
@@ -110,7 +151,7 @@ export class Client extends Base {
110
151
  }
111
152
  /**
112
153
  * @param configHash - The configuration hash returned in createClusterDefinition
113
- * @returns {Promise<ClusterDefintion>} The cluster definition for config hash
154
+ * @returns {Promise<ClusterDefinition>} The cluster definition for config hash
114
155
  * @throws On not found config hash.
115
156
  *
116
157
  * An example of how to use getClusterDefinition:
@@ -0,0 +1,33 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import pdf from 'pdf-parse';
11
+ import { ByteListType, ContainerType } from '@chainsafe/ssz';
12
+ import { TERMS_AND_CONDITIONS_URL } from '../constants';
13
+ import { strToUint8Array } from '../utils';
14
+ export const hashTermsAndConditions = () => __awaiter(void 0, void 0, void 0, function* () {
15
+ try {
16
+ // read the pdf
17
+ const response = yield fetch(TERMS_AND_CONDITIONS_URL);
18
+ const pdfBuffarrayBuffer = yield response.arrayBuffer();
19
+ const pdfBuffer = Buffer.from(pdfBuffarrayBuffer);
20
+ const data = yield pdf(pdfBuffer);
21
+ // ssz hash
22
+ const termsType = new ContainerType({
23
+ terms_and_conditions_hash: new ByteListType(Number.MAX_SAFE_INTEGER),
24
+ });
25
+ const termsHasVal = termsType.defaultValue();
26
+ termsHasVal.terms_and_conditions_hash = strToUint8Array(data === null || data === void 0 ? void 0 : data.text);
27
+ return ('0x' +
28
+ Buffer.from(termsType.hashTreeRoot(termsHasVal).buffer).toString('hex'));
29
+ }
30
+ catch (err) {
31
+ return null;
32
+ }
33
+ });
@@ -15,6 +15,7 @@ import { Base } from '../src/base';
15
15
  import { validatePayload } from '../src/ajv';
16
16
  import { HttpResponse, http } from 'msw';
17
17
  import { setupServer } from 'msw/node';
18
+ import { hashTermsAndConditions } from '../src/verification/termsAndConditions';
18
19
  /* eslint no-new: 0 */
19
20
  describe('Cluster Client', () => {
20
21
  var _a, _b;
@@ -31,6 +32,13 @@ describe('Cluster Client', () => {
31
32
  // expect(error.message).toBe('Obol-SDK is in Beta phase, mainnet is not yet supported')
32
33
  // }
33
34
  // })
35
+ test('createTermsAndConditions should return "successful authorization"', () => __awaiter(void 0, void 0, void 0, function* () {
36
+ clientInstance['request'] = jest
37
+ .fn()
38
+ .mockReturnValue(Promise.resolve({ message: 'successful authorization' }));
39
+ const isAuthorized = yield clientInstance.acceptObolLatestTermsAndConditions();
40
+ expect(isAuthorized).toEqual('successful authorization');
41
+ }));
34
42
  test('createClusterDefinition should return config_hash', () => __awaiter(void 0, void 0, void 0, function* () {
35
43
  clientInstance['request'] = jest
36
44
  .fn()
@@ -146,4 +154,8 @@ describe('Cluster Client without a signer', () => {
146
154
  const isValidLock = yield validateClusterLock(clusterLock);
147
155
  expect(isValidLock).toEqual(true);
148
156
  }));
157
+ test('Finds the hash of the latest version of terms and conditions', () => __awaiter(void 0, void 0, void 0, function* () {
158
+ const termsAndConditionsHash = yield hashTermsAndConditions();
159
+ expect(termsAndConditionsHash).toEqual('0x0015d01710b4d864004320a4826f6846c2db12a7f056a126fa5ae16c0b5e2b65');
160
+ }));
149
161
  });
@@ -9,7 +9,13 @@ export declare const CreatorConfigHashSigningTypes: {
9
9
  type: string;
10
10
  }[];
11
11
  };
12
- export declare const Domain: (chainId: number) => TypedDataDomain;
12
+ export declare const TermsAndConditionsSigningTypes: {
13
+ TermsAndConditions: {
14
+ name: string;
15
+ type: string;
16
+ }[];
17
+ };
18
+ export declare const Domain: (chainId?: number) => TypedDataDomain;
13
19
  export declare const CreatorTypedMessage: {
14
20
  CreatorConfigHash: {
15
21
  name: string;
@@ -75,3 +81,5 @@ export declare enum DefinitionFlow {
75
81
  export declare const DEFAULT_BASE_URL = "https://api.obol.tech";
76
82
  export declare const DEFAULT_CHAIN_ID = 17000;
77
83
  export declare const ETHER_TO_GWEI: number;
84
+ export declare const TERMS_AND_CONDITIONS_VERSION = 1;
85
+ export declare const TERMS_AND_CONDITIONS_URL: string;
@@ -1,6 +1,6 @@
1
1
  import { type Signer } from 'ethers';
2
2
  import { Base } from './base.js';
3
- import { type ClusterDefintion, type ClusterLock, type ClusterPayload, type OperatorPayload } from './types.js';
3
+ import { type ClusterDefinition, type ClusterLock, type ClusterPayload, type OperatorPayload } from './types.js';
4
4
  export * from './types.js';
5
5
  export * from './services.js';
6
6
  /**
@@ -22,6 +22,13 @@ export declare class Client extends Base {
22
22
  baseUrl?: string;
23
23
  chainId?: number;
24
24
  }, signer?: Signer);
25
+ /**
26
+ * Accepts Obol terms and conditions to be able to create or update data.
27
+ * @returns {Promise<string>} terms and conditions acceptance success message.
28
+ * @throws On unverified signature or wrong hash.
29
+ *
30
+ */
31
+ acceptObolLatestTermsAndConditions(): Promise<string>;
25
32
  /**
26
33
  * Creates a cluster definition which contains cluster configuration.
27
34
  * @param {ClusterPayload} newCluster - The new unique cluster.
@@ -36,22 +43,22 @@ export declare class Client extends Base {
36
43
  * Approves joining a cluster with specific configuration.
37
44
  * @param {OperatorPayload} operatorPayload - The operator data including signatures.
38
45
  * @param {string} configHash - The config hash of the cluster which the operator confirms joining to.
39
- * @returns {Promise<ClusterDefintion>} The cluster definition.
46
+ * @returns {Promise<ClusterDefinition>} The cluster definition.
40
47
  * @throws On unauthorized, duplicate entries, missing keys, not found cluster or invalid data.
41
48
  *
42
49
  * An example of how to use acceptClusterDefinition:
43
50
  * [acceptClusterDefinition](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts)
44
51
  */
45
- acceptClusterDefinition(operatorPayload: OperatorPayload, configHash: string): Promise<ClusterDefintion>;
52
+ acceptClusterDefinition(operatorPayload: OperatorPayload, configHash: string): Promise<ClusterDefinition>;
46
53
  /**
47
54
  * @param configHash - The configuration hash returned in createClusterDefinition
48
- * @returns {Promise<ClusterDefintion>} The cluster definition for config hash
55
+ * @returns {Promise<ClusterDefinition>} The cluster definition for config hash
49
56
  * @throws On not found config hash.
50
57
  *
51
58
  * An example of how to use getClusterDefinition:
52
59
  * [getObolClusterDefinition](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts)
53
60
  */
54
- getClusterDefinition(configHash: string): Promise<ClusterDefintion>;
61
+ getClusterDefinition(configHash: string): Promise<ClusterDefinition>;
55
62
  /**
56
63
  * @param configHash - The configuration hash in cluster-definition
57
64
  * @returns {Promise<ClusterLock>} The matched cluster details (lock) from DB
@@ -66,7 +66,7 @@ export interface ClusterPayload {
66
66
  /**
67
67
  * Cluster definition data needed for dkg
68
68
  */
69
- export interface ClusterDefintion extends ClusterPayload {
69
+ export interface ClusterDefinition extends ClusterPayload {
70
70
  /** The creator of the cluster. */
71
71
  creator: ClusterCreator;
72
72
  /** The cluster configuration version. */
@@ -147,7 +147,7 @@ export interface DistributedValidator {
147
147
  */
148
148
  export interface ClusterLock {
149
149
  /** The cluster definition. */
150
- cluster_definition: ClusterDefintion;
150
+ cluster_definition: ClusterDefinition;
151
151
  /** The cluster distributed validators. */
152
152
  distributed_validators: DistributedValidator[];
153
153
  /** The cluster bls signature aggregate. */
@@ -1,5 +1,5 @@
1
1
  import { DefinitionFlow } from './constants';
2
- import { type ClusterDefintion } from './types';
2
+ import { type ClusterDefinition } from './types';
3
3
  export declare const hexWithout0x: (hex: string) => string;
4
4
  export declare const strToUint8Array: (str: string) => Uint8Array;
5
- export declare const definitionFlow: (clusterDefinition: ClusterDefintion) => DefinitionFlow | null;
5
+ export declare const definitionFlow: (clusterDefinition: ClusterDefinition) => DefinitionFlow | null;
@@ -1,11 +1,11 @@
1
1
  /// <reference types="node" />
2
- import { type ClusterDefintion, type ClusterLock, type DepositData, type DistributedValidator } from '../types.js';
2
+ import { type ClusterDefinition, type ClusterLock, type DepositData, type DistributedValidator } from '../types.js';
3
3
  /**
4
4
  * @param cluster The cluster configuration or the cluster definition
5
5
  * @param configOnly a boolean to indicate config hash or definition hash
6
6
  * @returns The config hash or the definition hash in of the corresponding cluster
7
7
  */
8
- export declare const clusterConfigOrDefinitionHash: (cluster: ClusterDefintion, configOnly: boolean) => string;
8
+ export declare const clusterConfigOrDefinitionHash: (cluster: ClusterDefinition, configOnly: boolean) => string;
9
9
  /**
10
10
  * Returns the SSZ cluster lock hash of the given cluster lock object
11
11
  * @param cluster The cluster lock whose lock hash needs to be calculated
@@ -0,0 +1 @@
1
+ export declare const hashTermsAndConditions: () => Promise<string | null>;
@@ -2,7 +2,7 @@ import { UintNumberType } from '@chainsafe/ssz/lib/type/uint';
2
2
  import { type creatorAddressWrapperType, type creatorContainerType, type operatorAddressWrapperType, type operatorContainerType, validatorsContainerType } from './sszTypes';
3
3
  import { ByteListType, ByteVectorType, ContainerType, ListCompositeType } from '@chainsafe/ssz';
4
4
  import { type ValueOfFields } from '@chainsafe/ssz/lib/view/container';
5
- import { type ClusterDefintion, type ClusterLock } from '../types';
5
+ import { type ClusterDefinition, type ClusterLock } from '../types';
6
6
  type DefinitionFieldsV1X6 = {
7
7
  uuid: ByteListType;
8
8
  name: ByteListType;
@@ -24,7 +24,7 @@ type DefinitionContainerTypeV1X6 = ContainerType<DefinitionFieldsV1X6>;
24
24
  * @returns SSZ Containerized type of cluster input
25
25
  */
26
26
  export declare const clusterDefinitionContainerTypeV1X6: (configOnly: boolean) => DefinitionContainerTypeV1X6;
27
- export declare const hashClusterDefinitionV1X6: (cluster: ClusterDefintion, configOnly: boolean) => ValueOfFields<DefinitionFieldsV1X6>;
27
+ export declare const hashClusterDefinitionV1X6: (cluster: ClusterDefinition, configOnly: boolean) => ValueOfFields<DefinitionFieldsV1X6>;
28
28
  /**
29
29
  * @param cluster The published cluster lock
30
30
  * @returns The lock hash in of the corresponding cluster
@@ -2,7 +2,7 @@ import { UintNumberType } from '@chainsafe/ssz/lib/type/uint';
2
2
  import { type creatorAddressWrapperType, type creatorContainerType, type operatorAddressWrapperType, type operatorContainerType, validatorsContainerType } from './sszTypes';
3
3
  import { ByteListType, ByteVectorType, ContainerType, ListCompositeType } from '@chainsafe/ssz';
4
4
  import { type ValueOfFields } from '@chainsafe/ssz/lib/view/container';
5
- import { type ClusterDefintion, type ClusterLock } from '../types';
5
+ import { type ClusterDefinition, type ClusterLock } from '../types';
6
6
  type DefinitionFieldsV1X7 = {
7
7
  uuid: ByteListType;
8
8
  name: ByteListType;
@@ -24,7 +24,7 @@ type DefinitionContainerTypeV1X7 = ContainerType<DefinitionFieldsV1X7>;
24
24
  * @returns SSZ Containerized type of cluster input
25
25
  */
26
26
  export declare const clusterDefinitionContainerTypeV1X7: (configOnly: boolean) => DefinitionContainerTypeV1X7;
27
- export declare const hashClusterDefinitionV1X7: (cluster: ClusterDefintion, configOnly: boolean) => ValueOfFields<DefinitionFieldsV1X7>;
27
+ export declare const hashClusterDefinitionV1X7: (cluster: ClusterDefinition, configOnly: boolean) => ValueOfFields<DefinitionFieldsV1X7>;
28
28
  /**
29
29
  * @param cluster The published cluster lock
30
30
  * @returns The lock hash in of the corresponding cluster
@@ -2,7 +2,7 @@ import { UintNumberType } from '@chainsafe/ssz/lib/type/uint';
2
2
  import { type creatorAddressWrapperType, type creatorContainerType, type operatorAddressWrapperType, type operatorContainerType, validatorsContainerType } from './sszTypes';
3
3
  import { ByteListType, ByteVectorType, ContainerType, ListBasicType, ListCompositeType } from '@chainsafe/ssz';
4
4
  import { type ValueOfFields } from '@chainsafe/ssz/lib/view/container';
5
- import { type ClusterDefintion, type ClusterLock } from '../types';
5
+ import { type ClusterDefinition, type ClusterLock } from '../types';
6
6
  type DefinitionFieldsV1X8 = {
7
7
  uuid: ByteListType;
8
8
  name: ByteListType;
@@ -25,7 +25,7 @@ type DefinitionContainerTypeV1X8 = ContainerType<DefinitionFieldsV1X8>;
25
25
  * @returns SSZ Containerized type of cluster input
26
26
  */
27
27
  export declare const clusterDefinitionContainerTypeV1X8: (configOnly: boolean) => DefinitionContainerTypeV1X8;
28
- export declare const hashClusterDefinitionV1X8: (cluster: ClusterDefintion, configOnly: boolean) => ValueOfFields<DefinitionFieldsV1X8>;
28
+ export declare const hashClusterDefinitionV1X8: (cluster: ClusterDefinition, configOnly: boolean) => ValueOfFields<DefinitionFieldsV1X8>;
29
29
  /**
30
30
  * @param cluster The published cluster lock
31
31
  * @returns The lock hash in of the corresponding cluster
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obolnetwork/obol-sdk",
3
- "version": "1.0.14",
3
+ "version": "1.0.15",
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"
@@ -42,6 +42,7 @@
42
42
  "@chainsafe/discv5": "^0.5.1",
43
43
  "@chainsafe/ssz": "^0.14.0",
44
44
  "@metamask/eth-sig-util": "^7.0.1",
45
+ "@types/pdf-parse": "^1.1.4",
45
46
  "ajv": "^8.12.0",
46
47
  "cross-fetch": "^3.1.5",
47
48
  "elliptic": "^6.5.4",
@@ -49,9 +50,9 @@
49
50
  "eslint-plugin-import": "^2.29.1",
50
51
  "eslint-plugin-n": "^16.6.2",
51
52
  "eslint-plugin-promise": "^6.1.1",
52
- "ethers": "^6.4.0",
53
+ "ethers": "6.4.0",
53
54
  "nock": "^13.5.3",
54
- "release-it": "^17.1.1",
55
+ "pdf-parse": "^1.1.1",
55
56
  "semver": "^7.6.0",
56
57
  "typescript-eslint": "^7.1.0",
57
58
  "uuid": "^9.0.0"
@@ -61,11 +62,13 @@
61
62
  "@types/elliptic": "^6.4.18",
62
63
  "@types/jest": "^28.1.8",
63
64
  "@types/node": "^20.2.5",
65
+ "@types/semver": "^7.5.8",
64
66
  "@types/uuid": "^9.0.1",
65
67
  "eslint": "^8.57.0",
66
68
  "jest": "^28.1.3",
67
69
  "msw": "^2.2.1",
68
70
  "npm-run-all": "^4.1.5",
71
+ "release-it": "^17.2.1",
69
72
  "ts-jest": "^28.0.8",
70
73
  "tsup": "^6.7.0",
71
74
  "typedoc": "^0.25.7",
package/src/base.ts CHANGED
@@ -39,9 +39,11 @@ export abstract class Base {
39
39
  const response = await fetch(url, config)
40
40
  if (response.ok) {
41
41
  return await response.json()
42
+ } else {
43
+ const errorResponse = await response.json()
44
+ throw errorResponse
42
45
  }
43
- throw new Error(response.statusText)
44
- } catch (e) {
46
+ } catch (e: any) {
45
47
  throw e
46
48
  }
47
49
  }
package/src/constants.ts CHANGED
@@ -9,6 +9,12 @@ export const EIP712_DOMAIN_VERSION = '1'
9
9
  export const CreatorConfigHashSigningTypes = {
10
10
  CreatorConfigHash: [{ name: 'creator_config_hash', type: 'string' }],
11
11
  }
12
+ export const TermsAndConditionsSigningTypes = {
13
+ TermsAndConditions: [
14
+ { name: 'terms_and_conditions_hash', type: 'string' },
15
+ { name: 'version', type: 'uint256' },
16
+ ]
17
+ }
12
18
 
13
19
  const EIP712Domain = [
14
20
  { name: 'name', type: 'string' },
@@ -16,12 +22,15 @@ const EIP712Domain = [
16
22
  { name: 'chainId', type: 'uint256' },
17
23
  ]
18
24
 
19
- export const Domain = (chainId: number): TypedDataDomain => {
20
- return {
25
+ export const Domain = (chainId?: number): TypedDataDomain => {
26
+ const typeDataDomain: any = {
21
27
  name: EIP712_DOMAIN_NAME,
22
28
  version: EIP712_DOMAIN_VERSION,
23
- chainId,
24
29
  }
30
+ if (chainId) {
31
+ typeDataDomain.chainId = chainId
32
+ }
33
+ return typeDataDomain
25
34
  }
26
35
 
27
36
  export const CreatorTypedMessage = {
@@ -107,7 +116,7 @@ export const DOMAIN_DEPOSIT = '03000000'
107
116
  export const GENESIS_VALIDATOR_ROOT =
108
117
  '0000000000000000000000000000000000000000000000000000000000000000'
109
118
 
110
- // Flow used to create defintion
119
+ // Flow used to create definition
111
120
  export enum DefinitionFlow {
112
121
  Group = 'LP-Group',
113
122
  Solo = 'LP-Solo',
@@ -118,3 +127,6 @@ export const DEFAULT_BASE_URL = 'https://api.obol.tech'
118
127
  export const DEFAULT_CHAIN_ID = 17000
119
128
 
120
129
  export const ETHER_TO_GWEI = 10 ** 9
130
+
131
+ export const TERMS_AND_CONDITIONS_VERSION = 1
132
+ export const TERMS_AND_CONDITIONS_URL = (TERMS_AND_CONDITIONS_VERSION === 1) ? 'https://obol.tech/terms.pdf' : `https://obol.tech/${TERMS_AND_CONDITIONS_VERSION as number}/terms.pdf`
package/src/index.ts CHANGED
@@ -9,10 +9,12 @@ import {
9
9
  CONFIG_VERSION,
10
10
  OperatorConfigHashSigningTypes,
11
11
  EnrSigningTypes,
12
+ TERMS_AND_CONDITIONS_VERSION,
13
+ TermsAndConditionsSigningTypes,
12
14
  } from './constants.js'
13
15
  import { ConflictError } from './errors.js'
14
16
  import {
15
- type ClusterDefintion,
17
+ type ClusterDefinition,
16
18
  type ClusterLock,
17
19
  type ClusterPayload,
18
20
  type OperatorPayload,
@@ -20,6 +22,7 @@ import {
20
22
  import { clusterConfigOrDefinitionHash } from './verification/common.js'
21
23
  import { validatePayload } from './ajv.js'
22
24
  import { definitionSchema, operatorPayloadSchema } from './schema.js'
25
+ import { hashTermsAndConditions } from './verification/termsAndConditions.js'
23
26
  export * from './types.js'
24
27
  export * from './services.js'
25
28
 
@@ -47,6 +50,49 @@ export class Client extends Base {
47
50
  this.signer = signer
48
51
  }
49
52
 
53
+ /**
54
+ * Accepts Obol terms and conditions to be able to create or update data.
55
+ * @returns {Promise<string>} terms and conditions acceptance success message.
56
+ * @throws On unverified signature or wrong hash.
57
+ *
58
+ */
59
+ async acceptObolLatestTermsAndConditions (): Promise<string> {
60
+ if (!this.signer) { throw new Error('Signer is required in acceptObolTermsAndConditions') }
61
+
62
+ try {
63
+ const termsAndConditionsHash = await hashTermsAndConditions()
64
+ const address = await this.signer.getAddress()
65
+ const termsAndConditionsPayload = {
66
+ address,
67
+ version: TERMS_AND_CONDITIONS_VERSION,
68
+ terms_and_conditions_hash: termsAndConditionsHash
69
+ }
70
+
71
+ const termsAndConditionsSignature = await this.signer.signTypedData(
72
+ Domain(),
73
+ TermsAndConditionsSigningTypes,
74
+ {
75
+ terms_and_conditions_hash: termsAndConditionsHash,
76
+ version: TERMS_AND_CONDITIONS_VERSION,
77
+ },
78
+ )
79
+
80
+ const termsAndConditionsResponse: { message: string, success: boolean } = await this.request('/termsAndConditions', {
81
+ method: 'POST',
82
+ body: JSON.stringify(termsAndConditionsPayload),
83
+ headers: {
84
+ Authorization: `Bearer ${termsAndConditionsSignature}`,
85
+ },
86
+ })
87
+ return termsAndConditionsResponse?.message
88
+ } catch (err: any) {
89
+ if (err?.message === CONFLICT_ERROR_MSG) {
90
+ throw new ConflictError()
91
+ }
92
+ throw err
93
+ }
94
+ }
95
+
50
96
  /**
51
97
  * Creates a cluster definition which contains cluster configuration.
52
98
  * @param {ClusterPayload} newCluster - The new unique cluster.
@@ -61,7 +107,7 @@ export class Client extends Base {
61
107
 
62
108
  validatePayload(newCluster, definitionSchema)
63
109
 
64
- const clusterConfig: Partial<ClusterDefintion> = {
110
+ const clusterConfig: Partial<ClusterDefinition> = {
65
111
  ...newCluster,
66
112
  fork_version: this.fork_version,
67
113
  dkg_algorithm: DKG_ALGORITHM,
@@ -77,7 +123,7 @@ export class Client extends Base {
77
123
 
78
124
  clusterConfig.creator = { address }
79
125
  clusterConfig.config_hash = clusterConfigOrDefinitionHash(
80
- clusterConfig as ClusterDefintion,
126
+ clusterConfig as ClusterDefinition,
81
127
  true,
82
128
  )
83
129
 
@@ -87,7 +133,7 @@ export class Client extends Base {
87
133
  { creator_config_hash: clusterConfig.config_hash },
88
134
  )
89
135
 
90
- const clusterDefinition: ClusterDefintion = await this.request('/dv', {
136
+ const clusterDefinition: ClusterDefinition = await this.request('/v1/definition', {
91
137
  method: 'POST',
92
138
  body: JSON.stringify(clusterConfig),
93
139
  headers: {
@@ -108,7 +154,7 @@ export class Client extends Base {
108
154
  * Approves joining a cluster with specific configuration.
109
155
  * @param {OperatorPayload} operatorPayload - The operator data including signatures.
110
156
  * @param {string} configHash - The config hash of the cluster which the operator confirms joining to.
111
- * @returns {Promise<ClusterDefintion>} The cluster definition.
157
+ * @returns {Promise<ClusterDefinition>} The cluster definition.
112
158
  * @throws On unauthorized, duplicate entries, missing keys, not found cluster or invalid data.
113
159
  *
114
160
  * An example of how to use acceptClusterDefinition:
@@ -117,7 +163,7 @@ export class Client extends Base {
117
163
  async acceptClusterDefinition (
118
164
  operatorPayload: OperatorPayload,
119
165
  configHash: string,
120
- ): Promise<ClusterDefintion> {
166
+ ): Promise<ClusterDefinition> {
121
167
  if (!this.signer) { throw new Error('Signer is required in acceptClusterDefinition') }
122
168
 
123
169
  validatePayload(operatorPayload, operatorPayloadSchema)
@@ -142,8 +188,8 @@ export class Client extends Base {
142
188
  enr_signature: operatorENRSignature,
143
189
  fork_version: this.fork_version,
144
190
  }
145
- const clusterDefinition: ClusterDefintion = await this.request(
146
- `/dv/${configHash}`,
191
+ const clusterDefinition: ClusterDefinition = await this.request(
192
+ `/v1/definition/${configHash}`,
147
193
  {
148
194
  method: 'PUT',
149
195
  body: JSON.stringify(operatorData),
@@ -160,14 +206,14 @@ export class Client extends Base {
160
206
 
161
207
  /**
162
208
  * @param configHash - The configuration hash returned in createClusterDefinition
163
- * @returns {Promise<ClusterDefintion>} The cluster definition for config hash
209
+ * @returns {Promise<ClusterDefinition>} The cluster definition for config hash
164
210
  * @throws On not found config hash.
165
211
  *
166
212
  * An example of how to use getClusterDefinition:
167
213
  * [getObolClusterDefinition](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts)
168
214
  */
169
- async getClusterDefinition (configHash: string): Promise<ClusterDefintion> {
170
- const clusterDefinition: ClusterDefintion = await this.request(
215
+ async getClusterDefinition (configHash: string): Promise<ClusterDefinition> {
216
+ const clusterDefinition: ClusterDefinition = await this.request(
171
217
  `/dv/${configHash}`,
172
218
  {
173
219
  method: 'GET',
package/src/types.ts CHANGED
@@ -85,7 +85,7 @@ export interface ClusterPayload {
85
85
  /**
86
86
  * Cluster definition data needed for dkg
87
87
  */
88
- export interface ClusterDefintion extends ClusterPayload {
88
+ export interface ClusterDefinition extends ClusterPayload {
89
89
  /** The creator of the cluster. */
90
90
  creator: ClusterCreator
91
91
 
@@ -193,7 +193,7 @@ export interface DistributedValidator {
193
193
  */
194
194
  export interface ClusterLock {
195
195
  /** The cluster definition. */
196
- cluster_definition: ClusterDefintion
196
+ cluster_definition: ClusterDefinition
197
197
 
198
198
  /** The cluster distributed validators. */
199
199
  distributed_validators: DistributedValidator[]
package/src/utils.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DefinitionFlow } from './constants'
2
- import { type ClusterDefintion } from './types'
2
+ import { type ClusterDefinition } from './types'
3
3
 
4
4
  export const hexWithout0x = (hex: string): string => {
5
5
  return hex.slice(2, hex.length)
@@ -10,7 +10,7 @@ export const strToUint8Array = (str: string): Uint8Array => {
10
10
  }
11
11
 
12
12
  export const definitionFlow = (
13
- clusterDefinition: ClusterDefintion,
13
+ clusterDefinition: ClusterDefinition,
14
14
  ): DefinitionFlow | null => {
15
15
  if (
16
16
  clusterDefinition.creator.address &&
@@ -6,7 +6,7 @@ import {
6
6
  init,
7
7
  } from '@chainsafe/bls'
8
8
 
9
- import { FORK_MAPPING, type ClusterDefintion, type ClusterLock, type DepositData, type BuilderRegistrationMessage, type DistributedValidator } from '../types.js'
9
+ import { FORK_MAPPING, type ClusterDefinition, type ClusterLock, type DepositData, type BuilderRegistrationMessage, type DistributedValidator } from '../types.js'
10
10
  import * as semver from 'semver'
11
11
  import { clusterDefinitionContainerTypeV1X6, hashClusterDefinitionV1X6, hashClusterLockV1X6, verifyDVV1X6 } from './v1.6.0.js'
12
12
  import { clusterDefinitionContainerTypeV1X7, hashClusterDefinitionV1X7, hashClusterLockV1X7, verifyDVV1X7 } from './v1.7.0.js'
@@ -26,7 +26,7 @@ import { clusterDefinitionContainerTypeV1X8, hashClusterDefinitionV1X8, hashClus
26
26
  * @returns The config hash or the definition hash in of the corresponding cluster
27
27
  */
28
28
  export const clusterConfigOrDefinitionHash = (
29
- cluster: ClusterDefintion,
29
+ cluster: ClusterDefinition,
30
30
  configOnly: boolean,
31
31
  ): string => {
32
32
  let definitionType, val
@@ -142,7 +142,7 @@ const getEnrSigner = (
142
142
  }
143
143
 
144
144
  const verifyDefinitionSignatures = (
145
- clusterDefinition: ClusterDefintion,
145
+ clusterDefinition: ClusterDefinition,
146
146
  definitionType: DefinitionFlow,
147
147
  ): boolean => {
148
148
  if (definitionType === DefinitionFlow.Charon) {
@@ -0,0 +1,30 @@
1
+ import pdf from 'pdf-parse'
2
+ import { ByteListType, ContainerType } from '@chainsafe/ssz'
3
+ import { TERMS_AND_CONDITIONS_URL } from '../constants'
4
+ import { strToUint8Array } from '../utils'
5
+
6
+ export const hashTermsAndConditions = async (): Promise<string | null> => {
7
+ try {
8
+ // read the pdf
9
+ const response = await fetch(TERMS_AND_CONDITIONS_URL)
10
+ const pdfBuffarrayBuffer = await response.arrayBuffer()
11
+ const pdfBuffer = Buffer.from(pdfBuffarrayBuffer)
12
+ const data = await pdf(pdfBuffer)
13
+
14
+ // ssz hash
15
+ const termsType = new ContainerType({
16
+ terms_and_conditions_hash: new ByteListType(Number.MAX_SAFE_INTEGER),
17
+ })
18
+
19
+ const termsHasVal = termsType.defaultValue()
20
+
21
+ termsHasVal.terms_and_conditions_hash = strToUint8Array(data?.text)
22
+
23
+ return (
24
+ '0x' +
25
+ Buffer.from(termsType.hashTreeRoot(termsHasVal).buffer).toString('hex')
26
+ )
27
+ } catch (err) {
28
+ return null
29
+ }
30
+ }
@@ -3,7 +3,7 @@ import { strToUint8Array } from '../utils'
3
3
  import { type creatorAddressWrapperType, type creatorContainerType, newCreatorContainerType, newOperatorContainerType, type operatorAddressWrapperType, type operatorContainerType, validatorsContainerType } from './sszTypes'
4
4
  import { ByteListType, ByteVectorType, ContainerType, ListCompositeType, fromHexString } from '@chainsafe/ssz'
5
5
  import { type ValueOfFields } from '@chainsafe/ssz/lib/view/container'
6
- import { type ClusterDefintion, type ClusterLock, type DepositData } from '../types'
6
+ import { type ClusterDefinition, type ClusterLock, type DepositData } from '../types'
7
7
  import { verifyDepositData } from './common'
8
8
  import { aggregateSignatures, verifyAggregate, verifyMultiple } from '@chainsafe/bls'
9
9
 
@@ -61,7 +61,7 @@ export const clusterDefinitionContainerTypeV1X6 = (
61
61
  }
62
62
 
63
63
  export const hashClusterDefinitionV1X6 = (
64
- cluster: ClusterDefintion,
64
+ cluster: ClusterDefinition,
65
65
  configOnly: boolean,
66
66
  ): ValueOfFields<DefinitionFieldsV1X6> => {
67
67
  const definitionType = clusterDefinitionContainerTypeV1X6(configOnly)
@@ -3,7 +3,7 @@ import { strToUint8Array } from '../utils'
3
3
  import { builderRegistrationContainer, type creatorAddressWrapperType, type creatorContainerType, depositDataContainer, newCreatorContainerType, newOperatorContainerType, type operatorAddressWrapperType, type operatorContainerType, validatorsContainerType } from './sszTypes'
4
4
  import { ByteListType, ByteVectorType, ContainerType, ListCompositeType, fromHexString } from '@chainsafe/ssz'
5
5
  import { type ValueOfFields } from '@chainsafe/ssz/lib/view/container'
6
- import { type ClusterDefintion, type ClusterLock, type DepositData } from '../types'
6
+ import { type ClusterDefinition, type ClusterLock, type DepositData } from '../types'
7
7
  import { verifyBuilderRegistration, verifyDepositData, verifyNodeSignatures } from './common'
8
8
  import { aggregateSignatures, verifyAggregate, verifyMultiple } from '@chainsafe/bls'
9
9
 
@@ -61,7 +61,7 @@ export const clusterDefinitionContainerTypeV1X7 = (
61
61
  }
62
62
 
63
63
  export const hashClusterDefinitionV1X7 = (
64
- cluster: ClusterDefintion,
64
+ cluster: ClusterDefinition,
65
65
  configOnly: boolean,
66
66
  ): ValueOfFields<DefinitionFieldsV1X7> => {
67
67
  const definitionType = clusterDefinitionContainerTypeV1X7(configOnly)
@@ -3,7 +3,7 @@ import { strToUint8Array } from '../utils'
3
3
  import { builderRegistrationContainer, type creatorAddressWrapperType, type creatorContainerType, depositDataContainer, newCreatorContainerType, newOperatorContainerType, type operatorAddressWrapperType, type operatorContainerType, validatorsContainerType } from './sszTypes'
4
4
  import { ByteListType, ByteVectorType, ContainerType, ListBasicType, ListCompositeType, fromHexString } from '@chainsafe/ssz'
5
5
  import { type ValueOfFields } from '@chainsafe/ssz/lib/view/container'
6
- import { type ClusterDefintion, type ClusterLock, type DepositData } from '../types'
6
+ import { type ClusterDefinition, type ClusterLock, type DepositData } from '../types'
7
7
  import { verifyBuilderRegistration, verifyDepositData, verifyNodeSignatures } from './common'
8
8
  import { aggregateSignatures, verifyAggregate, verifyMultiple } from '@chainsafe/bls'
9
9
 
@@ -65,7 +65,7 @@ export const clusterDefinitionContainerTypeV1X8 = (
65
65
  }
66
66
 
67
67
  export const hashClusterDefinitionV1X8 = (
68
- cluster: ClusterDefintion,
68
+ cluster: ClusterDefinition,
69
69
  configOnly: boolean,
70
70
  ): ValueOfFields<DefinitionFieldsV1X8> => {
71
71
  const definitionType = clusterDefinitionContainerTypeV1X8(configOnly)