@obolnetwork/obol-sdk 2.11.9 → 2.11.12

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.
@@ -1,35 +1,35 @@
1
1
  import type { ProviderType, ExitClusterConfig, ExitValidationPayload, ExitValidationBlob, SignedExitValidationMessage, ExistingExitValidationBlobData, FullExitBlob } from '../types.js';
2
2
  /**
3
- * Exit validation and verification class for Obol distributed validators.
3
+ * Validates and verifies voluntary exit signatures for Obol distributed validators.
4
4
  *
5
- * This class provides functionality to validate and verify voluntary exit signatures
6
- * for distributed validators in an Obol cluster. It handles both partial exit signatures
7
- * from individual operators and payload signatures that authorize exit operations.
5
+ * Do not instantiate directly; access via `client.exit`.
8
6
  *
9
- * The class supports:
10
- * - Verification of BLS signatures for partial exit messages
11
- * - Verification of ECDSA signatures for exit payload authorization
12
- * - Validation of exit blobs against cluster configuration
13
- * - Duplicate detection and epoch validation
7
+ * Available methods:
8
+ * - {@link Exit.verifyPartialExitSignature} verify a BLS partial exit signature
9
+ * - {@link Exit.verifyExitPayloadSignature} verify an ECDSA exit payload signature
10
+ * - {@link Exit.validateExitBlobs} – validate exit blobs against cluster config
11
+ * - {@link Exit.recombineExitBlobs} aggregate partial signatures into a full exit blob
12
+ *
13
+ * All methods are read-only (no on-chain transactions). No signer is required.
14
14
  *
15
15
  * @example
16
16
  * ```typescript
17
- * const exit = new Exit(1, provider); // Mainnet with provider
17
+ * const client = new Client({ chainId: 1 }, signer, provider);
18
18
  *
19
19
  * // Verify a partial exit signature
20
- * const isValid = await exit.verifyPartialExitSignature(
20
+ * const isValid = await client.exit.verifyPartialExitSignature(
21
21
  * publicShareKey,
22
22
  * signedExitMessage,
23
23
  * forkVersion,
24
- * genesisValidatorsRoot
24
+ * genesisValidatorsRoot,
25
25
  * );
26
26
  *
27
27
  * // Validate exit blobs for a cluster
28
- * const validBlobs = await exit.validateExitBlobs(
28
+ * const validBlobs = await client.exit.validateExitBlobs(
29
29
  * clusterConfig,
30
30
  * exitsPayload,
31
31
  * beaconNodeApiUrl,
32
- * existingBlobData
32
+ * existingBlobData,
33
33
  * );
34
34
  * ```
35
35
  */
@@ -44,11 +44,8 @@ export declare class Exit {
44
44
  *
45
45
  * @example
46
46
  * ```typescript
47
- * // For mainnet with a provider
48
- * const exit = new Exit(1, provider);
49
- *
50
- * // For goerli testnet without provider
51
- * const exit = new Exit(5, null);
47
+ * const client = new Client({ chainId: 1 }, signer, provider);
48
+ * const exit = client.exit; // Access via client, do not instantiate directly
52
49
  * ```
53
50
  */
54
51
  constructor(chainId: number, provider: ProviderType | undefined | null);
@@ -1,11 +1,23 @@
1
1
  import { type ClaimableIncentives, type ETH_ADDRESS, type ProviderType, type SignerType, type ClaimIncentivesResponse } from '../types.js';
2
2
  /**
3
- * Incentives can be used for fetching and claiming Obol incentives.
4
- * @class
5
- * @internal Access it through Client.incentives.
3
+ * Manages Obol incentive rewards querying eligibility and claiming from
4
+ * on-chain Merkle Distributor contracts.
5
+ *
6
+ * Do not instantiate directly; access via `client.incentives`.
7
+ *
6
8
  * @example
7
- * const obolClient = new Client(config);
8
- * await obolClient.incentives.claimIncentives(address);
9
+ * ```typescript
10
+ * const client = new Client({ chainId: 560048 }, signer);
11
+ *
12
+ * // Check claimable incentives
13
+ * const data = await client.incentives.getIncentivesByAddress("0xOperator...");
14
+ *
15
+ * // Check if already claimed
16
+ * const claimed = await client.incentives.isClaimed(data.contract_address, data.index);
17
+ *
18
+ * // Claim (sends on-chain transaction)
19
+ * const { txHash } = await client.incentives.claimIncentives("0xOperator...");
20
+ * ```
9
21
  */
10
22
  export declare class Incentives {
11
23
  private readonly signer;
@@ -14,44 +26,61 @@ export declare class Incentives {
14
26
  readonly provider: ProviderType | undefined | null;
15
27
  constructor(signer: SignerType | undefined, chainId: number, request: (endpoint: string, options?: RequestInit) => Promise<any>, provider: ProviderType | undefined | null);
16
28
  /**
17
- * Claims Obol incentives from a Merkle Distributor contract using an address.
29
+ * Claims Obol incentives from a Merkle Distributor contract for the given address.
18
30
  *
19
- * This method automatically fetches incentive data and verifies whether the incentives have already been claimed.
20
- * If `txHash` is `null`, it indicates that the incentives were already claimed.
31
+ * Automatically fetches incentive data, checks if already claimed, and submits
32
+ * the on-chain claim transaction. Returns `{ txHash: null }` if incentives were
33
+ * already claimed (idempotent).
21
34
  *
22
- * Note: This method is not yet enabled and will throw an error if called.
35
+ * @param address - The operator's Ethereum address to claim incentives for.
36
+ * @returns The transaction hash, or `{ txHash: null }` if already claimed.
37
+ * @throws {SignerRequiredError} If no signer was provided.
38
+ * @throws {Error} If no incentives are found for the address.
23
39
  *
24
- * @remarks
25
- * **⚠️ Important:** If you're storing the private key in an `.env` file, ensure it is securely managed
26
- * and not pushed to version control.
27
- *
28
- * @param {string} address - The address to claim incentives for
29
- * @returns {Promise<ClaimIncentivesResponse>} The transaction hash or already claimed status
30
- * @throws Will throw an error if the incentives data is not found or the claim fails
31
- *
32
- * An example of how to use claimIncentives:
33
- * [obolClient](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L281)
40
+ * @example
41
+ * ```typescript
42
+ * const { txHash } = await client.incentives.claimIncentives("0xOperator...");
43
+ * if (txHash) {
44
+ * console.log("Claimed:", txHash);
45
+ * } else {
46
+ * console.log("Already claimed");
47
+ * }
48
+ * ```
34
49
  */
35
50
  claimIncentives(address: string): Promise<ClaimIncentivesResponse>;
36
51
  /**
37
- * Read isClaimed.
52
+ * Checks whether incentives have already been claimed for a given operator index.
38
53
  *
39
- * @param {ETH_ADDRESS} contractAddress - Address of the Merkle Distributor Contract
40
- * @param {ETH_ADDRESS} index - operator index in merkle tree
41
- * @returns {Promise<boolean>} true if incentives are already claime
54
+ * Read-only on-chain call no transaction sent.
42
55
  *
56
+ * @param contractAddress - Address of the Merkle Distributor contract.
57
+ * @param index - The operator's index in the Merkle tree (from {@link Incentives.getIncentivesByAddress}).
58
+ * @returns `true` if the incentives at that index have already been claimed.
43
59
  *
44
- * An example of how to use isClaimed:
45
- * [obolClient](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L266)
60
+ * @example
61
+ * ```typescript
62
+ * const claimed = await client.incentives.isClaimed(
63
+ * "0xMerkleDistributor...",
64
+ * 42,
65
+ * );
66
+ * ```
46
67
  */
47
68
  isClaimed(contractAddress: ETH_ADDRESS, index: number): Promise<boolean>;
48
69
  /**
49
- * @param address - Operator address
50
- * @returns {Promise<IncentivesType>} The matched incentives from DB
51
- * @throws On not found if address not found.
70
+ * Fetches claimable incentive data for an operator address from the Obol API.
71
+ *
72
+ * The returned data includes the Merkle proof, amount, and contract address
73
+ * needed to claim on-chain. This is a read-only API call.
74
+ *
75
+ * @param address - The operator's Ethereum address.
76
+ * @returns Claimable incentive data including amount, Merkle proof, and contract address.
77
+ * @throws {Error} If no incentives are found for the given address (404).
52
78
  *
53
- * An example of how to use getIncentivesByAddress:
54
- * [obolClient](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L250)
79
+ * @example
80
+ * ```typescript
81
+ * const incentives = await client.incentives.getIncentivesByAddress("0xOperator...");
82
+ * console.log(incentives.amount, incentives.contract_address);
83
+ * ```
55
84
  */
56
85
  getIncentivesByAddress(address: string): Promise<ClaimableIncentives>;
57
86
  }
@@ -9,150 +9,337 @@ export * from './services.js';
9
9
  export * from './verification/signature-validator.js';
10
10
  export * from './verification/common.js';
11
11
  export * from './constants.js';
12
+ export { ConflictError, SignerRequiredError, UnsupportedChainError, } from './errors.js';
12
13
  export { Incentives } from './incentives/incentives.js';
13
14
  export { Exit } from './exits/exit.js';
14
15
  export { ObolSplits } from './splits/splits.js';
15
16
  export { EOA } from './eoa/eoa.js';
17
+ export { blsVerify } from './blsUtils.js';
16
18
  /**
17
- * Obol sdk Client can be used for creating, managing and activating distributed validators.
19
+ * Primary entrypoint for the Obol SDK. Use this class to create, manage,
20
+ * and activate Distributed Validators via the Obol API.
21
+ *
22
+ * All operations are namespaced under the client instance:
23
+ * - `client.incentives.*` – claim and query Obol incentives
24
+ * - `client.exit.*` – validate and recombine voluntary exit signatures
25
+ * - `client.splits.*` – deploy OVM / SplitV2 reward-splitting contracts
26
+ * - `client.eoa.*` – EOA withdrawals and batch deposits
27
+ *
28
+ * Cluster lifecycle helpers live directly on the client:
29
+ * - {@link Client.acceptObolLatestTermsAndConditions}
30
+ * - {@link Client.createClusterDefinition}
31
+ * - {@link Client.acceptClusterDefinition}
32
+ * - {@link Client.getClusterDefinition}
33
+ * - {@link Client.getClusterLock}
34
+ * - {@link Client.getClusterLockByHash}
35
+ * - {@link Client.createObolRewardsSplit}
36
+ * - {@link Client.createObolTotalSplit}
37
+ * - {@link Client.getOWRTranches}
38
+ *
39
+ * Supported networks (by `chainId`): Mainnet (1), Hoodi (560048),
40
+ * Gnosis (100), Sepolia (11155111).
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * import { Client } from "@obolnetwork/obol-sdk";
45
+ * import { Wallet } from "ethers";
46
+ *
47
+ * const signer = new Wallet(process.env.PRIVATE_KEY!);
48
+ * const client = new Client({ chainId: 560048 }, signer);
49
+ *
50
+ * // 1. Accept terms (required once before creating/updating data)
51
+ * await client.acceptObolLatestTermsAndConditions();
52
+ *
53
+ * // 2. Create a cluster definition
54
+ * const configHash = await client.createClusterDefinition({
55
+ * name: "my-cluster",
56
+ * operators: [{ address: "0x..." }],
57
+ * validators: [{
58
+ * fee_recipient_address: "0x...",
59
+ * withdrawal_address: "0x...",
60
+ * }],
61
+ * });
62
+ *
63
+ * // 3. Retrieve the definition
64
+ * const def = await client.getClusterDefinition(configHash);
65
+ * ```
18
66
  */
19
67
  export declare class Client extends Base {
20
- /**
21
- * The signer used for signing transactions.
22
- */
23
68
  private readonly signer;
24
69
  /**
25
- * The incentives module, responsible for managing Obol tokens distribution.
26
- * @type {Incentives}
70
+ * Incentives module claim and query Obol incentive rewards.
71
+ *
72
+ * @see {@link Incentives}
27
73
  */
28
74
  incentives: Incentives;
29
75
  /**
30
- * The exit module, responsible for managing exit validation.
31
- * @type {Exit}
76
+ * Exit module verify partial exit signatures and recombine exit blobs
77
+ * for distributed validator voluntary exits.
78
+ *
79
+ * @see {@link Exit}
32
80
  */
33
81
  exit: Exit;
34
82
  /**
35
- * The splits module, responsible for managing splits.
36
- * @type {ObolSplits}
83
+ * Splits module deploy OVM and SplitV2 contracts for reward/principal
84
+ * splitting, request withdrawals, and deposit to OVM contracts.
85
+ *
86
+ * @see {@link ObolSplits}
37
87
  */
38
88
  splits: ObolSplits;
39
89
  /**
40
- * The eoa module, responsible for managing EOA operations.
41
- * @type {EOA}
90
+ * EOA module request withdrawals and batch-deposit validators
91
+ * via Externally Owned Account contracts.
92
+ *
93
+ * @see {@link EOA}
42
94
  */
43
95
  eoa: EOA;
44
96
  /**
45
- * The blockchain provider, used to interact with the network.
46
- * It can be null, undefined, or a valid provider instance and defaults to the Signer provider if Signer is passed.
97
+ * The blockchain provider used for on-chain reads.
98
+ * Defaults to `signer.provider` when a signer is supplied.
47
99
  */
48
100
  provider: ProviderType | undefined | null;
49
101
  /**
50
- * @param config - Client configurations
51
- * @param config.baseUrl - obol-api url
52
- * @param config.chainId - Blockchain network ID
53
- * @param signer - ethersJS Signer
54
- * @returns Obol-SDK Client instance
55
- *
56
- * An example of how to instantiate obol-sdk Client:
57
- * [obolClient](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L29)
102
+ * Creates a new Obol SDK client.
103
+ *
104
+ * @param config - Client configuration object.
105
+ * @param config.baseUrl - Obol API base URL. Defaults to `https://api.obol.tech`.
106
+ * @param config.chainId - Target chain ID. Defaults to `560048` (Hoodi).
107
+ * Supported: 1 (Mainnet), 560048 (Hoodi), 100 (Gnosis), 11155111 (Sepolia).
108
+ * @param signer - An ethers `Wallet` or `JsonRpcSigner`. Required for any
109
+ * write operation (creating clusters, deploying splits, claiming incentives).
110
+ * Read-only operations (`getClusterDefinition`, `getClusterLock`) work without a signer.
111
+ * @param provider - An ethers `Provider`. If omitted, falls back to `signer.provider`.
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * // Minimal read-only client (no signer)
116
+ * const readClient = new Client({ chainId: 1 });
117
+ * const def = await readClient.getClusterDefinition(configHash);
118
+ *
119
+ * // Full client with signer for write operations
120
+ * const client = new Client({ chainId: 560048 }, signer);
121
+ * ```
58
122
  */
59
123
  constructor(config: {
60
124
  baseUrl?: string;
61
125
  chainId?: number;
62
126
  }, signer?: SignerType, provider?: ProviderType);
63
127
  /**
64
- * Accepts Obol terms and conditions to be able to create or update data.
65
- * @returns {Promise<string>} terms and conditions acceptance success message.
66
- * @throws On unverified signature or wrong hash.
128
+ * Accepts the latest Obol terms and conditions.
129
+ *
130
+ * **Must be called once** before any write operation
131
+ * (`createClusterDefinition`, `acceptClusterDefinition`, etc.).
132
+ * Calling it again after acceptance throws a {@link ConflictError}.
133
+ *
134
+ * - Requires a `signer` to be provided at client construction.
135
+ * - Signs an EIP-712 typed-data message; no on-chain transaction is sent.
136
+ * - Idempotent per signer address: once accepted, subsequent calls throw `ConflictError`.
67
137
  *
68
- * An example of how to use acceptObolLatestTermsAndConditions:
69
- * [acceptObolLatestTermsAndConditions](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L44)
138
+ * @returns A success message string from the Obol API.
139
+ * @throws {SignerRequiredError} If no signer was provided to the client.
140
+ * @throws {ConflictError} If the terms were already accepted by this address.
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * const message = await client.acceptObolLatestTermsAndConditions();
145
+ * console.log(message); // "Terms and conditions accepted successfully"
146
+ * ```
70
147
  */
71
148
  acceptObolLatestTermsAndConditions(): Promise<string>;
72
149
  /**
73
- * Deploys OWR and Splitter Proxy.
150
+ * Deploys an Optimistic Withdrawal Recipient (OWR) and a Splitter Proxy contract.
151
+ *
152
+ * Use this when the **principal** goes to a single address and only **rewards**
153
+ * are split among recipients. For splitting both principal and rewards, see
154
+ * {@link Client.createObolTotalSplit}.
74
155
  *
75
- * @remarks
76
- * **⚠️ Important:** If you're storing the private key in an `.env` file, ensure it is securely managed
77
- * and not pushed to version control.
156
+ * - Requires a `signer` with ETH to pay deployment gas.
157
+ * - Sends one or more on-chain transactions (irreversible).
158
+ * - Automatically appends the Obol Retroactive Funding (RAF) recipient at the
159
+ * configured percentage (default 1%).
160
+ * - Only supported on Mainnet (1) and Hoodi (560048).
78
161
  *
79
- * @param {RewardsSplitPayload} rewardsSplitPayload - Data needed to deploy owr and splitter.
80
- * @returns {Promise<ClusterValidator>} owr address as withdrawal address and splitter as fee recipient
162
+ * @param rewardsSplitPayload - Configuration for the OWR and splitter deployment.
163
+ * @returns The deployed OWR address as `withdrawal_address` and the
164
+ * splitter proxy address as `fee_recipient_address`.
165
+ * @throws {SignerRequiredError} If no signer was provided.
166
+ * @throws {UnsupportedChainError} If the chain does not support splitters.
81
167
  *
82
- * An example of how to use createObolRewardsSplit:
83
- * [createObolRewardsSplit](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L141)
168
+ * @example
169
+ * ```typescript
170
+ * const { withdrawal_address, fee_recipient_address } =
171
+ * await client.createObolRewardsSplit({
172
+ * splitRecipients: [
173
+ * { account: "0xOperator1...", percentAllocation: 50 },
174
+ * { account: "0xOperator2...", percentAllocation: 49 },
175
+ * ],
176
+ * principalRecipient: "0xPrincipalAddress...",
177
+ * etherAmount: 32,
178
+ * });
179
+ * ```
84
180
  */
85
181
  createObolRewardsSplit({ splitRecipients, principalRecipient, etherAmount, ObolRAFSplit, distributorFee, controllerAddress, recoveryAddress, }: RewardsSplitPayload): Promise<ClusterValidator>;
86
182
  /**
87
- * Deploys Splitter Proxy.
183
+ * Deploys a Splitter Proxy contract that splits **both principal and rewards**.
184
+ *
185
+ * Unlike {@link Client.createObolRewardsSplit}, this method does not deploy an
186
+ * OWR – the splitter address is used as both `withdrawal_address` and
187
+ * `fee_recipient_address`.
88
188
  *
89
- * @remarks
90
- * **⚠️ Important:** If you're storing the private key in an `.env` file, ensure it is securely managed
91
- * and not pushed to version control.
189
+ * - Requires a `signer` with ETH to pay deployment gas.
190
+ * - Sends an on-chain transaction if the splitter is not already deployed (irreversible).
191
+ * - If the predicted splitter is already deployed, returns the existing address without
192
+ * sending a transaction (idempotent).
193
+ * - Automatically appends the Obol RAF recipient.
194
+ * - Only supported on Mainnet (1) and Hoodi (560048).
92
195
  *
93
- * @param {TotalSplitPayload} totalSplitPayload - Data needed to deploy splitter if it doesnt exist.
94
- * @returns {Promise<ClusterValidator>} splitter address as withdrawal address and splitter as fee recipient too
196
+ * @param totalSplitPayload - Configuration for the splitter deployment.
197
+ * @returns The splitter address as both `withdrawal_address` and `fee_recipient_address`.
198
+ * @throws {SignerRequiredError} If no signer was provided.
199
+ * @throws {UnsupportedChainError} If the chain does not support splitters.
200
+ * @throws {Error} If required on-chain factory contracts are unavailable.
95
201
  *
96
- * An example of how to use createObolTotalSplit:
97
- * [createObolTotalSplit](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L168)
202
+ * @example
203
+ * ```typescript
204
+ * const { withdrawal_address, fee_recipient_address } =
205
+ * await client.createObolTotalSplit({
206
+ * splitRecipients: [
207
+ * { account: "0xOperator1...", percentAllocation: 50 },
208
+ * { account: "0xOperator2...", percentAllocation: 49.9 },
209
+ * ],
210
+ * });
211
+ * // withdrawal_address === fee_recipient_address (same splitter)
212
+ * ```
98
213
  */
99
214
  createObolTotalSplit({ splitRecipients, ObolRAFSplit, distributorFee, controllerAddress, }: TotalSplitPayload): Promise<ClusterValidator>;
100
215
  /**
101
- * Read OWR Tranches.
216
+ * Reads the tranches of a deployed Optimistic Withdrawal Recipient (OWR) contract.
102
217
  *
103
- * @remarks
104
- * **⚠️ Important:** If you're storing the private key in an `.env` file, ensure it is securely managed
105
- * and not pushed to version control.
218
+ * Returns the principal recipient, reward recipient, and staked principal amount
219
+ * configured in the OWR. This is a read-only on-chain call (no transaction sent).
106
220
  *
107
- * @param {ETH_ADDRESS} owrAddress - Address of the Deployed OWR Contract
108
- * @returns {Promise<OWRTranches>} owr tranch information about principal and reward reciepient, as well as the principal amount
221
+ * - Requires a `signer` (for provider access).
109
222
  *
223
+ * @param owrAddress - The Ethereum address of the deployed OWR contract.
224
+ * @returns The OWR tranche data: `principalRecipient`, `rewardRecipient`, and `amountOfPrincipalStake`.
225
+ * @throws {SignerRequiredError} If no signer was provided.
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * const tranches = await client.getOWRTranches("0xOWRAddress...");
230
+ * console.log(tranches.principalRecipient);
231
+ * console.log(tranches.rewardRecipient);
232
+ * console.log(tranches.amountOfPrincipalStake);
233
+ * ```
110
234
  */
111
235
  getOWRTranches(owrAddress: ETH_ADDRESS): Promise<OWRTranches>;
112
236
  /**
113
- * Creates a cluster definition which contains cluster configuration.
114
- * @param {ClusterPayload} newCluster - The new unique cluster.
115
- * @returns {Promise<string>} config_hash.
116
- * @throws On duplicate entries, missing or wrong cluster keys.
237
+ * Creates a new cluster definition and registers it with the Obol API.
238
+ *
239
+ * A cluster definition describes the operators, validators, and configuration
240
+ * for a Distributed Key Generation (DKG) ceremony. After creation, each operator
241
+ * must call {@link Client.acceptClusterDefinition} to join.
242
+ *
243
+ * - Requires {@link Client.acceptObolLatestTermsAndConditions} to have been called first.
244
+ * - Requires a `signer`.
245
+ * - The `config_hash` returned is the unique identifier for this cluster.
246
+ * - Threshold is automatically set to `ceil(2/3 * operators.length)`.
247
+ * - Creating a duplicate cluster throws a {@link ConflictError}.
248
+ *
249
+ * @param newCluster - The cluster configuration payload.
250
+ * @returns The `config_hash` string that uniquely identifies this cluster definition.
251
+ * @throws {SignerRequiredError} If no signer was provided.
252
+ * @throws {ConflictError} If an identical cluster definition already exists.
117
253
  *
118
- * An example of how to use createClusterDefinition:
119
- * [createObolCluster](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L59)
254
+ * @example
255
+ * ```typescript
256
+ * const configHash = await client.createClusterDefinition({
257
+ * name: "my-dvt-cluster",
258
+ * operators: [{ address: "0xOp1..." }, { address: "0xOp2..." }],
259
+ * validators: [{
260
+ * fee_recipient_address: "0xFeeRecipient...",
261
+ * withdrawal_address: "0xWithdrawal...",
262
+ * }],
263
+ * });
264
+ * console.log("Cluster created:", configHash);
265
+ * ```
120
266
  */
121
267
  createClusterDefinition(newCluster: ClusterPayload): Promise<string>;
122
268
  /**
123
- * Approves joining a cluster with specific configuration.
124
- * @param {OperatorPayload} operatorPayload - The operator data including signatures.
125
- * @param {string} configHash - The config hash of the cluster which the operator confirms joining to.
126
- * @returns {Promise<ClusterDefinition>} The cluster definition.
127
- * @throws On unauthorized, duplicate entries, missing keys, not found cluster or invalid data.
128
- *
129
- * An example of how to use acceptClusterDefinition:
130
- * [acceptClusterDefinition](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L106)
269
+ * Accepts (joins) an existing cluster definition as an operator.
270
+ *
271
+ * Each operator in the cluster must call this method with their ENR and the
272
+ * cluster's `config_hash` to signal readiness for the DKG ceremony.
273
+ *
274
+ * - Requires a `signer` (the operator's address is derived from it).
275
+ * - Signs two EIP-712 messages: one for the config hash, one for the ENR.
276
+ * - The operator must be listed in the cluster definition's `operators` array.
277
+ *
278
+ * @param operatorPayload - Operator data. Must include `enr` (Ethereum Node Record)
279
+ * and `version` at minimum.
280
+ * @param configHash - The `config_hash` returned by {@link Client.createClusterDefinition}.
281
+ * @returns The updated cluster definition with the operator's acceptance recorded.
282
+ * @throws {SignerRequiredError} If no signer was provided.
283
+ *
284
+ * @example
285
+ * ```typescript
286
+ * const updatedDef = await client.acceptClusterDefinition(
287
+ * { enr: "enr:-LK4Q...", version: "v1.10.0" },
288
+ * configHash,
289
+ * );
290
+ * ```
131
291
  */
132
292
  acceptClusterDefinition(operatorPayload: OperatorPayload, configHash: string): Promise<ClusterDefinition>;
133
293
  /**
134
- * @param configHash - The configuration hash returned in createClusterDefinition
135
- * @returns {Promise<ClusterDefinition>} The cluster definition for config hash
136
- * @throws On not found config hash.
294
+ * Retrieves a cluster definition by its configuration hash.
295
+ *
296
+ * This is a read-only API call – no signer is required.
137
297
  *
138
- * An example of how to use getClusterDefinition:
139
- * [getObolClusterDefinition](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L74)
298
+ * @param configHash - The `config_hash` returned by {@link Client.createClusterDefinition}.
299
+ * @returns The full cluster definition including operators, validators, and metadata.
300
+ * @throws {Error} If no cluster definition exists for the given hash (404).
301
+ *
302
+ * @example
303
+ * ```typescript
304
+ * const definition = await client.getClusterDefinition(configHash);
305
+ * console.log(definition.name, definition.operators.length);
306
+ * ```
140
307
  */
141
308
  getClusterDefinition(configHash: string): Promise<ClusterDefinition>;
142
309
  /**
143
- * @param configHash - The configuration hash in cluster-definition
144
- * @returns {Promise<ClusterLock>} The matched cluster details (lock) from DB
145
- * @throws On not found cluster definition or lock.
310
+ * Retrieves a cluster lock by the cluster's configuration hash.
311
+ *
312
+ * A cluster lock is generated after a successful DKG ceremony and contains
313
+ * the distributed validators, their public key shares, and deposit data.
314
+ * This is a read-only API call – no signer is required.
146
315
  *
147
- * An example of how to use getClusterLock:
148
- * [getObolClusterLock](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L89)
316
+ * @param configHash - The `config_hash` from the cluster definition.
317
+ * @returns The cluster lock including distributed validators and deposit data.
318
+ * @throws {Error} If no lock exists for the given config hash (DKG not yet complete, or not found).
319
+ *
320
+ * @example
321
+ * ```typescript
322
+ * const lock = await client.getClusterLock(configHash);
323
+ * console.log(lock.distributed_validators.length);
324
+ * console.log(lock.lock_hash);
325
+ * ```
149
326
  */
150
327
  getClusterLock(configHash: string): Promise<ClusterLock>;
151
328
  /**
152
- * @param lockHash - The configuration hash in cluster-definition
153
- * @returns {Promise<ClusterLock>} The matched cluster details (lock) from DB
154
- * @throws On not found cluster definition or lock.
329
+ * Retrieves a cluster lock by its lock hash (as opposed to config hash).
330
+ *
331
+ * Use this when you have the `lock_hash` directly rather than the `config_hash`.
332
+ * This is a read-only API call – no signer is required.
333
+ *
334
+ * @param lockHash - The `lock_hash` from a cluster lock.
335
+ * @returns The cluster lock including distributed validators and deposit data.
336
+ * @throws {Error} If no lock exists for the given lock hash (not found).
155
337
  *
338
+ * @example
339
+ * ```typescript
340
+ * const lock = await client.getClusterLockByHash(lockHash);
341
+ * console.log(lock.cluster_definition.name);
342
+ * ```
156
343
  */
157
344
  getClusterLockByHash(lockHash: string): Promise<ClusterLock>;
158
345
  }
@@ -1,12 +1,31 @@
1
1
  import { type SafeRpcUrl, type ClusterLock } from './types.js';
2
2
  /**
3
- * Verifies Cluster Lock's validity.
4
- * @param lock - cluster lock
5
- * @param safeRpcUrl - optional safeRpcUrl for safe wallet verification
6
- * @returns {Promise<{ result: boolean }> } boolean result to indicate if lock is valid
7
- * @throws on missing keys or values.
8
- *
9
- * An example of how to use validateClusterLock:
10
- * [validateClusterLock](https://github.com/ObolNetwork/obol-sdk-examples/blob/main/TS-Example/index.ts#L127)
3
+ * Verifies the cryptographic validity of a cluster lock.
4
+ *
5
+ * Checks all operator signatures, config hashes, and definition hashes within
6
+ * the lock. Supports both EOA and Safe Wallet signatures.
7
+ *
8
+ * This is a standalone utility – it does **not** require a `Client` instance.
9
+ *
10
+ * **Charon:** this covers cryptographic and structural checks in this SDK only.
11
+ * Charon adds further runtime/protocol rules. `true` here does not guarantee
12
+ * Charon will accept the lock; use Charon or its supported tooling as the
13
+ * final gate before operating a cluster.
14
+ *
15
+ * @param lock - The cluster lock object (e.g. from {@link Client.getClusterLock}).
16
+ * @param safeRpcUrl - Optional RPC URL for Safe Wallet signature verification.
17
+ * If omitted, falls back to the `RPC_MAINNET` / `RPC_HOODI` / etc. env vars.
18
+ * @returns `true` if the lock is cryptographically valid; `false` if invalid
19
+ * (e.g. missing keys, invalid signatures, hash mismatches) or on any error.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * import { validateClusterLock, Client } from "@obolnetwork/obol-sdk";
24
+ *
25
+ * const configHash = "0x..."; // your cluster config hash
26
+ * const client = new Client({ chainId: 560048 });
27
+ * const lock = await client.getClusterLock(configHash);
28
+ * const isValid = await validateClusterLock(lock);
29
+ * ```
11
30
  */
12
31
  export declare const validateClusterLock: (lock: ClusterLock, safeRpcUrl?: SafeRpcUrl) => Promise<boolean>;