@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 +1 -1
- package/dist/cjs/package.json +2 -2
- package/dist/cjs/src/incentiveHelpers.js +2 -4
- package/dist/cjs/src/incentives.js +1 -1
- package/dist/cjs/src/services.js +3 -2
- package/dist/cjs/src/utils.js +4 -4
- package/dist/cjs/src/verification/common.js +12 -9
- package/dist/cjs/src/verification/signature-validator.js +6 -5
- package/dist/cjs/test/incentives.test.js +2 -2
- package/dist/cjs/test/methods.test.js +11 -0
- package/dist/esm/package.json +2 -2
- package/dist/esm/src/incentiveHelpers.js +2 -4
- package/dist/esm/src/incentives.js +1 -1
- package/dist/esm/src/services.js +3 -2
- package/dist/esm/src/utils.js +4 -4
- package/dist/esm/src/verification/common.js +12 -9
- package/dist/esm/src/verification/signature-validator.js +6 -5
- package/dist/esm/test/incentives.test.js +2 -2
- package/dist/esm/test/methods.test.js +11 -0
- package/dist/types/src/incentiveHelpers.d.ts +1 -1
- package/dist/types/src/incentives.d.ts +2 -2
- package/dist/types/src/services.d.ts +3 -2
- package/dist/types/src/types.d.ts +8 -4
- package/dist/types/src/utils.d.ts +1 -1
- package/dist/types/src/verification/common.d.ts +2 -2
- package/dist/types/src/verification/signature-validator.d.ts +5 -2
- package/package.json +2 -2
- package/src/incentiveHelpers.ts +1 -5
- package/src/incentives.ts +3 -4
- package/src/services.ts +4 -2
- package/src/types.ts +9 -3
- package/src/utils.ts +7 -4
- package/src/verification/common.ts +13 -1
- 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
|
|
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
|
package/dist/cjs/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@obolnetwork/obol-sdk",
|
|
3
|
-
"version": "2.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"
|
|
@@ -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.
|
|
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 = (
|
|
28
|
+
const isClaimedFromMerkleDistributor = (contractAddress, index, provider) => __awaiter(void 0, void 0, void 0, function* () {
|
|
30
29
|
try {
|
|
31
|
-
const
|
|
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)(
|
|
98
|
+
return yield (0, incentiveHelpers_1.isClaimedFromMerkleDistributor)(contractAddress, index, this.provider);
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
101
|
/**
|
package/dist/cjs/src/services.js
CHANGED
|
@@ -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) {
|
package/dist/cjs/src/utils.js
CHANGED
|
@@ -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
|
|
72
|
-
if (!
|
|
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(
|
|
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);
|
|
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
|
|
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(
|
|
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(
|
|
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];
|
package/dist/esm/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@obolnetwork/obol-sdk",
|
|
3
|
-
"version": "2.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"
|
|
@@ -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.
|
|
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 = (
|
|
24
|
+
export const isClaimedFromMerkleDistributor = (contractAddress, index, provider) => __awaiter(void 0, void 0, void 0, function* () {
|
|
26
25
|
try {
|
|
27
|
-
const
|
|
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(
|
|
95
|
+
return yield isClaimedFromMerkleDistributor(contractAddress, index, this.provider);
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
98
|
/**
|
package/dist/esm/src/services.js
CHANGED
|
@@ -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) {
|
package/dist/esm/src/utils.js
CHANGED
|
@@ -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
|
|
64
|
-
if (!
|
|
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(
|
|
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);
|
|
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
|
|
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(
|
|
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(
|
|
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: (
|
|
12
|
+
export declare const isClaimedFromMerkleDistributor: (contractAddress: ETH_ADDRESS, index: number, provider: ProviderType | undefined | null) => Promise<boolean>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
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<
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
59
|
+
"ethers": "^6.13.5",
|
|
60
60
|
"nock": "^13.5.3",
|
|
61
61
|
"pdf-parse": "^1.1.1",
|
|
62
62
|
"semver": "^7.6.0",
|
package/src/incentiveHelpers.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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<
|
|
143
|
+
async getIncentivesByAddress(address: string): Promise<ClaimableIncentives> {
|
|
145
144
|
const network = FORK_NAMES[this.chainId];
|
|
146
|
-
const incentives:
|
|
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
|
|
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 = (
|
|
80
|
-
|
|
81
|
-
|
|
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(
|
|
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);
|
|
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
|
|
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);
|