@obolnetwork/obol-sdk
v2.12.0
Published
A package for creating Distributed Validators using the Obol API.
Readme

This repo contains the Obol Software Development Kit, for creating Distributed Validators with the help of the Obol API.
Quick Start
import { Client } from "@obolnetwork/obol-sdk";
import { Wallet } from "ethers";
// 1. Create a client (Hoodi testnet)
const signer = new Wallet(process.env.PRIVATE_KEY!);
const client = new Client({ chainId: 560048 }, signer);
// 2. Accept terms and conditions (required once per address)
await client.acceptObolLatestTermsAndConditions();
// 3. Create a cluster definition
const configHash = await client.createClusterDefinition({
name: "my-cluster",
operators: [{ address: "0xOperator1..." }, { address: "0xOperator2..." }],
validators: [{
fee_recipient_address: "0xFeeRecipient...",
withdrawal_address: "0xWithdrawal...",
}],
});
// 4. Retrieve the definition / lock (read-only, no signer needed)
const definition = await client.getClusterDefinition(configHash);
const lock = await client.getClusterLock(configHash);Getting Started
Checkout our docs, examples, and SDK reference. Further guides and walkthroughs coming soon.
Terms and Conditions
To use obol-sdk and in order to be able to create a cluster definition or accept an invite to join a cluster, you must accept the latest Obol terms and conditions by calling acceptObolLatestTermsAndConditions.
⚠️ Important Security Notice:
If you're integrating this SDK with a backend (e.g., in Node.js), and you store a private key for executing splitter transactions, handle it with extreme caution. Ensure that:
- The private key is securely stored (e.g., in an
.envfile). - Never commit or push your
.envfile containing the private key to version control.
⚡️ Integration with Safe Wallet
When integrating the Obol SDK with a Safe Wallet, you can either pass an RPC URL OR provide the RPC_MAINNET or RPC_GNOSIS or RPC_SEPOLIA or RPC_HOODI environment variable, pointing to the correct network's RPC URL. This is required to interact with Safe kit.
Contributing
Please review the following guidelines:
How to Report Bugs
If you encounter a bug or unexpected behavior, please follow these steps to report it:
- Go to the "Issues" tab of this repository.
- Click on the "Get started" button in the Bug report section.
- Provide a clear title and description of the issue following the format provided.
How to Propose Changes
If you'd like to propose improvements or new features, please follow these steps:
- Fork this repository.
- Create a new branch for your changes.
- Make your changes and commit them with clear messages.
- Open a pull request with a detailed description of the changes.
Code Review Process
All contributions are reviewed before they are merged into the main branch. Please address any feedback provided during the review process.
Thank you for contributing to Obol-SDK!
For AI Agents and Code Generators
If you are an LLM, code-generation agent, or tool using this SDK programmatically, follow these guidelines to avoid common mistakes:
- Primary entrypoint:
Clientfrom@obolnetwork/obol-sdk. - Constructor:
new Client({ chainId }, signer?, provider?)–chainIddefaults to560048(Hoodi). - All operations are namespaced under the client instance. Do not construct HTTP requests manually.
Client API Surface
| Namespace | Method | Description |
|-----------|--------|-------------|
| (root) | acceptObolLatestTermsAndConditions() | Accept T&C (required once before writes) |
| (root) | createClusterDefinition(payload) | Create a new cluster → returns config_hash |
| (root) | acceptClusterDefinition(operatorPayload, configHash) | Join a cluster as an operator |
| (root) | getClusterDefinition(configHash) | Fetch cluster definition (read-only) |
| (root) | getClusterLock(configHash) | Fetch cluster lock by config hash (read-only) |
| (root) | getClusterLockByHash(lockHash) | Fetch cluster lock by lock hash (read-only) |
| (root) | createObolRewardsSplit(payload) | Deploy OWR + splitter (rewards-only split) |
| (root) | createObolTotalSplit(payload) | Deploy splitter (total split) |
| (root) | getOWRTranches(owrAddress) | Read OWR tranche info (read-only on-chain) |
| client.splits | createValidatorManagerAndRewardsSplit(payload) | Deploy OVM + SplitV2 (rewards-only) |
| client.splits | createValidatorManagerAndTotalSplit(payload) | Deploy OVM + SplitV2 (total) |
| client.splits | requestWithdrawal(payload) | Request withdrawal from OVM |
| client.splits | deposit(payload) | Deposit to OVM contract |
| client.incentives | getIncentivesByAddress(address) | Fetch claimable incentives (read-only) |
| client.incentives | isClaimed(contractAddress, index) | Check if incentives claimed (read-only) |
| client.incentives | claimIncentives(address) | Claim incentives on-chain |
| client.eoa | requestWithdrawal(payload) | Request EOA withdrawal |
| client.eoa | deposit(payload) | Batch deposit validators |
| client.exit | verifyPartialExitSignature(...) | Verify BLS partial exit signature |
| client.exit | verifyExitPayloadSignature(...) | Verify ECDSA exit payload signature |
| client.exit | validateExitBlobs(...) | Validate exit blobs against cluster config |
| client.exit | recombineExitBlobs(...) | Aggregate partial exit signatures |
Standalone Utilities
| Export | Description |
|--------|-------------|
| validateClusterLock(lock, safeRpcUrl?) | Cryptographic verification (signatures, hashes). Does not mirror every Charon runtime check—passing here is necessary, not sufficient, for Charon. |
Key Rules
- All request/response types are exported from the package root.
- Error types exported:
ConflictError,SignerRequiredError,UnsupportedChainError. - Methods that write (create, deploy, claim) require a
signer. Methods that read (get, fetch) do not. - Supported chain IDs:
1(Mainnet),560048(Hoodi). - Splitter/OVM deployment is only supported on chains 1 and 560048.
Next.js / SSR Configuration
If using this SDK in Next.js or other SSR frameworks, add this minimal config to your next.config.js:
webpack: (config, { isServer, webpack }) => {
if (!isServer) {
config.plugins.push(
new webpack.DefinePlugin({
'process.stdout.isTTY': 'false',
'process.stderr.isTTY': 'false',
})
);
} else {
// Server: Externalize native dependencies
config.externals = config.externals || [];
config.externals.push({
'@chainsafe/bls': 'commonjs @chainsafe/bls',
'bcrypto': 'commonjs bcrypto',
});
}
// Ignore .node files
config.plugins.push(
new webpack.IgnorePlugin({ resourceRegExp: /\.node$/ })
);
return config;
}