Build Kleros-facing dispute workflows from a TypeScript app. The SDK prepares unsigned transactions for dispute creation, evidence submission, meta-evidence amendments, appeals, and event decoding; your user's wallet remains responsible for signing and broadcasting.
The SDK never holds private keys and never takes custody of funds.
Your app -> Disputes SDK -> unsigned transaction -> user wallet -> blockchain
# Choose one integration. The SDK core has no Ethers or Viem dependency.
npm install @rakelabs/disputes-sdk @rakelabs/ethers-adapter ethers
# or
npm install @rakelabs/disputes-sdk @rakelabs/viem-adapter viemRequirements:
- Node.js 20+
- an RPC client and ABI codec supplied by your wallet/RPC integration
ethersis only needed for the ethers adapter shown below; viem integrations can be used instead
Use this package when your application needs a standalone Kleros dispute contract:
- choose Kleros court parameters with
extraData, - publish or reference a MetaEvidence URI,
- create a dispute with a fixed number of ruling options,
- submit evidence documents,
- read dispute state, evidence timelines, rulings, and events,
- prepare appeal transactions when the ruling can be appealed.
If your product is specifically escrow or payment oriented, start with @rakelabs/klescrow-sdk or @rakelabs/dpayments-sdk. Use this package when you need direct dispute primitives.
import { BrowserProvider } from 'ethers';
import { Disputes, ABI, extraData } from '@rakelabs/disputes-sdk';
import {
createEthersRpcClient,
createEthersAbiCodec,
} from '@rakelabs/ethers-adapter';
const provider = new BrowserProvider(window.ethereum);
await provider.send('eth_requestAccounts', []);
const signer = await provider.getSigner();
const walletAddress = await signer.getAddress();
const rpc = createEthersRpcClient(provider);
const codec = createEthersAbiCodec(ABI);
const disputes = await Disputes.fromRpc(rpc, { codec, walletAddress });
const arbitratorExtraData = extraData.generalCourt();
const estimate = await disputes.factory.estimateCost(arbitratorExtraData);
console.log('Total dispute cost:', estimate.total.toString());
const { tx, disputeId } = await disputes.factory.prepareCreateDispute({
arbitratorExtraData,
metaEvidenceUri: 'ipfs://QmYourMetaEvidenceDocument',
numberOfRulingOptions: 2n,
});
console.log(tx.preview);
const response = await signer.sendTransaction({
to: tx.to,
data: tx.data,
value: BigInt(tx.value),
});
await response.wait();
const created = (await disputes.factory.getLogs(0, 'latest'))
.find((event) => event.disputeId === disputeId);
if (!created) {
throw new Error('Dispute creation event was not found');
}
const dispute = disputes.dispute(created.instance);Kleros workflows usually have two document layers:
- MetaEvidence describes the dispute category, question, policy, and ruling options.
- Evidence describes the proof submitted for one specific dispute.
Use @rakelabs/evidence-publisher to build and publish both document types to IPFS, then pass the returned ipfs://... URIs into this SDK.
const info = await dispute.read();
console.log(info.state);
console.log(info.owner);
console.log(info.providerDisputeId);
console.log(info.numberOfRulingOptions);const evidenceTx = dispute.submitEvidence('ipfs://QmYourEvidenceDocument');
console.log(evidenceTx.preview);
await signer.sendTransaction({
to: evidenceTx.to,
data: evidenceTx.data,
value: BigInt(evidenceTx.value),
});const timeline = await dispute.getEvidenceTimeline(0, 'latest');
for (const event of timeline) {
console.log(event.submittedAt, event.party, event.evidenceUri);
}const [appealFeeWei, appealPeriod] = await Promise.all([
dispute.appealCost(),
dispute.appealPeriod(),
]);
if (appealPeriod.end === 0n) {
throw new Error('No appeal window is currently open');
}
const appealTx = dispute.appeal('0x', appealFeeWei);
await signer.sendTransaction({
to: appealTx.to,
data: appealTx.data,
value: BigInt(appealTx.value),
});Kleros uses extraData to select the court and minimum juror count.
import {
buildArbitratorExtraData,
parseArbitratorExtraData,
extraData,
} from '@rakelabs/disputes-sdk';
const encoded = buildArbitratorExtraData(0, 3);
const generalCourt = extraData.generalCourt();
const decoded = parseArbitratorExtraData(encoded);
console.log(generalCourt, decoded.subcourtId, decoded.minJurors);The core SDK does not inspect wallet/provider exceptions. Your ethers or viem integration should extract revert data and pass it to its ABI codec.
import { decodeEthersError } from '@rakelabs/ethers-adapter';
try {
await signer.sendTransaction({
to: tx.to,
data: tx.data,
value: BigInt(tx.value),
});
} catch (err) {
const decoded = decodeEthersError(err, codec);
if (decoded) {
console.error(decoded.name, decoded.args);
}
}This README and the linked guides describe the unreleased 0.2.0 API until
that version is tagged. For 0.1.x usage, open the matching Git release tag.
| Document | Use it for |
|---|---|
| docs/reference.md | API reference, types, actions, events, and common mistakes |
| docs/advanced.md | Reader, transaction builder, multicall, and implementation selection |
| docs/migration-0.1-to-0.2.md | Migrate from provider-based initialization |
| docs/on-chain.md | Contract-level behavior and event model |
- Always show
tx.previewbefore requesting a signature. - Store the dispute contract address after creation; it is the canonical on-chain handle.
- Publish durable MetaEvidence and Evidence URIs before submitting them on-chain.
- Check chain IDs, court parameters, ruling options, and contract addresses before sending transactions.
- This software interacts with autonomous contracts. Users transact at their own risk.