@obolnetwork/obol-sdk 2.4.4 → 2.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/package.json +6 -5
  3. package/dist/cjs/src/incentiveHelpers.js +2 -4
  4. package/dist/cjs/src/incentives.js +26 -5
  5. package/dist/cjs/src/index.js +3 -1
  6. package/dist/cjs/src/services.js +3 -2
  7. package/dist/cjs/src/types.js +0 -9
  8. package/dist/cjs/src/utils.js +4 -4
  9. package/dist/cjs/src/verification/common.js +12 -9
  10. package/dist/cjs/src/verification/signature-validator.js +6 -5
  11. package/dist/cjs/test/incentives.test.js +4 -4
  12. package/dist/cjs/test/methods.test.js +11 -0
  13. package/dist/esm/package.json +6 -5
  14. package/dist/esm/src/incentiveHelpers.js +3 -5
  15. package/dist/esm/src/incentives.js +26 -5
  16. package/dist/esm/src/index.js +2 -1
  17. package/dist/esm/src/services.js +3 -2
  18. package/dist/esm/src/splitHelpers.js +1 -1
  19. package/dist/esm/src/types.js +0 -9
  20. package/dist/esm/src/utils.js +4 -4
  21. package/dist/esm/src/verification/common.js +12 -9
  22. package/dist/esm/src/verification/signature-validator.js +6 -5
  23. package/dist/esm/test/incentives.test.js +4 -4
  24. package/dist/esm/test/methods.test.js +11 -0
  25. package/dist/types/src/incentiveHelpers.d.ts +3 -4
  26. package/dist/types/src/incentives.d.ts +29 -13
  27. package/dist/types/src/index.d.ts +15 -4
  28. package/dist/types/src/services.d.ts +3 -2
  29. package/dist/types/src/splitHelpers.d.ts +7 -8
  30. package/dist/types/src/types.d.ts +21 -5
  31. package/dist/types/src/utils.d.ts +5 -5
  32. package/dist/types/src/verification/common.d.ts +2 -2
  33. package/dist/types/src/verification/signature-validator.d.ts +5 -2
  34. package/package.json +6 -5
  35. package/src/incentiveHelpers.ts +5 -15
  36. package/src/incentives.ts +37 -33
  37. package/src/index.ts +27 -23
  38. package/src/services.ts +4 -2
  39. package/src/splitHelpers.ts +9 -14
  40. package/src/types.ts +37 -17
  41. package/src/utils.ts +11 -8
  42. package/src/verification/common.ts +13 -1
  43. package/src/verification/signature-validator.ts +10 -3
package/README.md CHANGED
@@ -19,7 +19,7 @@ If you're integrating this SDK with a **backend** (e.g., in Node.js), and you st
19
19
 
20
20
  ## ⚡️ Integration with Safe Wallet
21
21
 
22
- When integrating the Obol SDK with a **Safe Wallet**, you will need to provide the `RPC_MAINNET` or `RPC_HOLESKY` or `RPC_GNOSIS` or `RPC_SEPOLIA` environment variable, pointing to the correct network's RPC URL. This is required to interact with Safe kit.
22
+ When integrating the Obol SDK with a **Safe Wallet**, you can either pass an RPC URL OR provide the `RPC_MAINNET` or `RPC_HOLESKY` or `RPC_GNOSIS` or `RPC_SEPOLIA` environment variable, pointing to the correct network's RPC URL. This is required to interact with Safe kit.
23
23
 
24
24
 
25
25
  ## Contributing
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obolnetwork/obol-sdk",
3
- "version": "2.4.4",
3
+ "version": "2.4.6",
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"
@@ -23,7 +23,8 @@
23
23
  "lint": "eslint \"{src,test}/**/*.{js,ts}\" --fix",
24
24
  "lint-ci": "eslint \"{src,test}/**/*.{js,ts}\"",
25
25
  "prettier-ci": "prettier --check \"{src,test}/**/*.{js,ts}\"",
26
- "prettier": "prettier --write \"{src,test}/**/*.{js,ts}\""
26
+ "prettier": "prettier --write \"{src,test}/**/*.{js,ts}\"",
27
+ "docs": "typedoc"
27
28
  },
28
29
  "main": "./dist/cjs/src/index.js",
29
30
  "module": "./dist/esm/src/index.js",
@@ -55,7 +56,7 @@
55
56
  "eslint-plugin-import": "^2.29.1",
56
57
  "eslint-plugin-n": "^16.6.2",
57
58
  "eslint-plugin-promise": "^6.1.1",
58
- "ethers": "6.4.0",
59
+ "ethers": "^6.13.5",
59
60
  "nock": "^13.5.3",
60
61
  "pdf-parse": "^1.1.1",
61
62
  "semver": "^7.6.0",
@@ -79,8 +80,8 @@
79
80
  "release-it": "^17.2.1",
80
81
  "ts-jest": "^28.0.8",
81
82
  "tsup": "^6.7.0",
82
- "typedoc": "^0.25.7",
83
- "typedoc-plugin-markdown": "^4.0.0-next.50",
83
+ "typedoc": "^0.28.0",
84
+ "typedoc-plugin-markdown": "^4.5.2",
84
85
  "typescript": "~5.3.3"
85
86
  },
86
87
  "engines": {
@@ -12,7 +12,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.isClaimedFromMerkleDistributor = exports.claimIncentivesFromMerkleDistributor = void 0;
13
13
  const ethers_1 = require("ethers");
14
14
  const MerkleDistributorWithDeadline_1 = require("./abi/MerkleDistributorWithDeadline");
15
- const utils_1 = require("./utils");
16
15
  const claimIncentivesFromMerkleDistributor = (incentivesData) => __awaiter(void 0, void 0, void 0, function* () {
17
16
  try {
18
17
  const contract = new ethers_1.Contract(incentivesData.contractAddress, MerkleDistributorWithDeadline_1.MerkleDistributorABI.abi, incentivesData.signer);
@@ -26,10 +25,9 @@ const claimIncentivesFromMerkleDistributor = (incentivesData) => __awaiter(void
26
25
  }
27
26
  });
28
27
  exports.claimIncentivesFromMerkleDistributor = claimIncentivesFromMerkleDistributor;
29
- const isClaimedFromMerkleDistributor = (chainId, contractAddress, index, provider) => __awaiter(void 0, void 0, void 0, function* () {
28
+ const isClaimedFromMerkleDistributor = (contractAddress, index, provider) => __awaiter(void 0, void 0, void 0, function* () {
30
29
  try {
31
- const clientProvider = provider !== null && provider !== void 0 ? provider : (0, utils_1.getProvider)(chainId);
32
- const contract = new ethers_1.Contract(contractAddress, MerkleDistributorWithDeadline_1.MerkleDistributorABI.abi, clientProvider);
30
+ const contract = new ethers_1.Contract(contractAddress, MerkleDistributorWithDeadline_1.MerkleDistributorABI.abi, provider);
33
31
  const claimed = yield contract.isClaimed(BigInt(index));
34
32
  return claimed;
35
33
  }
@@ -14,6 +14,14 @@ const utils_1 = require("./utils");
14
14
  const types_1 = require("./types");
15
15
  const incentiveHelpers_1 = require("./incentiveHelpers");
16
16
  const constants_1 = require("./constants");
17
+ /**
18
+ * Incentives can be used for fetching and claiming Obol incentives.
19
+ * @class
20
+ * @internal Access it through Client.incentives.
21
+ * @example
22
+ * const obolClient = new Client(config);
23
+ * await obolClient.incentives.claimIncentives(address);
24
+ */
17
25
  class Incentives {
18
26
  constructor(signer, chainId, request, provider) {
19
27
  this.signer = signer;
@@ -22,16 +30,23 @@ class Incentives {
22
30
  this.provider = provider;
23
31
  }
24
32
  /**
25
- * Claims obol incentives from a Merkle Distributor contract using just an address.
26
- * The method automatically fetches incentives data and checks if already claimed.
33
+ * Claims Obol incentives from a Merkle Distributor contract using an address.
34
+ *
35
+ * This method automatically fetches incentive data and verifies whether the incentives have already been claimed.
36
+ * If `txHash` is `null`, it indicates that the incentives were already claimed.
37
+ *
38
+ * Note: This method is not yet enabled and will throw an error if called.
27
39
  *
28
40
  * @remarks
29
41
  * **⚠️ Important:** If you're storing the private key in an `.env` file, ensure it is securely managed
30
42
  * and not pushed to version control.
31
43
  *
32
44
  * @param {string} address - The address to claim incentives for
33
- * @returns {Promise<{ txHash: string } | { alreadyClaimed: true }>} The transaction hash or already claimed status
45
+ * @returns {Promise<ClaimIncentivesResponse>} The transaction hash or already claimed status
34
46
  * @throws Will throw an error if the incentives data is not found or the claim fails
47
+ *
48
+ * An example of how to use claimIncentives:
49
+ * [obolClient](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L281)
35
50
  */
36
51
  claimIncentives(address) {
37
52
  return __awaiter(this, void 0, void 0, function* () {
@@ -49,7 +64,7 @@ class Incentives {
49
64
  }
50
65
  const claimed = yield this.isClaimed(incentivesData.contract_address, incentivesData.index);
51
66
  if (claimed) {
52
- return { alreadyClaimed: true };
67
+ return { txHash: null };
53
68
  }
54
69
  const { txHash } = yield (0, incentiveHelpers_1.claimIncentivesFromMerkleDistributor)({
55
70
  signer: this.signer,
@@ -74,16 +89,22 @@ class Incentives {
74
89
  * @param {ETH_ADDRESS} index - operator index in merkle tree
75
90
  * @returns {Promise<boolean>} true if incentives are already claime
76
91
  *
92
+ *
93
+ * An example of how to use isClaimed:
94
+ * [obolClient](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L266)
77
95
  */
78
96
  isClaimed(contractAddress, index) {
79
97
  return __awaiter(this, void 0, void 0, function* () {
80
- return yield (0, incentiveHelpers_1.isClaimedFromMerkleDistributor)(this.chainId, contractAddress, index, this.provider);
98
+ return yield (0, incentiveHelpers_1.isClaimedFromMerkleDistributor)(contractAddress, index, this.provider);
81
99
  });
82
100
  }
83
101
  /**
84
102
  * @param address - Operator address
85
103
  * @returns {Promise<IncentivesType>} The matched incentives from DB
86
104
  * @throws On not found if address not found.
105
+ *
106
+ * An example of how to use getIncentivesByAddress:
107
+ * [obolClient](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L250)
87
108
  */
88
109
  getIncentivesByAddress(address) {
89
110
  return __awaiter(this, void 0, void 0, function* () {
@@ -23,7 +23,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
23
23
  });
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.Client = void 0;
26
+ exports.Client = exports.Incentives = void 0;
27
27
  const ethers_1 = require("ethers");
28
28
  const uuid_1 = require("uuid");
29
29
  const base_js_1 = require("./base.js");
@@ -39,6 +39,8 @@ __exportStar(require("./types.js"), exports);
39
39
  __exportStar(require("./services.js"), exports);
40
40
  __exportStar(require("./verification/signature-validator.js"), exports);
41
41
  __exportStar(require("./verification/common.js"), exports);
42
+ var incentives_js_2 = require("./incentives.js");
43
+ Object.defineProperty(exports, "Incentives", { enumerable: true, get: function () { return incentives_js_2.Incentives; } });
42
44
  /**
43
45
  * Obol sdk Client can be used for creating, managing and activating distributed validators.
44
46
  */
@@ -14,15 +14,16 @@ const common_js_1 = require("./verification/common.js");
14
14
  /**
15
15
  * Verifies Cluster Lock's validity.
16
16
  * @param lock - cluster lock
17
+ * @param safeRpcUrl - optional safeRpcUrl for safe wallet verification
17
18
  * @returns {Promise<{ result: boolean }> } boolean result to indicate if lock is valid
18
19
  * @throws on missing keys or values.
19
20
  *
20
21
  * An example of how to use validateClusterLock:
21
22
  * [validateClusterLock](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L127)
22
23
  */
23
- const validateClusterLock = (lock) => __awaiter(void 0, void 0, void 0, function* () {
24
+ const validateClusterLock = (lock, safeRpcUrl) => __awaiter(void 0, void 0, void 0, function* () {
24
25
  try {
25
- const isLockValid = yield (0, common_js_1.isValidClusterLock)(lock);
26
+ const isLockValid = yield (0, common_js_1.isValidClusterLock)(lock, safeRpcUrl);
26
27
  return isLockValid;
27
28
  }
28
29
  catch (err) {
@@ -19,20 +19,11 @@ var FORK_MAPPING;
19
19
  /** Hoodi Chain. */
20
20
  FORK_MAPPING[FORK_MAPPING["0x10000910"] = 560048] = "0x10000910";
21
21
  })(FORK_MAPPING || (exports.FORK_MAPPING = FORK_MAPPING = {}));
22
- /**
23
- * Permitted Chain Names
24
- */
25
22
  exports.FORK_NAMES = {
26
- /** Mainnet. */
27
23
  [FORK_MAPPING['0x00000000']]: 'mainnet',
28
- /** Goerli/Prater. */
29
24
  [FORK_MAPPING['0x00001020']]: 'goerli',
30
- /** Gnosis Chain. */
31
25
  [FORK_MAPPING['0x00000064']]: 'gnosis',
32
- /** Holesky. */
33
26
  [FORK_MAPPING['0x01017000']]: 'holesky',
34
- /** Sepolia. */
35
27
  [FORK_MAPPING['0x90000069']]: 'sepolia',
36
- /** Hoodi. */
37
28
  [FORK_MAPPING['0x10000910']]: 'hoodi',
38
29
  };
@@ -67,11 +67,11 @@ const isContractAvailable = (contractAddress, provider, bytecode) => __awaiter(v
67
67
  return !!code && code !== '0x' && code !== '0x0';
68
68
  });
69
69
  exports.isContractAvailable = isContractAvailable;
70
- const getProvider = (chainId) => {
71
- const rpcUrl = constants_1.PROVIDER_MAP[chainId];
72
- if (!rpcUrl || rpcUrl === 'undefined') {
70
+ const getProvider = (chainId, rpcUrl) => {
71
+ const resolvedRpcUrl = rpcUrl !== null && rpcUrl !== void 0 ? rpcUrl : constants_1.PROVIDER_MAP[chainId];
72
+ if (chainId && (!resolvedRpcUrl || resolvedRpcUrl === 'undefined')) {
73
73
  throw new Error(`No provider configured for ${types_1.FORK_NAMES[chainId]}`);
74
74
  }
75
- return new ethers_1.ethers.JsonRpcProvider(rpcUrl);
75
+ return new ethers_1.ethers.JsonRpcProvider(resolvedRpcUrl);
76
76
  };
77
77
  exports.getProvider = getProvider;
@@ -131,7 +131,7 @@ const clusterLockHash = (clusterLock) => {
131
131
  exports.clusterLockHash = clusterLockHash;
132
132
  // Lock verification
133
133
  // cluster-definition signatures verification
134
- const validatePOSTConfigHashSigner = (address, signature, configHash, chainId) => __awaiter(void 0, void 0, void 0, function* () {
134
+ const validatePOSTConfigHashSigner = (address, signature, configHash, chainId, safeRpcUrl) => __awaiter(void 0, void 0, void 0, function* () {
135
135
  try {
136
136
  const data = (0, constants_js_1.signCreatorConfigHashPayload)({ creator_config_hash: configHash }, chainId);
137
137
  return yield (0, signature_validator_js_1.validateAddressSignature)({
@@ -139,13 +139,14 @@ const validatePOSTConfigHashSigner = (address, signature, configHash, chainId) =
139
139
  token: signature,
140
140
  data,
141
141
  chainId,
142
+ safeRpcUrl,
142
143
  });
143
144
  }
144
145
  catch (err) {
145
146
  throw err;
146
147
  }
147
148
  });
148
- const validatePUTConfigHashSigner = (address, signature, configHash, chainId) => __awaiter(void 0, void 0, void 0, function* () {
149
+ const validatePUTConfigHashSigner = (address, signature, configHash, chainId, safeRpcUrl) => __awaiter(void 0, void 0, void 0, function* () {
149
150
  try {
150
151
  const data = (0, constants_js_1.signOperatorConfigHashPayload)({ operator_config_hash: configHash }, chainId);
151
152
  return yield (0, signature_validator_js_1.validateAddressSignature)({
@@ -153,13 +154,14 @@ const validatePUTConfigHashSigner = (address, signature, configHash, chainId) =>
153
154
  token: signature,
154
155
  data,
155
156
  chainId,
157
+ safeRpcUrl,
156
158
  });
157
159
  }
158
160
  catch (err) {
159
161
  throw err;
160
162
  }
161
163
  });
162
- const validateEnrSigner = (address, signature, payload, chainId) => __awaiter(void 0, void 0, void 0, function* () {
164
+ const validateEnrSigner = (address, signature, payload, chainId, safeRpcUrl) => __awaiter(void 0, void 0, void 0, function* () {
163
165
  try {
164
166
  const data = (0, constants_js_1.signEnrPayload)({ enr: payload }, chainId);
165
167
  return yield (0, signature_validator_js_1.validateAddressSignature)({
@@ -167,18 +169,19 @@ const validateEnrSigner = (address, signature, payload, chainId) => __awaiter(vo
167
169
  token: signature,
168
170
  data,
169
171
  chainId,
172
+ safeRpcUrl,
170
173
  });
171
174
  }
172
175
  catch (err) {
173
176
  throw err;
174
177
  }
175
178
  });
176
- const verifyDefinitionSignatures = (clusterDefinition, definitionType) => __awaiter(void 0, void 0, void 0, function* () {
179
+ const verifyDefinitionSignatures = (clusterDefinition, definitionType, safeRpcUrl) => __awaiter(void 0, void 0, void 0, function* () {
177
180
  if (definitionType === constants_js_1.DefinitionFlow.Charon) {
178
181
  return true;
179
182
  }
180
183
  else {
181
- const isPOSTConfigHashSignerValid = yield validatePOSTConfigHashSigner(clusterDefinition.creator.address, clusterDefinition.creator.config_signature, clusterDefinition.config_hash, types_js_1.FORK_MAPPING[clusterDefinition.fork_version]);
184
+ const isPOSTConfigHashSignerValid = yield validatePOSTConfigHashSigner(clusterDefinition.creator.address, clusterDefinition.creator.config_signature, clusterDefinition.config_hash, types_js_1.FORK_MAPPING[clusterDefinition.fork_version], safeRpcUrl);
182
185
  if (!isPOSTConfigHashSignerValid) {
183
186
  return false;
184
187
  }
@@ -186,8 +189,8 @@ const verifyDefinitionSignatures = (clusterDefinition, definitionType) => __awai
186
189
  return true;
187
190
  }
188
191
  for (const operator of clusterDefinition.operators) {
189
- const isPUTConfigHashSignerValid = yield validatePUTConfigHashSigner(operator.address, operator.config_signature, clusterDefinition.config_hash, types_js_1.FORK_MAPPING[clusterDefinition.fork_version]);
190
- const isENRSignerValid = yield validateEnrSigner(operator.address, operator.enr_signature, operator.enr, types_js_1.FORK_MAPPING[clusterDefinition.fork_version]);
192
+ const isPUTConfigHashSignerValid = yield validatePUTConfigHashSigner(operator.address, operator.config_signature, clusterDefinition.config_hash, types_js_1.FORK_MAPPING[clusterDefinition.fork_version], safeRpcUrl);
193
+ const isENRSignerValid = yield validateEnrSigner(operator.address, operator.enr_signature, operator.enr, types_js_1.FORK_MAPPING[clusterDefinition.fork_version], safeRpcUrl);
191
194
  if (!isPUTConfigHashSignerValid || !isENRSignerValid) {
192
195
  return false;
193
196
  }
@@ -322,13 +325,13 @@ const verifyLockData = (clusterLock) => __awaiter(void 0, void 0, void 0, functi
322
325
  }
323
326
  return false;
324
327
  });
325
- const isValidClusterLock = (clusterLock) => __awaiter(void 0, void 0, void 0, function* () {
328
+ const isValidClusterLock = (clusterLock, safeRpcUrl) => __awaiter(void 0, void 0, void 0, function* () {
326
329
  try {
327
330
  const definitionType = (0, utils_js_1.definitionFlow)(clusterLock.cluster_definition);
328
331
  if (definitionType == null) {
329
332
  return false;
330
333
  }
331
- const isValidDefinitionData = yield verifyDefinitionSignatures(clusterLock.cluster_definition, definitionType);
334
+ const isValidDefinitionData = yield verifyDefinitionSignatures(clusterLock.cluster_definition, definitionType, safeRpcUrl);
332
335
  if (!isValidDefinitionData) {
333
336
  return false;
334
337
  }
@@ -20,9 +20,9 @@ const protocol_kit_1 = __importDefault(require("@safe-global/protocol-kit"));
20
20
  const constants_1 = require("../constants");
21
21
  const utils_1 = require("@safe-global/protocol-kit/dist/src/utils");
22
22
  const utils_2 = require("../utils");
23
- const validateAddressSignature = ({ address, token, data, chainId, }) => __awaiter(void 0, void 0, void 0, function* () {
23
+ const validateAddressSignature = ({ address, token, data, chainId, safeRpcUrl, }) => __awaiter(void 0, void 0, void 0, function* () {
24
24
  try {
25
- const provider = (0, utils_2.getProvider)(chainId); // [TODO Hanan], should expect it from signer.provider, or passed in client
25
+ const provider = (0, utils_2.getProvider)(chainId, safeRpcUrl);
26
26
  if (provider) {
27
27
  const contractAddress = yield (0, utils_2.isContractAvailable)(address, provider);
28
28
  if (contractAddress) {
@@ -31,6 +31,7 @@ const validateAddressSignature = ({ address, token, data, chainId, }) => __await
31
31
  data: data,
32
32
  address,
33
33
  chainId,
34
+ safeRpcUrl,
34
35
  });
35
36
  }
36
37
  }
@@ -54,11 +55,11 @@ const validateEOASignature = ({ token, data, address, }) => {
54
55
  }
55
56
  };
56
57
  exports.validateEOASignature = validateEOASignature;
57
- const validateSmartContractSignature = ({ token, data, address, chainId, }) => __awaiter(void 0, void 0, void 0, function* () {
58
+ const validateSmartContractSignature = ({ token, data, address, chainId, safeRpcUrl, }) => __awaiter(void 0, void 0, void 0, function* () {
58
59
  try {
59
- const provider = constants_1.PROVIDER_MAP[chainId];
60
+ const safeProvider = safeRpcUrl !== null && safeRpcUrl !== void 0 ? safeRpcUrl : constants_1.PROVIDER_MAP[chainId];
60
61
  const protocolKit = yield protocol_kit_1.default.init({
61
- provider,
62
+ provider: safeProvider,
62
63
  safeAddress: address,
63
64
  });
64
65
  const messageHash = (0, utils_1.hashTypedData)(data);
@@ -104,13 +104,13 @@ global.fetch = globals_1.jest.fn();
104
104
  merkleProof: mockIncentivesData.merkle_proof,
105
105
  });
106
106
  }));
107
- (0, globals_1.test)('claimIncentives should return alreadyClaimed when incentives are already claimed', () => __awaiter(void 0, void 0, void 0, function* () {
107
+ (0, globals_1.test)('claimIncentives should return txHash as null when incentives are already claimed', () => __awaiter(void 0, void 0, void 0, function* () {
108
108
  globals_1.jest
109
109
  .spyOn(clientInstance.incentives, 'getIncentivesByAddress')
110
110
  .mockResolvedValue(mockIncentivesData);
111
111
  globals_1.jest.spyOn(clientInstance.incentives, 'isClaimed').mockResolvedValue(true);
112
112
  const result = yield clientInstance.incentives.claimIncentives(mockIncentivesData.operator_address);
113
- (0, globals_1.expect)(result).toEqual({ alreadyClaimed: true });
113
+ (0, globals_1.expect)(result).toEqual({ txHash: null });
114
114
  (0, globals_1.expect)(incentivesHelpers.claimIncentivesFromMerkleDistributor).not.toHaveBeenCalled();
115
115
  }));
116
116
  (0, globals_1.test)('claimIncentives should throw an error if no incentives found for address', () => __awaiter(void 0, void 0, void 0, function* () {
@@ -145,7 +145,7 @@ global.fetch = globals_1.jest.fn();
145
145
  .mockImplementation(() => __awaiter(void 0, void 0, void 0, function* () { return yield Promise.resolve(true); }));
146
146
  const result = yield clientInstance.incentives.isClaimed(mockIncentivesData.contract_address, mockIncentivesData.index);
147
147
  (0, globals_1.expect)(result).toBe(true);
148
- (0, globals_1.expect)(incentivesHelpers.isClaimedFromMerkleDistributor).toHaveBeenCalledWith(clientInstance.incentives.chainId, mockIncentivesData.contract_address, mockIncentivesData.index, {});
148
+ (0, globals_1.expect)(incentivesHelpers.isClaimedFromMerkleDistributor).toHaveBeenCalledWith(mockIncentivesData.contract_address, mockIncentivesData.index, {});
149
149
  }));
150
150
  (0, globals_1.test)('isClaimed should return false when incentive is not claimed', () => __awaiter(void 0, void 0, void 0, function* () {
151
151
  globals_1.jest
@@ -172,7 +172,7 @@ global.fetch = globals_1.jest.fn();
172
172
  .mockImplementation(() => __awaiter(void 0, void 0, void 0, function* () { return yield Promise.resolve(true); }));
173
173
  const result = yield clientWithoutSigner.incentives.isClaimed(mockIncentivesData.contract_address, mockIncentivesData.index);
174
174
  (0, globals_1.expect)(result).toBe(true);
175
- (0, globals_1.expect)(incentivesHelpers.isClaimedFromMerkleDistributor).toHaveBeenCalledWith(clientWithoutSigner.incentives.chainId, mockIncentivesData.contract_address, mockIncentivesData.index, provider);
175
+ (0, globals_1.expect)(incentivesHelpers.isClaimedFromMerkleDistributor).toHaveBeenCalledWith(mockIncentivesData.contract_address, mockIncentivesData.index, provider);
176
176
  }));
177
177
  (0, globals_1.test)('getIncentivesByAddress should make the correct API request', () => __awaiter(void 0, void 0, void 0, function* () {
178
178
  const mockAddress = '0x1234567890abcdef1234567890abcdef12345678';
@@ -165,6 +165,9 @@ describe('Cluster Client without a signer', () => {
165
165
  beforeAll(() => {
166
166
  jest.restoreAllMocks();
167
167
  });
168
+ beforeEach(() => {
169
+ jest.resetModules();
170
+ });
168
171
  test('createClusterDefinition should throw an error without signer', () => __awaiter(void 0, void 0, void 0, function* () {
169
172
  try {
170
173
  yield clientInstance.createClusterDefinition(fixtures_js_1.clusterConfigV1X8);
@@ -219,6 +222,14 @@ describe('Cluster Client without a signer', () => {
219
222
  const isValidLock = yield (0, index_1.validateClusterLock)(clusterLock);
220
223
  expect(isValidLock).toEqual(true);
221
224
  }));
225
+ test('should return true on verified cluster lock with Safe wallet and safe rpc url', () => __awaiter(void 0, void 0, void 0, function* () {
226
+ process.env.RPC_HOLESKY = undefined;
227
+ /* eslint-disable @typescript-eslint/no-var-requires */
228
+ const { validateClusterLock: validateLockWithRpcUrl, } = require('../src/index');
229
+ const safeRpcUrl = 'https://ethereum-holesky-rpc.publicnode.com';
230
+ const isValidLock = yield validateLockWithRpcUrl(fixtures_js_1.clusterLockWithSafe, safeRpcUrl);
231
+ expect(isValidLock).toEqual(true);
232
+ }));
222
233
  test('validateCluster should return false for cluster with null deposit_amounts and incorrect partial_deposits', () => __awaiter(void 0, void 0, void 0, function* () {
223
234
  const partialDeposit = fixtures_js_1.nullDepositAmountsClusterLockV1X8.distributed_validators[0]
224
235
  .partial_deposit_data[0];
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obolnetwork/obol-sdk",
3
- "version": "2.4.4",
3
+ "version": "2.4.6",
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"
@@ -23,7 +23,8 @@
23
23
  "lint": "eslint \"{src,test}/**/*.{js,ts}\" --fix",
24
24
  "lint-ci": "eslint \"{src,test}/**/*.{js,ts}\"",
25
25
  "prettier-ci": "prettier --check \"{src,test}/**/*.{js,ts}\"",
26
- "prettier": "prettier --write \"{src,test}/**/*.{js,ts}\""
26
+ "prettier": "prettier --write \"{src,test}/**/*.{js,ts}\"",
27
+ "docs": "typedoc"
27
28
  },
28
29
  "main": "./dist/cjs/src/index.js",
29
30
  "module": "./dist/esm/src/index.js",
@@ -55,7 +56,7 @@
55
56
  "eslint-plugin-import": "^2.29.1",
56
57
  "eslint-plugin-n": "^16.6.2",
57
58
  "eslint-plugin-promise": "^6.1.1",
58
- "ethers": "6.4.0",
59
+ "ethers": "^6.13.5",
59
60
  "nock": "^13.5.3",
60
61
  "pdf-parse": "^1.1.1",
61
62
  "semver": "^7.6.0",
@@ -79,8 +80,8 @@
79
80
  "release-it": "^17.2.1",
80
81
  "ts-jest": "^28.0.8",
81
82
  "tsup": "^6.7.0",
82
- "typedoc": "^0.25.7",
83
- "typedoc-plugin-markdown": "^4.0.0-next.50",
83
+ "typedoc": "^0.28.0",
84
+ "typedoc-plugin-markdown": "^4.5.2",
84
85
  "typescript": "~5.3.3"
85
86
  },
86
87
  "engines": {
@@ -7,9 +7,8 @@ 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 { Contract, } from 'ethers';
10
+ import { Contract } from 'ethers';
11
11
  import { MerkleDistributorABI } from './abi/MerkleDistributorWithDeadline';
12
- import { getProvider } from './utils';
13
12
  export const claimIncentivesFromMerkleDistributor = (incentivesData) => __awaiter(void 0, void 0, void 0, function* () {
14
13
  try {
15
14
  const contract = new Contract(incentivesData.contractAddress, MerkleDistributorABI.abi, incentivesData.signer);
@@ -22,10 +21,9 @@ export const claimIncentivesFromMerkleDistributor = (incentivesData) => __awaite
22
21
  throw new Error(`Failed to claim incentives: ${error.message}`);
23
22
  }
24
23
  });
25
- export const isClaimedFromMerkleDistributor = (chainId, contractAddress, index, provider) => __awaiter(void 0, void 0, void 0, function* () {
24
+ export const isClaimedFromMerkleDistributor = (contractAddress, index, provider) => __awaiter(void 0, void 0, void 0, function* () {
26
25
  try {
27
- const clientProvider = provider !== null && provider !== void 0 ? provider : getProvider(chainId);
28
- const contract = new Contract(contractAddress, MerkleDistributorABI.abi, clientProvider);
26
+ const contract = new Contract(contractAddress, MerkleDistributorABI.abi, provider);
29
27
  const claimed = yield contract.isClaimed(BigInt(index));
30
28
  return claimed;
31
29
  }
@@ -11,6 +11,14 @@ import { isContractAvailable } from './utils';
11
11
  import { FORK_NAMES, } from './types';
12
12
  import { claimIncentivesFromMerkleDistributor, isClaimedFromMerkleDistributor, } from './incentiveHelpers';
13
13
  import { DEFAULT_BASE_VERSION } from './constants';
14
+ /**
15
+ * Incentives can be used for fetching and claiming Obol incentives.
16
+ * @class
17
+ * @internal Access it through Client.incentives.
18
+ * @example
19
+ * const obolClient = new Client(config);
20
+ * await obolClient.incentives.claimIncentives(address);
21
+ */
14
22
  export class Incentives {
15
23
  constructor(signer, chainId, request, provider) {
16
24
  this.signer = signer;
@@ -19,16 +27,23 @@ export class Incentives {
19
27
  this.provider = provider;
20
28
  }
21
29
  /**
22
- * Claims obol incentives from a Merkle Distributor contract using just an address.
23
- * The method automatically fetches incentives data and checks if already claimed.
30
+ * Claims Obol incentives from a Merkle Distributor contract using an address.
31
+ *
32
+ * This method automatically fetches incentive data and verifies whether the incentives have already been claimed.
33
+ * If `txHash` is `null`, it indicates that the incentives were already claimed.
34
+ *
35
+ * Note: This method is not yet enabled and will throw an error if called.
24
36
  *
25
37
  * @remarks
26
38
  * **⚠️ Important:** If you're storing the private key in an `.env` file, ensure it is securely managed
27
39
  * and not pushed to version control.
28
40
  *
29
41
  * @param {string} address - The address to claim incentives for
30
- * @returns {Promise<{ txHash: string } | { alreadyClaimed: true }>} The transaction hash or already claimed status
42
+ * @returns {Promise<ClaimIncentivesResponse>} The transaction hash or already claimed status
31
43
  * @throws Will throw an error if the incentives data is not found or the claim fails
44
+ *
45
+ * An example of how to use claimIncentives:
46
+ * [obolClient](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L281)
32
47
  */
33
48
  claimIncentives(address) {
34
49
  return __awaiter(this, void 0, void 0, function* () {
@@ -46,7 +61,7 @@ export class Incentives {
46
61
  }
47
62
  const claimed = yield this.isClaimed(incentivesData.contract_address, incentivesData.index);
48
63
  if (claimed) {
49
- return { alreadyClaimed: true };
64
+ return { txHash: null };
50
65
  }
51
66
  const { txHash } = yield claimIncentivesFromMerkleDistributor({
52
67
  signer: this.signer,
@@ -71,16 +86,22 @@ export class Incentives {
71
86
  * @param {ETH_ADDRESS} index - operator index in merkle tree
72
87
  * @returns {Promise<boolean>} true if incentives are already claime
73
88
  *
89
+ *
90
+ * An example of how to use isClaimed:
91
+ * [obolClient](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L266)
74
92
  */
75
93
  isClaimed(contractAddress, index) {
76
94
  return __awaiter(this, void 0, void 0, function* () {
77
- return yield isClaimedFromMerkleDistributor(this.chainId, contractAddress, index, this.provider);
95
+ return yield isClaimedFromMerkleDistributor(contractAddress, index, this.provider);
78
96
  });
79
97
  }
80
98
  /**
81
99
  * @param address - Operator address
82
100
  * @returns {Promise<IncentivesType>} The matched incentives from DB
83
101
  * @throws On not found if address not found.
102
+ *
103
+ * An example of how to use getIncentivesByAddress:
104
+ * [obolClient](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L250)
84
105
  */
85
106
  getIncentivesByAddress(address) {
86
107
  return __awaiter(this, void 0, void 0, function* () {
@@ -7,7 +7,7 @@ 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';
10
+ import { ZeroAddress } from 'ethers';
11
11
  import { v4 as uuidv4 } from 'uuid';
12
12
  import { Base } from './base.js';
13
13
  import { CONFLICT_ERROR_MSG, CreatorConfigHashSigningTypes, Domain, DKG_ALGORITHM, CONFIG_VERSION, OperatorConfigHashSigningTypes, EnrSigningTypes, TERMS_AND_CONDITIONS_VERSION, TermsAndConditionsSigningTypes, DEFAULT_BASE_VERSION, TERMS_AND_CONDITIONS_HASH, AVAILABLE_SPLITTER_CHAINS, CHAIN_CONFIGURATION, DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT, DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT, OBOL_SDK_EMAIL, } from './constants.js';
@@ -22,6 +22,7 @@ export * from './types.js';
22
22
  export * from './services.js';
23
23
  export * from './verification/signature-validator.js';
24
24
  export * from './verification/common.js';
25
+ export { Incentives } from './incentives.js';
25
26
  /**
26
27
  * Obol sdk Client can be used for creating, managing and activating distributed validators.
27
28
  */
@@ -11,15 +11,16 @@ import { isValidClusterLock } from './verification/common.js';
11
11
  /**
12
12
  * Verifies Cluster Lock's validity.
13
13
  * @param lock - cluster lock
14
+ * @param safeRpcUrl - optional safeRpcUrl for safe wallet verification
14
15
  * @returns {Promise<{ result: boolean }> } boolean result to indicate if lock is valid
15
16
  * @throws on missing keys or values.
16
17
  *
17
18
  * An example of how to use validateClusterLock:
18
19
  * [validateClusterLock](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L127)
19
20
  */
20
- export const validateClusterLock = (lock) => __awaiter(void 0, void 0, void 0, function* () {
21
+ export const validateClusterLock = (lock, safeRpcUrl) => __awaiter(void 0, void 0, void 0, function* () {
21
22
  try {
22
- const isLockValid = yield isValidClusterLock(lock);
23
+ const isLockValid = yield isValidClusterLock(lock, safeRpcUrl);
23
24
  return isLockValid;
24
25
  }
25
26
  catch (err) {
@@ -7,7 +7,7 @@ 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 { Contract, Interface, parseEther, ZeroAddress, } from 'ethers';
10
+ import { Contract, Interface, parseEther, ZeroAddress } from 'ethers';
11
11
  import { OWRContract, OWRFactoryContract } from './abi/OWR';
12
12
  import { splitMainEthereumAbi } from './abi/SplitMain';
13
13
  import { MultiCallContract } from './abi/Multicall';
@@ -16,20 +16,11 @@ export var FORK_MAPPING;
16
16
  /** Hoodi Chain. */
17
17
  FORK_MAPPING[FORK_MAPPING["0x10000910"] = 560048] = "0x10000910";
18
18
  })(FORK_MAPPING || (FORK_MAPPING = {}));
19
- /**
20
- * Permitted Chain Names
21
- */
22
19
  export const FORK_NAMES = {
23
- /** Mainnet. */
24
20
  [FORK_MAPPING['0x00000000']]: 'mainnet',
25
- /** Goerli/Prater. */
26
21
  [FORK_MAPPING['0x00001020']]: 'goerli',
27
- /** Gnosis Chain. */
28
22
  [FORK_MAPPING['0x00000064']]: 'gnosis',
29
- /** Holesky. */
30
23
  [FORK_MAPPING['0x01017000']]: 'holesky',
31
- /** Sepolia. */
32
24
  [FORK_MAPPING['0x90000069']]: 'sepolia',
33
- /** Hoodi. */
34
25
  [FORK_MAPPING['0x10000910']]: 'hoodi',
35
26
  };
@@ -59,10 +59,10 @@ export const isContractAvailable = (contractAddress, provider, bytecode) => __aw
59
59
  }
60
60
  return !!code && code !== '0x' && code !== '0x0';
61
61
  });
62
- export const getProvider = (chainId) => {
63
- const rpcUrl = PROVIDER_MAP[chainId];
64
- if (!rpcUrl || rpcUrl === 'undefined') {
62
+ export const getProvider = (chainId, rpcUrl) => {
63
+ const resolvedRpcUrl = rpcUrl !== null && rpcUrl !== void 0 ? rpcUrl : PROVIDER_MAP[chainId];
64
+ if (chainId && (!resolvedRpcUrl || resolvedRpcUrl === 'undefined')) {
65
65
  throw new Error(`No provider configured for ${FORK_NAMES[chainId]}`);
66
66
  }
67
- return new ethers.JsonRpcProvider(rpcUrl);
67
+ return new ethers.JsonRpcProvider(resolvedRpcUrl);
68
68
  };