@obolnetwork/obol-sdk 2.4.5 → 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.
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.5",
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"
@@ -56,7 +56,7 @@
56
56
  "eslint-plugin-import": "^2.29.1",
57
57
  "eslint-plugin-n": "^16.6.2",
58
58
  "eslint-plugin-promise": "^6.1.1",
59
- "ethers": "6.4.0",
59
+ "ethers": "^6.13.5",
60
60
  "nock": "^13.5.3",
61
61
  "pdf-parse": "^1.1.1",
62
62
  "semver": "^7.6.0",
@@ -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
  }
@@ -95,7 +95,7 @@ class Incentives {
95
95
  */
96
96
  isClaimed(contractAddress, index) {
97
97
  return __awaiter(this, void 0, void 0, function* () {
98
- return yield (0, incentiveHelpers_1.isClaimedFromMerkleDistributor)(this.chainId, contractAddress, index, this.provider);
98
+ return yield (0, incentiveHelpers_1.isClaimedFromMerkleDistributor)(contractAddress, index, this.provider);
99
99
  });
100
100
  }
101
101
  /**
@@ -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) {
@@ -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);
@@ -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.5",
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"
@@ -56,7 +56,7 @@
56
56
  "eslint-plugin-import": "^2.29.1",
57
57
  "eslint-plugin-n": "^16.6.2",
58
58
  "eslint-plugin-promise": "^6.1.1",
59
- "ethers": "6.4.0",
59
+ "ethers": "^6.13.5",
60
60
  "nock": "^13.5.3",
61
61
  "pdf-parse": "^1.1.1",
62
62
  "semver": "^7.6.0",
@@ -9,7 +9,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
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
  }
@@ -92,7 +92,7 @@ export class Incentives {
92
92
  */
93
93
  isClaimed(contractAddress, index) {
94
94
  return __awaiter(this, void 0, void 0, function* () {
95
- return yield isClaimedFromMerkleDistributor(this.chainId, contractAddress, index, this.provider);
95
+ return yield isClaimedFromMerkleDistributor(contractAddress, index, this.provider);
96
96
  });
97
97
  }
98
98
  /**
@@ -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) {
@@ -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
  };
@@ -100,7 +100,7 @@ export const clusterLockHash = (clusterLock) => {
100
100
  };
101
101
  // Lock verification
102
102
  // cluster-definition signatures verification
103
- const validatePOSTConfigHashSigner = (address, signature, configHash, chainId) => __awaiter(void 0, void 0, void 0, function* () {
103
+ const validatePOSTConfigHashSigner = (address, signature, configHash, chainId, safeRpcUrl) => __awaiter(void 0, void 0, void 0, function* () {
104
104
  try {
105
105
  const data = signCreatorConfigHashPayload({ creator_config_hash: configHash }, chainId);
106
106
  return yield validateAddressSignature({
@@ -108,13 +108,14 @@ const validatePOSTConfigHashSigner = (address, signature, configHash, chainId) =
108
108
  token: signature,
109
109
  data,
110
110
  chainId,
111
+ safeRpcUrl,
111
112
  });
112
113
  }
113
114
  catch (err) {
114
115
  throw err;
115
116
  }
116
117
  });
117
- const validatePUTConfigHashSigner = (address, signature, configHash, chainId) => __awaiter(void 0, void 0, void 0, function* () {
118
+ const validatePUTConfigHashSigner = (address, signature, configHash, chainId, safeRpcUrl) => __awaiter(void 0, void 0, void 0, function* () {
118
119
  try {
119
120
  const data = signOperatorConfigHashPayload({ operator_config_hash: configHash }, chainId);
120
121
  return yield validateAddressSignature({
@@ -122,13 +123,14 @@ const validatePUTConfigHashSigner = (address, signature, configHash, chainId) =>
122
123
  token: signature,
123
124
  data,
124
125
  chainId,
126
+ safeRpcUrl,
125
127
  });
126
128
  }
127
129
  catch (err) {
128
130
  throw err;
129
131
  }
130
132
  });
131
- const validateEnrSigner = (address, signature, payload, chainId) => __awaiter(void 0, void 0, void 0, function* () {
133
+ const validateEnrSigner = (address, signature, payload, chainId, safeRpcUrl) => __awaiter(void 0, void 0, void 0, function* () {
132
134
  try {
133
135
  const data = signEnrPayload({ enr: payload }, chainId);
134
136
  return yield validateAddressSignature({
@@ -136,18 +138,19 @@ const validateEnrSigner = (address, signature, payload, chainId) => __awaiter(vo
136
138
  token: signature,
137
139
  data,
138
140
  chainId,
141
+ safeRpcUrl,
139
142
  });
140
143
  }
141
144
  catch (err) {
142
145
  throw err;
143
146
  }
144
147
  });
145
- const verifyDefinitionSignatures = (clusterDefinition, definitionType) => __awaiter(void 0, void 0, void 0, function* () {
148
+ const verifyDefinitionSignatures = (clusterDefinition, definitionType, safeRpcUrl) => __awaiter(void 0, void 0, void 0, function* () {
146
149
  if (definitionType === DefinitionFlow.Charon) {
147
150
  return true;
148
151
  }
149
152
  else {
150
- const isPOSTConfigHashSignerValid = yield validatePOSTConfigHashSigner(clusterDefinition.creator.address, clusterDefinition.creator.config_signature, clusterDefinition.config_hash, FORK_MAPPING[clusterDefinition.fork_version]);
153
+ const isPOSTConfigHashSignerValid = yield validatePOSTConfigHashSigner(clusterDefinition.creator.address, clusterDefinition.creator.config_signature, clusterDefinition.config_hash, FORK_MAPPING[clusterDefinition.fork_version], safeRpcUrl);
151
154
  if (!isPOSTConfigHashSignerValid) {
152
155
  return false;
153
156
  }
@@ -155,8 +158,8 @@ const verifyDefinitionSignatures = (clusterDefinition, definitionType) => __awai
155
158
  return true;
156
159
  }
157
160
  for (const operator of clusterDefinition.operators) {
158
- const isPUTConfigHashSignerValid = yield validatePUTConfigHashSigner(operator.address, operator.config_signature, clusterDefinition.config_hash, FORK_MAPPING[clusterDefinition.fork_version]);
159
- const isENRSignerValid = yield validateEnrSigner(operator.address, operator.enr_signature, operator.enr, FORK_MAPPING[clusterDefinition.fork_version]);
161
+ const isPUTConfigHashSignerValid = yield validatePUTConfigHashSigner(operator.address, operator.config_signature, clusterDefinition.config_hash, FORK_MAPPING[clusterDefinition.fork_version], safeRpcUrl);
162
+ const isENRSignerValid = yield validateEnrSigner(operator.address, operator.enr_signature, operator.enr, FORK_MAPPING[clusterDefinition.fork_version], safeRpcUrl);
160
163
  if (!isPUTConfigHashSignerValid || !isENRSignerValid) {
161
164
  return false;
162
165
  }
@@ -287,13 +290,13 @@ const verifyLockData = (clusterLock) => __awaiter(void 0, void 0, void 0, functi
287
290
  }
288
291
  return false;
289
292
  });
290
- export const isValidClusterLock = (clusterLock) => __awaiter(void 0, void 0, void 0, function* () {
293
+ export const isValidClusterLock = (clusterLock, safeRpcUrl) => __awaiter(void 0, void 0, void 0, function* () {
291
294
  try {
292
295
  const definitionType = definitionFlow(clusterLock.cluster_definition);
293
296
  if (definitionType == null) {
294
297
  return false;
295
298
  }
296
- const isValidDefinitionData = yield verifyDefinitionSignatures(clusterLock.cluster_definition, definitionType);
299
+ const isValidDefinitionData = yield verifyDefinitionSignatures(clusterLock.cluster_definition, definitionType, safeRpcUrl);
297
300
  if (!isValidDefinitionData) {
298
301
  return false;
299
302
  }
@@ -14,9 +14,9 @@ import Safe from '@safe-global/protocol-kit';
14
14
  import { PROVIDER_MAP } from '../constants';
15
15
  import { hashTypedData } from '@safe-global/protocol-kit/dist/src/utils';
16
16
  import { isContractAvailable, getProvider } from '../utils';
17
- export const validateAddressSignature = ({ address, token, data, chainId, }) => __awaiter(void 0, void 0, void 0, function* () {
17
+ export const validateAddressSignature = ({ address, token, data, chainId, safeRpcUrl, }) => __awaiter(void 0, void 0, void 0, function* () {
18
18
  try {
19
- const provider = getProvider(chainId); // [TODO Hanan], should expect it from signer.provider, or passed in client
19
+ const provider = getProvider(chainId, safeRpcUrl);
20
20
  if (provider) {
21
21
  const contractAddress = yield isContractAvailable(address, provider);
22
22
  if (contractAddress) {
@@ -25,6 +25,7 @@ export const validateAddressSignature = ({ address, token, data, chainId, }) =>
25
25
  data: data,
26
26
  address,
27
27
  chainId,
28
+ safeRpcUrl,
28
29
  });
29
30
  }
30
31
  }
@@ -46,11 +47,11 @@ export const validateEOASignature = ({ token, data, address, }) => {
46
47
  throw err;
47
48
  }
48
49
  };
49
- export const validateSmartContractSignature = ({ token, data, address, chainId, }) => __awaiter(void 0, void 0, void 0, function* () {
50
+ export const validateSmartContractSignature = ({ token, data, address, chainId, safeRpcUrl, }) => __awaiter(void 0, void 0, void 0, function* () {
50
51
  try {
51
- const provider = PROVIDER_MAP[chainId];
52
+ const safeProvider = safeRpcUrl !== null && safeRpcUrl !== void 0 ? safeRpcUrl : PROVIDER_MAP[chainId];
52
53
  const protocolKit = yield Safe.init({
53
- provider,
54
+ provider: safeProvider,
54
55
  safeAddress: address,
55
56
  });
56
57
  const messageHash = hashTypedData(data);
@@ -120,7 +120,7 @@ describe('Client.incentives', () => {
120
120
  .mockImplementation(() => __awaiter(void 0, void 0, void 0, function* () { return yield Promise.resolve(true); }));
121
121
  const result = yield clientInstance.incentives.isClaimed(mockIncentivesData.contract_address, mockIncentivesData.index);
122
122
  expect(result).toBe(true);
123
- expect(incentivesHelpers.isClaimedFromMerkleDistributor).toHaveBeenCalledWith(clientInstance.incentives.chainId, mockIncentivesData.contract_address, mockIncentivesData.index, {});
123
+ expect(incentivesHelpers.isClaimedFromMerkleDistributor).toHaveBeenCalledWith(mockIncentivesData.contract_address, mockIncentivesData.index, {});
124
124
  }));
125
125
  test('isClaimed should return false when incentive is not claimed', () => __awaiter(void 0, void 0, void 0, function* () {
126
126
  jest
@@ -147,7 +147,7 @@ describe('Client.incentives', () => {
147
147
  .mockImplementation(() => __awaiter(void 0, void 0, void 0, function* () { return yield Promise.resolve(true); }));
148
148
  const result = yield clientWithoutSigner.incentives.isClaimed(mockIncentivesData.contract_address, mockIncentivesData.index);
149
149
  expect(result).toBe(true);
150
- expect(incentivesHelpers.isClaimedFromMerkleDistributor).toHaveBeenCalledWith(clientWithoutSigner.incentives.chainId, mockIncentivesData.contract_address, mockIncentivesData.index, provider);
150
+ expect(incentivesHelpers.isClaimedFromMerkleDistributor).toHaveBeenCalledWith(mockIncentivesData.contract_address, mockIncentivesData.index, provider);
151
151
  }));
152
152
  test('getIncentivesByAddress should make the correct API request', () => __awaiter(void 0, void 0, void 0, function* () {
153
153
  const mockAddress = '0x1234567890abcdef1234567890abcdef12345678';
@@ -140,6 +140,9 @@ describe('Cluster Client without a signer', () => {
140
140
  beforeAll(() => {
141
141
  jest.restoreAllMocks();
142
142
  });
143
+ beforeEach(() => {
144
+ jest.resetModules();
145
+ });
143
146
  test('createClusterDefinition should throw an error without signer', () => __awaiter(void 0, void 0, void 0, function* () {
144
147
  try {
145
148
  yield clientInstance.createClusterDefinition(clusterConfigV1X8);
@@ -194,6 +197,14 @@ describe('Cluster Client without a signer', () => {
194
197
  const isValidLock = yield validateClusterLock(clusterLock);
195
198
  expect(isValidLock).toEqual(true);
196
199
  }));
200
+ test('should return true on verified cluster lock with Safe wallet and safe rpc url', () => __awaiter(void 0, void 0, void 0, function* () {
201
+ process.env.RPC_HOLESKY = undefined;
202
+ /* eslint-disable @typescript-eslint/no-var-requires */
203
+ const { validateClusterLock: validateLockWithRpcUrl, } = require('../src/index');
204
+ const safeRpcUrl = 'https://ethereum-holesky-rpc.publicnode.com';
205
+ const isValidLock = yield validateLockWithRpcUrl(clusterLockWithSafe, safeRpcUrl);
206
+ expect(isValidLock).toEqual(true);
207
+ }));
197
208
  test('validateCluster should return false for cluster with null deposit_amounts and incorrect partial_deposits', () => __awaiter(void 0, void 0, void 0, function* () {
198
209
  const partialDeposit = nullDepositAmountsClusterLockV1X8.distributed_validators[0]
199
210
  .partial_deposit_data[0];
@@ -9,4 +9,4 @@ export declare const claimIncentivesFromMerkleDistributor: (incentivesData: {
9
9
  }) => Promise<{
10
10
  txHash: string;
11
11
  }>;
12
- export declare const isClaimedFromMerkleDistributor: (chainId: number, contractAddress: ETH_ADDRESS, index: number, provider: ProviderType | undefined | null) => Promise<boolean>;
12
+ export declare const isClaimedFromMerkleDistributor: (contractAddress: ETH_ADDRESS, index: number, provider: ProviderType | undefined | null) => Promise<boolean>;
@@ -1,4 +1,4 @@
1
- import { type Incentives as IncentivesType, type ETH_ADDRESS, type ProviderType, type SignerType, type ClaimIncentivesResponse } from './types';
1
+ import { type ClaimableIncentives, type ETH_ADDRESS, type ProviderType, type SignerType, type ClaimIncentivesResponse } from './types';
2
2
  /**
3
3
  * Incentives can be used for fetching and claiming Obol incentives.
4
4
  * @class
@@ -53,5 +53,5 @@ export declare class Incentives {
53
53
  * An example of how to use getIncentivesByAddress:
54
54
  * [obolClient](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L250)
55
55
  */
56
- getIncentivesByAddress(address: string): Promise<IncentivesType>;
56
+ getIncentivesByAddress(address: string): Promise<ClaimableIncentives>;
57
57
  }
@@ -1,11 +1,12 @@
1
- import { type ClusterLock } from './types.js';
1
+ import { type SafeRpcUrl, type ClusterLock } from './types.js';
2
2
  /**
3
3
  * Verifies Cluster Lock's validity.
4
4
  * @param lock - cluster lock
5
+ * @param safeRpcUrl - optional safeRpcUrl for safe wallet verification
5
6
  * @returns {Promise<{ result: boolean }> } boolean result to indicate if lock is valid
6
7
  * @throws on missing keys or values.
7
8
  *
8
9
  * An example of how to use validateClusterLock:
9
10
  * [validateClusterLock](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L127)
10
11
  */
11
- export declare const validateClusterLock: (lock: ClusterLock) => Promise<boolean>;
12
+ export declare const validateClusterLock: (lock: ClusterLock, safeRpcUrl?: SafeRpcUrl) => Promise<boolean>;
@@ -1,4 +1,4 @@
1
- import { type ethers, type JsonRpcApiProvider, type JsonRpcProvider, type JsonRpcSigner, type Provider, type Signer } from 'ethers';
1
+ import { type Wallet, type ethers, type JsonRpcApiProvider, type JsonRpcProvider, type JsonRpcSigner, type Provider, type Signer } from 'ethers';
2
2
  /**
3
3
  * Permitted ChainID's
4
4
  */
@@ -212,9 +212,9 @@ export type ClusterLock = {
212
212
  node_signatures?: string[];
213
213
  };
214
214
  /**
215
- * Incentives
215
+ * Claimable Obol Incentives
216
216
  */
217
- export type Incentives = {
217
+ export type ClaimableIncentives = {
218
218
  /** Operator Address. */
219
219
  operator_address: string;
220
220
  /** The amount the recipient is entitled to. */
@@ -234,10 +234,14 @@ export type ETH_ADDRESS = string;
234
234
  * Provider Types
235
235
  */
236
236
  export type ProviderType = Provider | JsonRpcProvider | JsonRpcApiProvider | ethers.BrowserProvider;
237
+ /**
238
+ * Safe Wallet Provider Types
239
+ */
240
+ export type SafeRpcUrl = string;
237
241
  /**
238
242
  * Signer Types
239
243
  */
240
- export type SignerType = Signer | JsonRpcSigner;
244
+ export type SignerType = Signer | JsonRpcSigner | Wallet;
241
245
  /**
242
246
  * claimIncentives Response
243
247
  */
@@ -6,4 +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: ProviderType) => Promise<string>;
8
8
  export declare const isContractAvailable: (contractAddress: string, provider: ProviderType, bytecode?: string) => Promise<boolean>;
9
- export declare const getProvider: (chainId: number) => ethers.JsonRpcProvider;
9
+ export declare const getProvider: (chainId: number, rpcUrl?: string) => ethers.JsonRpcProvider;
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import { type ClusterDefinition, type ClusterLock, type DepositData, type DistributedValidator } from '../types.js';
2
+ import { type ClusterDefinition, type ClusterLock, type DepositData, type DistributedValidator, type SafeRpcUrl } 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
@@ -29,4 +29,4 @@ export declare const verifyBuilderRegistration: (validator: DistributedValidator
29
29
  };
30
30
  export declare const verifyNodeSignatures: (clusterLock: ClusterLock) => boolean;
31
31
  export declare const signingRoot: (domain: Uint8Array, messageBuffer: Buffer) => Uint8Array;
32
- export declare const isValidClusterLock: (clusterLock: ClusterLock) => Promise<boolean>;
32
+ export declare const isValidClusterLock: (clusterLock: ClusterLock, safeRpcUrl?: SafeRpcUrl) => Promise<boolean>;
@@ -1,19 +1,22 @@
1
1
  import { type TypedMessage } from '@metamask/eth-sig-util';
2
2
  import { type EIP712TypedData } from '@safe-global/safe-core-sdk-types';
3
- export declare const validateAddressSignature: ({ address, token, data, chainId, }: {
3
+ import { type SafeRpcUrl } from '../types';
4
+ export declare const validateAddressSignature: ({ address, token, data, chainId, safeRpcUrl, }: {
4
5
  address: string;
5
6
  token: string;
6
7
  data: TypedMessage<any>;
7
8
  chainId: number;
9
+ safeRpcUrl?: string | undefined;
8
10
  }) => Promise<boolean>;
9
11
  export declare const validateEOASignature: ({ token, data, address, }: {
10
12
  token: string;
11
13
  data: TypedMessage<any>;
12
14
  address: string;
13
15
  }) => boolean;
14
- export declare const validateSmartContractSignature: ({ token, data, address, chainId, }: {
16
+ export declare const validateSmartContractSignature: ({ token, data, address, chainId, safeRpcUrl, }: {
15
17
  token: string;
16
18
  data: EIP712TypedData;
17
19
  address: string;
18
20
  chainId: number;
21
+ safeRpcUrl?: string | undefined;
19
22
  }) => Promise<boolean>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obolnetwork/obol-sdk",
3
- "version": "2.4.5",
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"
@@ -56,7 +56,7 @@
56
56
  "eslint-plugin-import": "^2.29.1",
57
57
  "eslint-plugin-n": "^16.6.2",
58
58
  "eslint-plugin-promise": "^6.1.1",
59
- "ethers": "6.4.0",
59
+ "ethers": "^6.13.5",
60
60
  "nock": "^13.5.3",
61
61
  "pdf-parse": "^1.1.1",
62
62
  "semver": "^7.6.0",
@@ -1,7 +1,6 @@
1
1
  import { type ProviderType, type SignerType, type ETH_ADDRESS } from './types';
2
2
  import { Contract } from 'ethers';
3
3
  import { MerkleDistributorABI } from './abi/MerkleDistributorWithDeadline';
4
- import { getProvider } from './utils';
5
4
 
6
5
  export const claimIncentivesFromMerkleDistributor = async (incentivesData: {
7
6
  signer: SignerType;
@@ -35,18 +34,15 @@ export const claimIncentivesFromMerkleDistributor = async (incentivesData: {
35
34
  };
36
35
 
37
36
  export const isClaimedFromMerkleDistributor = async (
38
- chainId: number,
39
37
  contractAddress: ETH_ADDRESS,
40
38
  index: number,
41
39
  provider: ProviderType | undefined | null,
42
40
  ): Promise<boolean> => {
43
41
  try {
44
- const clientProvider = provider ?? getProvider(chainId);
45
-
46
42
  const contract = new Contract(
47
43
  contractAddress,
48
44
  MerkleDistributorABI.abi,
49
- clientProvider,
45
+ provider,
50
46
  );
51
47
 
52
48
  const claimed = await contract.isClaimed(BigInt(index));
package/src/incentives.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { isContractAvailable } from './utils';
2
2
  import {
3
- type Incentives as IncentivesType,
3
+ type ClaimableIncentives,
4
4
  type ETH_ADDRESS,
5
5
  FORK_NAMES,
6
6
  type ProviderType,
@@ -126,7 +126,6 @@ export class Incentives {
126
126
  index: number,
127
127
  ): Promise<boolean> {
128
128
  return await isClaimedFromMerkleDistributor(
129
- this.chainId,
130
129
  contractAddress,
131
130
  index,
132
131
  this.provider,
@@ -141,9 +140,9 @@ export class Incentives {
141
140
  * An example of how to use getIncentivesByAddress:
142
141
  * [obolClient](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L250)
143
142
  */
144
- async getIncentivesByAddress(address: string): Promise<IncentivesType> {
143
+ async getIncentivesByAddress(address: string): Promise<ClaimableIncentives> {
145
144
  const network = FORK_NAMES[this.chainId];
146
- const incentives: IncentivesType = await this.request(
145
+ const incentives: ClaimableIncentives = await this.request(
147
146
  `/${DEFAULT_BASE_VERSION}/address/incentives/${network}/${address}`,
148
147
  {
149
148
  method: 'GET',
package/src/services.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { type ClusterLock } from './types.js';
1
+ import { type SafeRpcUrl, type ClusterLock } from './types.js';
2
2
  import { isValidClusterLock } from './verification/common.js';
3
3
 
4
4
  /**
5
5
  * Verifies Cluster Lock's validity.
6
6
  * @param lock - cluster lock
7
+ * @param safeRpcUrl - optional safeRpcUrl for safe wallet verification
7
8
  * @returns {Promise<{ result: boolean }> } boolean result to indicate if lock is valid
8
9
  * @throws on missing keys or values.
9
10
  *
@@ -12,9 +13,10 @@ import { isValidClusterLock } from './verification/common.js';
12
13
  */
13
14
  export const validateClusterLock = async (
14
15
  lock: ClusterLock,
16
+ safeRpcUrl?: SafeRpcUrl,
15
17
  ): Promise<boolean> => {
16
18
  try {
17
- const isLockValid = await isValidClusterLock(lock);
19
+ const isLockValid = await isValidClusterLock(lock, safeRpcUrl);
18
20
  return isLockValid;
19
21
  } catch (err: any) {
20
22
  throw err;
package/src/types.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ type Wallet,
2
3
  type ethers,
3
4
  type JsonRpcApiProvider,
4
5
  type JsonRpcProvider,
@@ -295,9 +296,9 @@ export type ClusterLock = {
295
296
  };
296
297
 
297
298
  /**
298
- * Incentives
299
+ * Claimable Obol Incentives
299
300
  */
300
- export type Incentives = {
301
+ export type ClaimableIncentives = {
301
302
  /** Operator Address. */
302
303
  operator_address: string;
303
304
 
@@ -328,10 +329,15 @@ export type ProviderType =
328
329
  | JsonRpcApiProvider
329
330
  | ethers.BrowserProvider;
330
331
 
332
+ /**
333
+ * Safe Wallet Provider Types
334
+ */
335
+ export type SafeRpcUrl = string;
336
+
331
337
  /**
332
338
  * Signer Types
333
339
  */
334
- export type SignerType = Signer | JsonRpcSigner;
340
+ export type SignerType = Signer | JsonRpcSigner | Wallet;
335
341
 
336
342
  /**
337
343
  * claimIncentives Response
package/src/utils.ts CHANGED
@@ -76,10 +76,13 @@ export const isContractAvailable = async (
76
76
  return !!code && code !== '0x' && code !== '0x0';
77
77
  };
78
78
 
79
- export const getProvider = (chainId: number): ethers.JsonRpcProvider => {
80
- const rpcUrl = PROVIDER_MAP[chainId];
81
- if (!rpcUrl || rpcUrl === 'undefined') {
79
+ export const getProvider = (
80
+ chainId: number,
81
+ rpcUrl?: string,
82
+ ): ethers.JsonRpcProvider => {
83
+ const resolvedRpcUrl = rpcUrl ?? PROVIDER_MAP[chainId];
84
+ if (chainId && (!resolvedRpcUrl || resolvedRpcUrl === 'undefined')) {
82
85
  throw new Error(`No provider configured for ${FORK_NAMES[chainId]}`);
83
86
  }
84
- return new ethers.JsonRpcProvider(rpcUrl);
87
+ return new ethers.JsonRpcProvider(resolvedRpcUrl);
85
88
  };
@@ -1,7 +1,6 @@
1
1
  import { fromHexString } from '@chainsafe/ssz';
2
2
  import elliptic from 'elliptic';
3
3
  import { init } from '@chainsafe/bls';
4
-
5
4
  import {
6
5
  FORK_MAPPING,
7
6
  type ClusterDefinition,
@@ -9,6 +8,7 @@ import {
9
8
  type DepositData,
10
9
  type BuilderRegistrationMessage,
11
10
  type DistributedValidator,
11
+ type SafeRpcUrl,
12
12
  } from '../types.js';
13
13
  import * as semver from 'semver';
14
14
  import {
@@ -167,6 +167,7 @@ const validatePOSTConfigHashSigner = async (
167
167
  signature: string,
168
168
  configHash: string,
169
169
  chainId: FORK_MAPPING,
170
+ safeRpcUrl?: SafeRpcUrl,
170
171
  ): Promise<boolean> => {
171
172
  try {
172
173
  const data = signCreatorConfigHashPayload(
@@ -179,6 +180,7 @@ const validatePOSTConfigHashSigner = async (
179
180
  token: signature,
180
181
  data,
181
182
  chainId,
183
+ safeRpcUrl,
182
184
  });
183
185
  } catch (err) {
184
186
  throw err;
@@ -190,6 +192,7 @@ const validatePUTConfigHashSigner = async (
190
192
  signature: string,
191
193
  configHash: string,
192
194
  chainId: number,
195
+ safeRpcUrl?: SafeRpcUrl,
193
196
  ): Promise<boolean> => {
194
197
  try {
195
198
  const data = signOperatorConfigHashPayload(
@@ -201,6 +204,7 @@ const validatePUTConfigHashSigner = async (
201
204
  token: signature,
202
205
  data,
203
206
  chainId,
207
+ safeRpcUrl,
204
208
  });
205
209
  } catch (err) {
206
210
  throw err;
@@ -212,6 +216,7 @@ const validateEnrSigner = async (
212
216
  signature: string,
213
217
  payload: string,
214
218
  chainId: number,
219
+ safeRpcUrl?: SafeRpcUrl,
215
220
  ): Promise<boolean> => {
216
221
  try {
217
222
  const data = signEnrPayload({ enr: payload }, chainId);
@@ -221,6 +226,7 @@ const validateEnrSigner = async (
221
226
  token: signature,
222
227
  data,
223
228
  chainId,
229
+ safeRpcUrl,
224
230
  });
225
231
  } catch (err) {
226
232
  throw err;
@@ -230,6 +236,7 @@ const validateEnrSigner = async (
230
236
  const verifyDefinitionSignatures = async (
231
237
  clusterDefinition: ClusterDefinition,
232
238
  definitionType: DefinitionFlow,
239
+ safeRpcUrl?: SafeRpcUrl,
233
240
  ): Promise<boolean> => {
234
241
  if (definitionType === DefinitionFlow.Charon) {
235
242
  return true;
@@ -239,6 +246,7 @@ const verifyDefinitionSignatures = async (
239
246
  clusterDefinition.creator.config_signature as string,
240
247
  clusterDefinition.config_hash,
241
248
  FORK_MAPPING[clusterDefinition.fork_version as keyof typeof FORK_MAPPING],
249
+ safeRpcUrl,
242
250
  );
243
251
 
244
252
  if (!isPOSTConfigHashSignerValid) {
@@ -256,6 +264,7 @@ const verifyDefinitionSignatures = async (
256
264
  FORK_MAPPING[
257
265
  clusterDefinition.fork_version as keyof typeof FORK_MAPPING
258
266
  ],
267
+ safeRpcUrl,
259
268
  );
260
269
 
261
270
  const isENRSignerValid = await validateEnrSigner(
@@ -265,6 +274,7 @@ const verifyDefinitionSignatures = async (
265
274
  FORK_MAPPING[
266
275
  clusterDefinition.fork_version as keyof typeof FORK_MAPPING
267
276
  ],
277
+ safeRpcUrl,
268
278
  );
269
279
 
270
280
  if (!isPUTConfigHashSignerValid || !isENRSignerValid) {
@@ -485,6 +495,7 @@ const verifyLockData = async (clusterLock: ClusterLock): Promise<boolean> => {
485
495
 
486
496
  export const isValidClusterLock = async (
487
497
  clusterLock: ClusterLock,
498
+ safeRpcUrl?: SafeRpcUrl,
488
499
  ): Promise<boolean> => {
489
500
  try {
490
501
  const definitionType = definitionFlow(clusterLock.cluster_definition);
@@ -494,6 +505,7 @@ export const isValidClusterLock = async (
494
505
  const isValidDefinitionData = await verifyDefinitionSignatures(
495
506
  clusterLock.cluster_definition,
496
507
  definitionType,
508
+ safeRpcUrl,
497
509
  );
498
510
  if (!isValidDefinitionData) {
499
511
  return false;
@@ -10,20 +10,23 @@ import { PROVIDER_MAP } from '../constants';
10
10
  import { hashTypedData } from '@safe-global/protocol-kit/dist/src/utils';
11
11
  import { type EIP712TypedData } from '@safe-global/safe-core-sdk-types';
12
12
  import { isContractAvailable, getProvider } from '../utils';
13
+ import { type SafeRpcUrl } from '../types';
13
14
 
14
15
  export const validateAddressSignature = async ({
15
16
  address,
16
17
  token,
17
18
  data,
18
19
  chainId,
20
+ safeRpcUrl,
19
21
  }: {
20
22
  address: string;
21
23
  token: string;
22
24
  data: TypedMessage<any>;
23
25
  chainId: number;
26
+ safeRpcUrl?: SafeRpcUrl;
24
27
  }): Promise<boolean> => {
25
28
  try {
26
- const provider = getProvider(chainId); // [TODO Hanan], should expect it from signer.provider, or passed in client
29
+ const provider = getProvider(chainId, safeRpcUrl);
27
30
  if (provider) {
28
31
  const contractAddress = await isContractAvailable(address, provider);
29
32
  if (contractAddress) {
@@ -32,6 +35,7 @@ export const validateAddressSignature = async ({
32
35
  data: data as unknown as EIP712TypedData,
33
36
  address,
34
37
  chainId,
38
+ safeRpcUrl,
35
39
  });
36
40
  }
37
41
  }
@@ -69,16 +73,19 @@ export const validateSmartContractSignature = async ({
69
73
  data,
70
74
  address,
71
75
  chainId,
76
+ safeRpcUrl,
72
77
  }: {
73
78
  token: string;
74
79
  data: EIP712TypedData;
75
80
  address: string;
76
81
  chainId: number;
82
+ safeRpcUrl?: SafeRpcUrl;
77
83
  }): Promise<boolean> => {
78
84
  try {
79
- const provider = PROVIDER_MAP[chainId];
85
+ const safeProvider = safeRpcUrl ?? PROVIDER_MAP[chainId];
86
+
80
87
  const protocolKit = await Safe.init({
81
- provider,
88
+ provider: safeProvider,
82
89
  safeAddress: address,
83
90
  });
84
91
  const messageHash = hashTypedData(data);