@obolnetwork/obol-sdk 2.1.1 → 2.2.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.
@@ -14,13 +14,12 @@ import { FORK_MAPPING, } from '../types.js';
14
14
  import * as semver from 'semver';
15
15
  import { clusterDefinitionContainerTypeV1X6, hashClusterDefinitionV1X6, hashClusterLockV1X6, verifyDVV1X6, } from './v1.6.0.js';
16
16
  import { clusterDefinitionContainerTypeV1X7, hashClusterDefinitionV1X7, hashClusterLockV1X7, verifyDVV1X7, } from './v1.7.0.js';
17
- import { ethers } from 'ethers';
18
17
  import { DOMAIN_APPLICATION_BUILDER, DOMAIN_DEPOSIT, DefinitionFlow, GENESIS_VALIDATOR_ROOT, signCreatorConfigHashPayload, signEnrPayload, signOperatorConfigHashPayload, } from '../constants.js';
19
- import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util';
20
18
  import { builderRegistrationMessageType, depositMessageType, forkDataType, signingRootType, } from './sszTypes.js';
21
19
  import { definitionFlow, hexWithout0x } from '../utils.js';
22
20
  import { ENR } from '@chainsafe/discv5';
23
21
  import { clusterDefinitionContainerTypeV1X8, hashClusterDefinitionV1X8, hashClusterLockV1X8, verifyDVV1X8, } from './v1.8.0.js';
22
+ import { validateAddressSignature } from './signature-validator.js';
24
23
  // cluster-definition hash
25
24
  /**
26
25
  * @param cluster The cluster configuration or the cluster definition
@@ -78,62 +77,70 @@ export const clusterLockHash = (clusterLock) => {
78
77
  };
79
78
  // Lock verification
80
79
  // cluster-definition signatures verification
81
- const getPOSTConfigHashSigner = (signature, configHash, chainId) => {
80
+ const validatePOSTConfigHashSigner = (address, signature, configHash, chainId) => __awaiter(void 0, void 0, void 0, function* () {
82
81
  try {
83
- const sig = ethers.Signature.from(signature);
84
82
  const data = signCreatorConfigHashPayload({ creator_config_hash: configHash }, chainId);
85
- const digest = TypedDataUtils.eip712Hash(data, SignTypedDataVersion.V4);
86
- return ethers.recoverAddress(digest, sig).toLowerCase();
83
+ return yield validateAddressSignature({
84
+ address,
85
+ token: signature,
86
+ data,
87
+ chainId,
88
+ });
87
89
  }
88
90
  catch (err) {
89
91
  throw err;
90
92
  }
91
- };
92
- const getPUTConfigHashSigner = (signature, configHash, chainId) => {
93
+ });
94
+ const validatePUTConfigHashSigner = (address, signature, configHash, chainId) => __awaiter(void 0, void 0, void 0, function* () {
93
95
  try {
94
- const sig = ethers.Signature.from(signature);
95
96
  const data = signOperatorConfigHashPayload({ operator_config_hash: configHash }, chainId);
96
- const digest = TypedDataUtils.eip712Hash(data, SignTypedDataVersion.V4);
97
- return ethers.recoverAddress(digest, sig).toLowerCase();
97
+ return yield validateAddressSignature({
98
+ address,
99
+ token: signature,
100
+ data,
101
+ chainId,
102
+ });
98
103
  }
99
104
  catch (err) {
100
105
  throw err;
101
106
  }
102
- };
103
- const getEnrSigner = (signature, payload, chainId) => {
107
+ });
108
+ const validateEnrSigner = (address, signature, payload, chainId) => __awaiter(void 0, void 0, void 0, function* () {
104
109
  try {
105
- const sig = ethers.Signature.from(signature);
106
110
  const data = signEnrPayload({ enr: payload }, chainId);
107
- const digest = TypedDataUtils.eip712Hash(data, SignTypedDataVersion.V4);
108
- return ethers.recoverAddress(digest, sig).toLowerCase();
111
+ return yield validateAddressSignature({
112
+ address,
113
+ token: signature,
114
+ data,
115
+ chainId,
116
+ });
109
117
  }
110
118
  catch (err) {
111
119
  throw err;
112
120
  }
113
- };
114
- const verifyDefinitionSignatures = (clusterDefinition, definitionType) => {
121
+ });
122
+ const verifyDefinitionSignatures = (clusterDefinition, definitionType) => __awaiter(void 0, void 0, void 0, function* () {
115
123
  if (definitionType === DefinitionFlow.Charon) {
116
124
  return true;
117
125
  }
118
126
  else {
119
- const configSigner = getPOSTConfigHashSigner(clusterDefinition.creator.config_signature, clusterDefinition.config_hash, FORK_MAPPING[clusterDefinition.fork_version]);
120
- if (configSigner !== clusterDefinition.creator.address.toLowerCase()) {
127
+ const isPOSTConfigHashSignerValid = yield validatePOSTConfigHashSigner(clusterDefinition.creator.address, clusterDefinition.creator.config_signature, clusterDefinition.config_hash, FORK_MAPPING[clusterDefinition.fork_version]);
128
+ if (!isPOSTConfigHashSignerValid) {
121
129
  return false;
122
130
  }
123
131
  if (definitionType === DefinitionFlow.Solo) {
124
132
  return true;
125
133
  }
126
- return clusterDefinition.operators.every(operator => {
127
- const configSigner = getPUTConfigHashSigner(operator.config_signature, clusterDefinition.config_hash, FORK_MAPPING[clusterDefinition.fork_version]);
128
- const enrSigner = getEnrSigner(operator.enr_signature, operator.enr, FORK_MAPPING[clusterDefinition.fork_version]);
129
- if (configSigner !== operator.address.toLowerCase() ||
130
- enrSigner !== operator.address.toLowerCase()) {
134
+ return clusterDefinition.operators.every((operator) => __awaiter(void 0, void 0, void 0, function* () {
135
+ const isPUTConfigHashSignerValid = yield validatePUTConfigHashSigner(operator.address, operator.config_signature, clusterDefinition.config_hash, FORK_MAPPING[clusterDefinition.fork_version]);
136
+ const isENRSignerValid = yield validateEnrSigner(operator.address, operator.enr_signature, operator.enr, FORK_MAPPING[clusterDefinition.fork_version]);
137
+ if (!isPUTConfigHashSignerValid || !isENRSignerValid) {
131
138
  return false;
132
139
  }
133
140
  return true;
134
- });
141
+ }));
135
142
  }
136
- };
143
+ });
137
144
  // cluster-lock data verification
138
145
  const computeSigningRoot = (sszObjectRoot, domain) => {
139
146
  const signingRootDefaultValue = signingRootType.defaultValue();
@@ -260,7 +267,7 @@ export const isValidClusterLock = (clusterLock) => __awaiter(void 0, void 0, voi
260
267
  if (definitionType == null) {
261
268
  return false;
262
269
  }
263
- const isValidDefinitionData = verifyDefinitionSignatures(clusterLock.cluster_definition, definitionType);
270
+ const isValidDefinitionData = yield verifyDefinitionSignatures(clusterLock.cluster_definition, definitionType);
264
271
  if (!isValidDefinitionData) {
265
272
  return false;
266
273
  }
@@ -0,0 +1,63 @@
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
+ /* eslint-disable @typescript-eslint/restrict-template-expressions */
11
+ import { ethers } from 'ethers';
12
+ import { SignTypedDataVersion, TypedDataUtils, } from '@metamask/eth-sig-util';
13
+ import Safe from '@safe-global/protocol-kit';
14
+ import { PROVIDER_MAP } from '../constants';
15
+ import { hashTypedData } from '@safe-global/protocol-kit/dist/src/utils';
16
+ import { isContractAvailable, getProvider } from '../utils';
17
+ export const validateAddressSignature = ({ address, token, data, chainId, }) => __awaiter(void 0, void 0, void 0, function* () {
18
+ try {
19
+ const provider = getProvider(chainId);
20
+ if (provider) {
21
+ const contractAddress = yield isContractAvailable(address, provider);
22
+ if (contractAddress) {
23
+ return yield validateSmartContractSignature({
24
+ token,
25
+ data: data,
26
+ address,
27
+ chainId,
28
+ });
29
+ }
30
+ }
31
+ return validateEOASignature({ token, data, address });
32
+ }
33
+ catch (error) {
34
+ return validateEOASignature({ token, data, address });
35
+ }
36
+ });
37
+ export const validateEOASignature = ({ token, data, address, }) => {
38
+ try {
39
+ const sig = ethers.Signature.from(token);
40
+ const digest = TypedDataUtils.eip712Hash(data, SignTypedDataVersion.V4);
41
+ return (ethers.recoverAddress(digest, sig).toLowerCase() ===
42
+ address.toLocaleLowerCase());
43
+ }
44
+ catch (err) {
45
+ console.error(`validate EOA Signature error: ${err}`);
46
+ throw err;
47
+ }
48
+ };
49
+ export const validateSmartContractSignature = ({ token, data, address, chainId, }) => __awaiter(void 0, void 0, void 0, function* () {
50
+ try {
51
+ const provider = PROVIDER_MAP[chainId];
52
+ const protocolKit = yield Safe.init({
53
+ provider,
54
+ safeAddress: address,
55
+ });
56
+ const messageHash = hashTypedData(data);
57
+ const isValidSignature = yield protocolKit.isValidSignature(messageHash, token);
58
+ return isValidSignature;
59
+ }
60
+ catch (err) {
61
+ throw new Error(`Error validating smart contract signature: ${err.message}`);
62
+ }
63
+ });
@@ -420,3 +420,90 @@ export const nullDepositAmountsClusterLockV1X8 = {
420
420
  '0xdb6332a4ca9c41592511e7480c835b449fea120430e759735f3c24f1a6d496551a516151d2baae21d0cc9469b28dc1122b672af40ebb0e77045da92e0da6a03500',
421
421
  ],
422
422
  };
423
+ export const clusterLockWithSafe = {
424
+ cluster_definition: {
425
+ name: 'Rainy cluster',
426
+ creator: {
427
+ address: '0xEF43Ff87070247EC9D28B482D6474318e9DAFc98',
428
+ config_signature: '0x0c4aaafcef722cf770b419d47a21571b3c73275185a1b719e5709168af6327eb2ee392134e660d6e4ea113abd184dd4f2bd8ee09a4ae33f434433f7575873b981c',
429
+ },
430
+ operators: [
431
+ {
432
+ address: '0xe5b709A14859EdF820347D78E587b1634B0ec771',
433
+ enr: 'enr:-HW4QGrhMVQGLtKv_KlubzcVZM9AUsSVgDPtq4k_0q344ZtWJ6tD4riMP5jfWXXdHIklAm_pHdQv0MbTE1DrXmuEM7yAgmlkgnY0iXNlY3AyNTZrMaECa_cfUuFYiuFQp_dUEEicCBdFWNfzm8QhrNhBwey8PBg',
434
+ config_signature: '0xad813cd706d7469b560570ef13b0afdf36d0637e9bc5362ec358c218085d9b231b1377f0078e1ed9c2af99fddb3d7608c03bf8aee6edc931c73d7204a7fdee181b',
435
+ enr_signature: '0x304c022e74c13bfd492219efc52fb133f728afa01fefa9fe1d2540ce7c23ca730dd8248b0ad76630dd120975085d77fba20c050620f1e9583590d003110dbca41c',
436
+ },
437
+ {
438
+ address: '0x83b7CA5be230A50DEE511657CCFF262ba04D0353',
439
+ enr: 'enr:-HW4QNGbmr_0YhIeTUQfljlDGGCnR8RGhtWr_gjMI8a5BwigGur2qFgGZrqSumAvk4gX9ciP2Tu74uOdOZPw6nvTi7aAgmlkgnY0iXNlY3AyNTZrMaEC0nrshQsWtEOphaLrZVTzPFOz8EAJweGbhXDtSsXPiLY',
440
+ config_signature: '0x41dfb02ff13775d53ad3f739151c44952ff98a3283d124b6f69782e7458150e92771e32f965803391818d23ad95c9c7bcba15e896c3a576d1a700a16d8cb29fe1b',
441
+ enr_signature: '0x388460abfdeded56839e4ae1480ea48a70ac44a89963bda24152e4f879a9d22918c3e77733f2ccaed5c2745f503565d651a357c6a50634a597d61e16c384961e1c',
442
+ },
443
+ {
444
+ address: '0x123C1cD7AeE10b525D09b848D4A62596958f7D9C',
445
+ enr: 'enr:-HW4QMpDdYHJR3TlnWsnuIKfbmg2aRMMcAFlhTuuZa0Grd3dMFC0AEPHvHM2cKboULC4RUl2Dz7-LpwL2EJRGhJzxcyAgmlkgnY0iXNlY3AyNTZrMaEC73iR5dcgO_bJ8tvsHLrFglwspGFhaAbiBtGAHAJPD9c',
446
+ config_signature: '0x91d23aefc02263fdf0edd0dcfdf26eee01c4d673b97a91a099fc10dfc01f55fa690eccc809658ea2ed17383df355f80a62bd96df15e40a2e2f740189fa6537f61b',
447
+ enr_signature: '0x1035b284df3b1d60434bfe2d139f1472ed29663e2af719779bb5da35537f202032ab0f7cdc540c724c8e80bedfba247809d741260201fbb5bcbfc7f0813334241b',
448
+ },
449
+ {
450
+ address: '0x169212AE1A834C1E0efd8C827Abdbc2b5557B86C', // safe address
451
+ enr: 'enr:-HW4QPwheIq51G9r9k-6GYrhp9-y-hN2SSowqgQemvPyBu9cOKERqPeqy0JuZ0yKyWiji406eK8fFeHX5fKU-6OI3u6AgmlkgnY0iXNlY3AyNTZrMaECaARWVFt5KNkHPno5YYdFBUrkLU5EmOB3K0Ns9hp-s-s',
452
+ config_signature: '0xf97cc2536afe96e43e07aac140254cea37b0345573b12476f8840e571095d72049aa9e03a099ef2965523f83648c4323cd16ec2ee3f5e6efac177da13bcf6eb21c',
453
+ enr_signature: '0x422f471d54eb19fc52e076bb11fd5c8de895734ed111b66708ef805ae124f87b7519e841e59eccdb75c8328223efd8bb324f3fd7d3048f037419bb83e03caae41b',
454
+ },
455
+ ],
456
+ uuid: '3ac43657-b465-49a8-94aa-25b7f7979c2a',
457
+ version: 'v1.8.0',
458
+ timestamp: '2025-01-08T15:18:28.598Z',
459
+ num_validators: 1,
460
+ threshold: 3,
461
+ validators: [
462
+ {
463
+ fee_recipient_address: '0xe5b709A14859EdF820347D78E587b1634B0ec771',
464
+ withdrawal_address: '0xe5b709A14859EdF820347D78E587b1634B0ec771',
465
+ },
466
+ ],
467
+ dkg_algorithm: 'default',
468
+ fork_version: '0x01017000',
469
+ deposit_amounts: ['32000000000'],
470
+ config_hash: '0x1849d9434a5ce7a7d15e1e030a48740a307c80424608c6618386d78a2e597fb3',
471
+ definition_hash: '0x85dabfcebf9bf850521cdb65ea6083c462d9ac57eb62d9a9eb47761ec10bc80e',
472
+ },
473
+ distributed_validators: [
474
+ {
475
+ distributed_public_key: '0xb0427b57f3f31da882a1ca5d9980e6164ba4c4d3e2c35fb6699f4e76d88e1aede881e0bdb87c48ac707da282f0c92e92',
476
+ public_shares: [
477
+ '0x8d150f921a54fe258bb57f568288de87e392946124df65861afbe52d2a3be6a1fa09d19beb62f879b0f18b2e3b26f69f',
478
+ '0x8fa69ec7c31eeda66007bc3d49a3d5654dbe6a3c0890898ea5913ed263a0efc3f6744064dbde3ee0cc1d2f08db33b4d7',
479
+ '0xb862933b7b7adb33cfb3de1052f7dd9e45dfa30accda01dfc4d3c38716747504aade8f832fa5a256dcaf13f80dbbb38f',
480
+ '0xb5a15d86945c7ff38e0b09158a3264033e6dbaebf2ec9f3426988e6d24e38b3dbd6fe413d3253b308404bb691757fb2e',
481
+ ],
482
+ builder_registration: {
483
+ message: {
484
+ fee_recipient: '0xe5b709a14859edf820347d78e587b1634b0ec771',
485
+ gas_limit: 30000000,
486
+ timestamp: 1696000704,
487
+ pubkey: '0xb0427b57f3f31da882a1ca5d9980e6164ba4c4d3e2c35fb6699f4e76d88e1aede881e0bdb87c48ac707da282f0c92e92',
488
+ },
489
+ signature: '0xa5e4220e7d8e7fd4430590c70a27104473b15c8ea03b2d1571bc86bbbcb443e2b7467f75a7e77b5d9b1a10d5fbc335cf0d59c3b3bc1b6d036b2e22511abcf41373567a15185f011ce0129741f0ae0e9e49759f9c5c0ce62f2a99f30e8c7df996',
490
+ },
491
+ partial_deposit_data: [
492
+ {
493
+ pubkey: '0xb0427b57f3f31da882a1ca5d9980e6164ba4c4d3e2c35fb6699f4e76d88e1aede881e0bdb87c48ac707da282f0c92e92',
494
+ withdrawal_credentials: '0x010000000000000000000000e5b709a14859edf820347d78e587b1634b0ec771',
495
+ amount: '32000000000',
496
+ signature: '0x90aff3cb35c577644cb5fe3036ae8aeb5ac6cc2ce2a33a4332e2e883dc48f8fb71165531862d3035a4b6e3a60215d661021672a59f9406e3bd04b5f88f4f77c5f54a9ff37167807010123cdcc230040e5a485b5eb36704fd276dbf541f7151a8',
497
+ },
498
+ ],
499
+ },
500
+ ],
501
+ signature_aggregate: '0x86fdeae3db45215d5f24fc19645f8f95e91fffd1a1d0a87668d3f27724fa735b85fb15de120106b8463ff1d32823813e0f139ee7cb6d3b026c6b81cc96566819817742a13e24ac44fc6571385d5b0fdbaace7e50e2cfbcd692e36b1ca25436e4',
502
+ lock_hash: '0xe03b0fdf6d6a7d38360b0ceac1f0380b051f710e80d0e166b855ef3148d46733',
503
+ node_signatures: [
504
+ '0x564328bcad4cac498aa51b7e7e041cf264e9c65c95138df1fc00d4bf714bc38419c43b5a32662d5c8b747d646d6e77830b2bfd62fa8402e136147c244acdce0300',
505
+ '0xb0e984efd2874fc4bc97bb9c42dccbc723080cf29a20a3cf0b05867ebe87bc430c29a560936000439fcde610ce91452ced763586d3bcca24fbe4fe83bb39202601',
506
+ '0xf3633a8e59db2e8d253ffe1b284f9edab1c1c1aca6288c3a3b2e9cb4eb7e789e23366fc325274f13b7311b9faf53665c80b5cb1f93810713f7b094cd2bec1d9900',
507
+ '0x4067921a5257efe4ceb103f2129faaa7a502781157b3b54e5efca559c558c2b43b89f30962e87df88fbf62250049a31888fcd62735d54b7553e5dc75c3b6ae0901',
508
+ ],
509
+ };
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { ethers, JsonRpcProvider } from 'ethers';
11
11
  import { Client, validateClusterLock } from '../src/index';
12
- import { clusterConfigV1X7, clusterConfigV1X8, clusterLockV1X6, clusterLockV1X7, clusterLockV1X8, nullDepositAmountsClusterLockV1X8, } from './fixtures.js';
12
+ import { clusterConfigV1X7, clusterConfigV1X8, clusterLockV1X6, clusterLockV1X7, clusterLockV1X8, clusterLockWithSafe, nullDepositAmountsClusterLockV1X8, } from './fixtures.js';
13
13
  import { SDK_VERSION } from '../src/constants';
14
14
  import { Base } from '../src/base';
15
15
  import { validatePayload } from '../src/ajv';
@@ -177,6 +177,10 @@ describe('Cluster Client without a signer', () => {
177
177
  version: 'null deposit_amounts v1.8.0',
178
178
  clusterLock: nullDepositAmountsClusterLockV1X8,
179
179
  },
180
+ {
181
+ version: 'Cluster with safe address v1.8.0',
182
+ clusterLock: clusterLockWithSafe,
183
+ },
180
184
  ])("$version: 'should return true on verified cluster lock'", ({ clusterLock }) => __awaiter(void 0, void 0, void 0, function* () {
181
185
  const isValidLock = yield validateClusterLock(clusterLock);
182
186
  expect(isValidLock).toEqual(true);
@@ -110,3 +110,4 @@ export declare const CHAIN_CONFIGURATION: {
110
110
  export declare const DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT = 1;
111
111
  export declare const DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT = 0.1;
112
112
  export declare const OBOL_SDK_EMAIL = "sdk@dvlabs.tech";
113
+ export declare const PROVIDER_MAP: Record<number, string>;
@@ -3,6 +3,7 @@ import { Base } from './base.js';
3
3
  import { type RewardsSplitPayload, type ClusterDefinition, type ClusterLock, type ClusterPayload, type OperatorPayload, type TotalSplitPayload, type ClusterValidator, type ETH_ADDRESS, type OWRTranches } from './types.js';
4
4
  export * from './types.js';
5
5
  export * from './services.js';
6
+ export * from './verification/signature-validator.js';
6
7
  /**
7
8
  * Obol sdk Client can be used for creating, managing and activating distributed validators.
8
9
  */
@@ -9,8 +9,14 @@ export declare enum FORK_MAPPING {
9
9
  /** Gnosis Chain. */
10
10
  '0x00000064' = 100,
11
11
  /** Holesky. */
12
- '0x01017000' = 17000
12
+ '0x01017000' = 17000,
13
+ /** Sepolia. */
14
+ '0x90000069' = 11155111
13
15
  }
16
+ /**
17
+ * Permitted Chain Names
18
+ */
19
+ export declare const FORK_NAMES: Record<number, string>;
14
20
  /**
15
21
  * Node operator data
16
22
  */
@@ -1,4 +1,4 @@
1
- import { type Provider } from 'ethers';
1
+ import { ethers, type Provider } from 'ethers';
2
2
  import { DefinitionFlow } from './constants';
3
3
  import { type ClusterDefinition } from './types';
4
4
  export declare const hexWithout0x: (hex: string) => string;
@@ -6,3 +6,4 @@ export declare const strToUint8Array: (str: string) => Uint8Array;
6
6
  export declare const definitionFlow: (clusterDefinition: ClusterDefinition) => DefinitionFlow | null;
7
7
  export declare const findDeployedBytecode: (contractAddress: string, provider: Provider) => Promise<string>;
8
8
  export declare const isContractAvailable: (contractAddress: string, provider: Provider, bytecode?: string) => Promise<boolean>;
9
+ export declare const getProvider: (chainId: number) => ethers.Provider;
@@ -0,0 +1,19 @@
1
+ import { type TypedMessage } from '@metamask/eth-sig-util';
2
+ import { type EIP712TypedData } from '@safe-global/safe-core-sdk-types';
3
+ export declare const validateAddressSignature: ({ address, token, data, chainId, }: {
4
+ address: string;
5
+ token: string;
6
+ data: TypedMessage<any>;
7
+ chainId: number;
8
+ }) => Promise<boolean>;
9
+ export declare const validateEOASignature: ({ token, data, address, }: {
10
+ token: string;
11
+ data: TypedMessage<any>;
12
+ address: string;
13
+ }) => boolean;
14
+ export declare const validateSmartContractSignature: ({ token, data, address, chainId, }: {
15
+ token: string;
16
+ data: EIP712TypedData;
17
+ address: string;
18
+ chainId: number;
19
+ }) => Promise<boolean>;
@@ -212,3 +212,54 @@ export declare const nullDepositAmountsClusterLockV1X8: {
212
212
  lock_hash: string;
213
213
  node_signatures: string[];
214
214
  };
215
+ export declare const clusterLockWithSafe: {
216
+ cluster_definition: {
217
+ name: string;
218
+ creator: {
219
+ address: string;
220
+ config_signature: string;
221
+ };
222
+ operators: {
223
+ address: string;
224
+ enr: string;
225
+ config_signature: string;
226
+ enr_signature: string;
227
+ }[];
228
+ uuid: string;
229
+ version: string;
230
+ timestamp: string;
231
+ num_validators: number;
232
+ threshold: number;
233
+ validators: {
234
+ fee_recipient_address: string;
235
+ withdrawal_address: string;
236
+ }[];
237
+ dkg_algorithm: string;
238
+ fork_version: string;
239
+ deposit_amounts: string[];
240
+ config_hash: string;
241
+ definition_hash: string;
242
+ };
243
+ distributed_validators: {
244
+ distributed_public_key: string;
245
+ public_shares: string[];
246
+ builder_registration: {
247
+ message: {
248
+ fee_recipient: string;
249
+ gas_limit: number;
250
+ timestamp: number;
251
+ pubkey: string;
252
+ };
253
+ signature: string;
254
+ };
255
+ partial_deposit_data: {
256
+ pubkey: string;
257
+ withdrawal_credentials: string;
258
+ amount: string;
259
+ signature: string;
260
+ }[];
261
+ }[];
262
+ signature_aggregate: string;
263
+ lock_hash: string;
264
+ node_signatures: string[];
265
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obolnetwork/obol-sdk",
3
- "version": "2.1.1",
3
+ "version": "2.2.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"
@@ -44,9 +44,12 @@
44
44
  "@chainsafe/discv5": "^0.5.1",
45
45
  "@chainsafe/ssz": "^0.14.0",
46
46
  "@metamask/eth-sig-util": "^7.0.1",
47
+ "@safe-global/protocol-kit": "4.1.1",
48
+ "@safe-global/safe-core-sdk-types": "5.1.0",
47
49
  "@types/pdf-parse": "^1.1.4",
48
50
  "ajv": "^8.12.0",
49
51
  "cross-fetch": "^3.1.5",
52
+ "dotenv": "^16.4.7",
50
53
  "elliptic": "^6.5.4",
51
54
  "eslint-config-standard-with-typescript": "^43.0.1",
52
55
  "eslint-plugin-import": "^2.29.1",
package/src/constants.ts CHANGED
@@ -11,6 +11,9 @@ import {
11
11
  MAINNET_SPLITMAIN_BYTECODE,
12
12
  } from './bytecodes';
13
13
 
14
+ import * as dotenv from 'dotenv';
15
+ dotenv.config();
16
+
14
17
  export const CONFLICT_ERROR_MSG = 'Conflict';
15
18
 
16
19
  export const EIP712_DOMAIN_NAME = 'Obol';
@@ -195,3 +198,10 @@ export const DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT = 1;
195
198
  export const DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT = 0.1;
196
199
 
197
200
  export const OBOL_SDK_EMAIL = 'sdk@dvlabs.tech';
201
+
202
+ export const PROVIDER_MAP: Record<number, string> = {
203
+ 1: `${process.env.RPC_MAINNET}`, // Mainnet
204
+ 17000: `${process.env.RPC_HOLESKY}`, // Holesky
205
+ 11155111: `${process.env.RPC_SEPOLIA}`, // Sepolia
206
+ 100: `${process.env.RPC_GNOSIS}`, // Gnosis
207
+ };
package/src/index.ts CHANGED
@@ -49,6 +49,7 @@ import {
49
49
  import { isContractAvailable } from './utils.js';
50
50
  export * from './types.js';
51
51
  export * from './services.js';
52
+ export * from './verification/signature-validator.js';
52
53
 
53
54
  /**
54
55
  * Obol sdk Client can be used for creating, managing and activating distributed validators.
@@ -91,6 +92,7 @@ export class Client extends Base {
91
92
  address,
92
93
  version: TERMS_AND_CONDITIONS_VERSION,
93
94
  terms_and_conditions_hash: termsAndConditionsHash,
95
+ fork_version: this.fork_version,
94
96
  };
95
97
 
96
98
  const termsAndConditionsSignature = await this.signer.signTypedData(
package/src/types.ts CHANGED
@@ -13,8 +13,31 @@ export enum FORK_MAPPING {
13
13
 
14
14
  /** Holesky. */
15
15
  '0x01017000' = 17000,
16
+
17
+ /** Sepolia. */
18
+ '0x90000069' = 11155111,
16
19
  }
17
20
 
21
+ /**
22
+ * Permitted Chain Names
23
+ */
24
+ export const FORK_NAMES: Record<number, string> = {
25
+ /** Mainnet. */
26
+ [FORK_MAPPING['0x00000000']]: 'mainnet',
27
+
28
+ /** Goerli/Prater. */
29
+ [FORK_MAPPING['0x00001020']]: 'goerli',
30
+
31
+ /** Gnosis Chain. */
32
+ [FORK_MAPPING['0x00000064']]: 'gnosis',
33
+
34
+ /** Holesky. */
35
+ [FORK_MAPPING['0x01017000']]: 'holesky',
36
+
37
+ /** Sepolia. */
38
+ [FORK_MAPPING['0x90000069']]: 'sepolia',
39
+ };
40
+
18
41
  /**
19
42
  * Node operator data
20
43
  */
package/src/utils.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { type Provider } from 'ethers';
2
- import { DefinitionFlow } from './constants';
3
- import { type ClusterDefinition } from './types';
1
+ import { ethers, type Provider } from 'ethers';
2
+ import { DefinitionFlow, PROVIDER_MAP } from './constants';
3
+ import { FORK_NAMES, type ClusterDefinition } from './types';
4
4
 
5
5
  export const hexWithout0x = (hex: string): string => {
6
6
  return hex.slice(2, hex.length);
@@ -75,3 +75,11 @@ export const isContractAvailable = async (
75
75
  }
76
76
  return !!code && code !== '0x' && code !== '0x0';
77
77
  };
78
+
79
+ export const getProvider = (chainId: number): ethers.Provider => {
80
+ const rpcUrl = PROVIDER_MAP[chainId];
81
+ if (!rpcUrl) {
82
+ throw new Error(`No provider configured for ${FORK_NAMES[chainId]}`);
83
+ }
84
+ return new ethers.JsonRpcProvider(rpcUrl);
85
+ };