@obolnetwork/obol-sdk 2.3.0 → 2.4.1
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/dist/cjs/package.json +2 -2
- package/dist/cjs/src/abi/MerkleDistributorWithDeadline.js +73 -0
- package/dist/cjs/src/constants.js +5 -2
- package/dist/cjs/src/incentives.js +93 -0
- package/dist/cjs/src/incentivesHalpers.js +41 -0
- package/dist/cjs/src/index.js +6 -14
- package/dist/cjs/src/verification/signature-validator.js +1 -1
- package/dist/cjs/test/incentives.test.js +174 -0
- package/dist/cjs/test/methods.test.js +0 -44
- package/dist/esm/package.json +2 -2
- package/dist/esm/src/abi/MerkleDistributorWithDeadline.js +70 -0
- package/dist/esm/src/constants.js +1 -1
- package/dist/esm/src/incentives.js +89 -0
- package/dist/esm/src/incentivesHalpers.js +36 -0
- package/dist/esm/src/index.js +7 -15
- package/dist/esm/src/verification/signature-validator.js +1 -1
- package/dist/esm/test/incentives.test.js +149 -0
- package/dist/esm/test/methods.test.js +0 -44
- package/dist/types/src/abi/MerkleDistributorWithDeadline.d.ts +91 -0
- package/dist/types/src/incentives.d.ts +50 -0
- package/dist/types/src/incentivesHalpers.d.ts +13 -0
- package/dist/types/src/index.d.ts +6 -9
- package/dist/types/test/incentives.test.d.ts +1 -0
- package/package.json +2 -2
- package/src/abi/MerkleDistributorWithDeadline.ts +70 -0
- package/src/constants.ts +1 -1
- package/src/incentives.ts +137 -0
- package/src/incentivesHalpers.ts +65 -0
- package/src/index.ts +32 -19
- package/src/verification/signature-validator.ts +1 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export const MerkleDistributorABI = {
|
|
2
|
+
abi: [
|
|
3
|
+
{
|
|
4
|
+
inputs: [
|
|
5
|
+
{ internalType: 'address', name: 'token_', type: 'address' },
|
|
6
|
+
{ internalType: 'bytes32', name: 'merkleRoot_', type: 'bytes32' },
|
|
7
|
+
],
|
|
8
|
+
stateMutability: 'nonpayable',
|
|
9
|
+
type: 'constructor',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
anonymous: false,
|
|
13
|
+
inputs: [
|
|
14
|
+
{
|
|
15
|
+
indexed: false,
|
|
16
|
+
internalType: 'uint256',
|
|
17
|
+
name: 'index',
|
|
18
|
+
type: 'uint256',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
indexed: false,
|
|
22
|
+
internalType: 'address',
|
|
23
|
+
name: 'account',
|
|
24
|
+
type: 'address',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
indexed: false,
|
|
28
|
+
internalType: 'uint256',
|
|
29
|
+
name: 'amount',
|
|
30
|
+
type: 'uint256',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
name: 'Claimed',
|
|
34
|
+
type: 'event',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
inputs: [
|
|
38
|
+
{ internalType: 'uint256', name: 'index', type: 'uint256' },
|
|
39
|
+
{ internalType: 'address', name: 'account', type: 'address' },
|
|
40
|
+
{ internalType: 'uint256', name: 'amount', type: 'uint256' },
|
|
41
|
+
{ internalType: 'bytes32[]', name: 'merkleProof', type: 'bytes32[]' },
|
|
42
|
+
],
|
|
43
|
+
name: 'claim',
|
|
44
|
+
outputs: [],
|
|
45
|
+
stateMutability: 'nonpayable',
|
|
46
|
+
type: 'function',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
inputs: [{ internalType: 'uint256', name: 'index', type: 'uint256' }],
|
|
50
|
+
name: 'isClaimed',
|
|
51
|
+
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
|
|
52
|
+
stateMutability: 'view',
|
|
53
|
+
type: 'function',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
inputs: [],
|
|
57
|
+
name: 'merkleRoot',
|
|
58
|
+
outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
|
|
59
|
+
stateMutability: 'view',
|
|
60
|
+
type: 'function',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
inputs: [],
|
|
64
|
+
name: 'token',
|
|
65
|
+
outputs: [{ internalType: 'address', name: '', type: 'address' }],
|
|
66
|
+
stateMutability: 'view',
|
|
67
|
+
type: 'function',
|
|
68
|
+
},
|
|
69
|
+
] as const,
|
|
70
|
+
};
|
package/src/constants.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type TypedMessage } from '@metamask/eth-sig-util';
|
|
2
2
|
import { type TypedDataDomain } from 'ethers';
|
|
3
|
-
import
|
|
3
|
+
import pjson from '../package.json';
|
|
4
4
|
import { FORK_MAPPING } from './types';
|
|
5
5
|
import {
|
|
6
6
|
HOLESKY_MULTICALL_BYTECODE,
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type JsonRpcApiProvider,
|
|
3
|
+
type JsonRpcProvider,
|
|
4
|
+
type JsonRpcSigner,
|
|
5
|
+
type Provider,
|
|
6
|
+
type Signer,
|
|
7
|
+
} from 'ethers';
|
|
8
|
+
import { isContractAvailable } from './utils';
|
|
9
|
+
import { type Incentives as IncentivesType, type ETH_ADDRESS } from './types';
|
|
10
|
+
import {
|
|
11
|
+
claimIncentivesFromMerkleDistributor,
|
|
12
|
+
isClaimedFromMerkleDistributor,
|
|
13
|
+
} from './incentivesHalpers';
|
|
14
|
+
import { DEFAULT_BASE_VERSION } from './constants';
|
|
15
|
+
|
|
16
|
+
export class Incentives {
|
|
17
|
+
private readonly signer: Signer | JsonRpcSigner | undefined;
|
|
18
|
+
public readonly chainId: number;
|
|
19
|
+
private readonly request: (
|
|
20
|
+
endpoint: string,
|
|
21
|
+
options?: RequestInit,
|
|
22
|
+
) => Promise<any>;
|
|
23
|
+
|
|
24
|
+
public readonly provider:
|
|
25
|
+
| Provider
|
|
26
|
+
| JsonRpcProvider
|
|
27
|
+
| JsonRpcApiProvider
|
|
28
|
+
| undefined
|
|
29
|
+
| null;
|
|
30
|
+
|
|
31
|
+
constructor(
|
|
32
|
+
signer: Signer | JsonRpcSigner | undefined,
|
|
33
|
+
chainId: number,
|
|
34
|
+
request: (endpoint: string, options?: RequestInit) => Promise<any>,
|
|
35
|
+
provider:
|
|
36
|
+
| Provider
|
|
37
|
+
| JsonRpcProvider
|
|
38
|
+
| JsonRpcApiProvider
|
|
39
|
+
| undefined
|
|
40
|
+
| null,
|
|
41
|
+
) {
|
|
42
|
+
this.signer = signer;
|
|
43
|
+
this.chainId = chainId;
|
|
44
|
+
this.request = request;
|
|
45
|
+
this.provider = provider;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Claims obol incentives from a Merkle Distributor contract.
|
|
50
|
+
*
|
|
51
|
+
* @remarks
|
|
52
|
+
* **⚠️ Important:** If you're storing the private key in an `.env` file, ensure it is securely managed
|
|
53
|
+
* and not pushed to version control.
|
|
54
|
+
*
|
|
55
|
+
* @param {Object} incentivesData - The incentives data needed for claiming.
|
|
56
|
+
* @param {string} incentivesData.contractAddress - The address of the Merkle Distributor contract.
|
|
57
|
+
* @param {number} incentivesData.index - The index in the Merkle tree.
|
|
58
|
+
* @param {string} incentivesData.operatorAddress - The address of the operator.
|
|
59
|
+
* @param {number} incentivesData.amount - The amount to claim.
|
|
60
|
+
* @param {string[]} incentivesData.merkleProof - The Merkle proof.
|
|
61
|
+
* @returns {Promise<{ txHash: string }>} The transaction hash of the claim transaction.
|
|
62
|
+
* @throws Will throw an error if the contract is not available or the claim fails.
|
|
63
|
+
*
|
|
64
|
+
*/
|
|
65
|
+
async claimIncentives(incentivesData: {
|
|
66
|
+
contractAddress: ETH_ADDRESS;
|
|
67
|
+
index: number;
|
|
68
|
+
operatorAddress: ETH_ADDRESS;
|
|
69
|
+
amount: number;
|
|
70
|
+
merkleProof: string[];
|
|
71
|
+
}): Promise<{ txHash: string }> {
|
|
72
|
+
if (!this.signer) {
|
|
73
|
+
throw new Error('Signer is required in claimIncentives');
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const isContractDeployed = await isContractAvailable(
|
|
77
|
+
incentivesData.contractAddress,
|
|
78
|
+
this.signer.provider as Provider,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (!isContractDeployed) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Merkle Distributor contract is not available at address ${incentivesData.contractAddress}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const { txHash } = await claimIncentivesFromMerkleDistributor({
|
|
88
|
+
signer: this.signer,
|
|
89
|
+
contractAddress: incentivesData.contractAddress,
|
|
90
|
+
index: incentivesData.index,
|
|
91
|
+
operatorAddress: incentivesData.operatorAddress,
|
|
92
|
+
amount: incentivesData.amount,
|
|
93
|
+
merkleProof: incentivesData.merkleProof,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return { txHash };
|
|
97
|
+
} catch (error: any) {
|
|
98
|
+
console.log('Error claiming incentives:', error);
|
|
99
|
+
throw new Error(`Failed to claim incentives: ${error.message}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Read isClaimed.
|
|
105
|
+
*
|
|
106
|
+
* @param {ETH_ADDRESS} contractAddress - Address of the Merkle Distributor Contract
|
|
107
|
+
* @param {ETH_ADDRESS} index - operator index in merkle tree
|
|
108
|
+
* @returns {Promise<boolean>} true if incentives are already claime
|
|
109
|
+
*
|
|
110
|
+
*/
|
|
111
|
+
async isClaimed(
|
|
112
|
+
contractAddress: ETH_ADDRESS,
|
|
113
|
+
index: number,
|
|
114
|
+
): Promise<boolean> {
|
|
115
|
+
return await isClaimedFromMerkleDistributor(
|
|
116
|
+
this.chainId,
|
|
117
|
+
contractAddress,
|
|
118
|
+
index,
|
|
119
|
+
this.provider,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @param address - Operator address
|
|
125
|
+
* @returns {Promise<IncentivesType>} The matched incentives from DB
|
|
126
|
+
* @throws On not found if address not found.
|
|
127
|
+
*/
|
|
128
|
+
async getIncentivesByAddress(address: string): Promise<IncentivesType> {
|
|
129
|
+
const incentives: IncentivesType = await this.request(
|
|
130
|
+
`/${DEFAULT_BASE_VERSION}/address/incentives/${address}`,
|
|
131
|
+
{
|
|
132
|
+
method: 'GET',
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
return incentives;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type ETH_ADDRESS } from './types';
|
|
2
|
+
import {
|
|
3
|
+
Contract,
|
|
4
|
+
type JsonRpcApiProvider,
|
|
5
|
+
type JsonRpcProvider,
|
|
6
|
+
type Provider,
|
|
7
|
+
type Signer,
|
|
8
|
+
} from 'ethers';
|
|
9
|
+
import { MerkleDistributorABI } from './abi/MerkleDistributorWithDeadline';
|
|
10
|
+
import { getProvider } from './utils';
|
|
11
|
+
|
|
12
|
+
export const claimIncentivesFromMerkleDistributor = async (incentivesData: {
|
|
13
|
+
signer: Signer;
|
|
14
|
+
contractAddress: ETH_ADDRESS;
|
|
15
|
+
index: number;
|
|
16
|
+
operatorAddress: ETH_ADDRESS;
|
|
17
|
+
amount: number;
|
|
18
|
+
merkleProof: string[];
|
|
19
|
+
}): Promise<{ txHash: string }> => {
|
|
20
|
+
try {
|
|
21
|
+
const contract = new Contract(
|
|
22
|
+
incentivesData.contractAddress,
|
|
23
|
+
MerkleDistributorABI.abi,
|
|
24
|
+
incentivesData.signer,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const tx = await contract.claim(
|
|
28
|
+
BigInt(incentivesData.index),
|
|
29
|
+
incentivesData.operatorAddress,
|
|
30
|
+
BigInt(incentivesData.amount),
|
|
31
|
+
incentivesData.merkleProof,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const receipt = await tx.wait();
|
|
35
|
+
|
|
36
|
+
return { txHash: receipt.hash };
|
|
37
|
+
} catch (error: any) {
|
|
38
|
+
console.log('Error claiming incentives:', error);
|
|
39
|
+
throw new Error(`Failed to claim incentives: ${error.message}`);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const isClaimedFromMerkleDistributor = async (
|
|
44
|
+
chainId: number,
|
|
45
|
+
contractAddress: ETH_ADDRESS,
|
|
46
|
+
index: number,
|
|
47
|
+
provider: Provider | JsonRpcProvider | JsonRpcApiProvider | undefined | null,
|
|
48
|
+
): Promise<boolean> => {
|
|
49
|
+
try {
|
|
50
|
+
const clientProvider = provider ?? getProvider(chainId);
|
|
51
|
+
|
|
52
|
+
const contract = new Contract(
|
|
53
|
+
contractAddress,
|
|
54
|
+
MerkleDistributorABI.abi,
|
|
55
|
+
clientProvider,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const claimed = await contract.isClaimed(BigInt(index));
|
|
59
|
+
|
|
60
|
+
return claimed;
|
|
61
|
+
} catch (error: any) {
|
|
62
|
+
console.log('Error checking claim status:', error);
|
|
63
|
+
throw new Error(`Failed to check claim status: ${error.message}`);
|
|
64
|
+
}
|
|
65
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ZeroAddress,
|
|
3
|
+
type Provider,
|
|
4
|
+
type Signer,
|
|
5
|
+
type JsonRpcSigner,
|
|
6
|
+
type JsonRpcProvider,
|
|
7
|
+
type JsonRpcApiProvider,
|
|
8
|
+
} from 'ethers';
|
|
2
9
|
import { v4 as uuidv4 } from 'uuid';
|
|
3
10
|
import { Base } from './base.js';
|
|
4
11
|
import {
|
|
@@ -30,7 +37,6 @@ import {
|
|
|
30
37
|
type ClusterValidator,
|
|
31
38
|
type ETH_ADDRESS,
|
|
32
39
|
type OWRTranches,
|
|
33
|
-
type Incentives,
|
|
34
40
|
} from './types.js';
|
|
35
41
|
import { clusterConfigOrDefinitionHash } from './verification/common.js';
|
|
36
42
|
import { validatePayload } from './ajv.js';
|
|
@@ -48,6 +54,7 @@ import {
|
|
|
48
54
|
getOWRTranches,
|
|
49
55
|
} from './splitHelpers.js';
|
|
50
56
|
import { isContractAvailable } from './utils.js';
|
|
57
|
+
import { Incentives } from './incentives.js';
|
|
51
58
|
export * from './types.js';
|
|
52
59
|
export * from './services.js';
|
|
53
60
|
export * from './verification/signature-validator.js';
|
|
@@ -57,7 +64,14 @@ export * from './verification/common.js';
|
|
|
57
64
|
* Obol sdk Client can be used for creating, managing and activating distributed validators.
|
|
58
65
|
*/
|
|
59
66
|
export class Client extends Base {
|
|
60
|
-
private readonly signer: Signer | undefined;
|
|
67
|
+
private readonly signer: Signer | JsonRpcSigner | undefined;
|
|
68
|
+
public incentives: Incentives;
|
|
69
|
+
public provider:
|
|
70
|
+
| Provider
|
|
71
|
+
| JsonRpcProvider
|
|
72
|
+
| JsonRpcApiProvider
|
|
73
|
+
| undefined
|
|
74
|
+
| null;
|
|
61
75
|
|
|
62
76
|
/**
|
|
63
77
|
* @param config - Client configurations
|
|
@@ -69,9 +83,23 @@ export class Client extends Base {
|
|
|
69
83
|
* An example of how to instantiate obol-sdk Client:
|
|
70
84
|
* [obolClient](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L29)
|
|
71
85
|
*/
|
|
72
|
-
constructor(
|
|
86
|
+
constructor(
|
|
87
|
+
config: { baseUrl?: string; chainId?: number },
|
|
88
|
+
signer?: Signer | JsonRpcSigner,
|
|
89
|
+
provider?: Provider | JsonRpcProvider,
|
|
90
|
+
) {
|
|
73
91
|
super(config);
|
|
74
92
|
this.signer = signer;
|
|
93
|
+
// Use the provided provider, or fall back to signer.provider if available
|
|
94
|
+
this.provider =
|
|
95
|
+
provider ??
|
|
96
|
+
(signer && 'provider' in signer ? signer.provider : undefined);
|
|
97
|
+
this.incentives = new Incentives(
|
|
98
|
+
this.signer,
|
|
99
|
+
this.chainId,
|
|
100
|
+
this.request.bind(this),
|
|
101
|
+
(this.provider = provider),
|
|
102
|
+
);
|
|
75
103
|
}
|
|
76
104
|
|
|
77
105
|
/**
|
|
@@ -521,19 +549,4 @@ export class Client extends Base {
|
|
|
521
549
|
);
|
|
522
550
|
return lock;
|
|
523
551
|
}
|
|
524
|
-
|
|
525
|
-
/**
|
|
526
|
-
* @param address - Operator address
|
|
527
|
-
* @returns {Promise<Incentives>} The matched incentives from DB
|
|
528
|
-
* @throws On not found if address not found.
|
|
529
|
-
*/
|
|
530
|
-
async getIncentivesByAddress(address: string): Promise<Incentives> {
|
|
531
|
-
const incentives: Incentives = await this.request(
|
|
532
|
-
`/${DEFAULT_BASE_VERSION}/address/incentives/${address}`,
|
|
533
|
-
{
|
|
534
|
-
method: 'GET',
|
|
535
|
-
},
|
|
536
|
-
);
|
|
537
|
-
return incentives;
|
|
538
|
-
}
|
|
539
552
|
}
|
|
@@ -23,7 +23,7 @@ export const validateAddressSignature = async ({
|
|
|
23
23
|
chainId: number;
|
|
24
24
|
}): Promise<boolean> => {
|
|
25
25
|
try {
|
|
26
|
-
const provider = getProvider(chainId);
|
|
26
|
+
const provider = getProvider(chainId); // [TODO Hanan], should expect it from signer.provider, or passed in client
|
|
27
27
|
if (provider) {
|
|
28
28
|
const contractAddress = await isContractAvailable(address, provider);
|
|
29
29
|
if (contractAddress) {
|