Skip to main content

Documentation Index

Fetch the complete documentation index at: https://kleros-mintlify-changelog-2026-05-12-1778458371.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Before You Deploy

Use this checklist before deploying to mainnet. Each item has bitten someone before.

Smart Contract Security

Access Control

  • rule() only callable by arbitrator
    require(msg.sender == address(arbitrator), "Only arbitrator");
    
  • Dispute creation restricted to valid parties
  • Evidence submission restricted appropriately
  • Admin functions protected (if any)

State Management

  • Disputes cannot be ruled twice
    require(status == Status.Disputed, "Invalid state");
    status = Status.Resolved; // Update BEFORE external calls
    
  • Invalid ruling values rejected
    require(_ruling <= numberOfChoices, "Invalid ruling");
    
  • Ruling 0 (refuse to arbitrate) handled explicitly
  • All state transitions are valid (no skipping states)

Reentrancy Protection

  • State updated before external calls in rule()
  • Use checks-effects-interactions pattern
  • Consider ReentrancyGuard for complex logic
    // BAD: external call before state update
    payable(winner).transfer(amount);
    status = Status.Resolved;
    
    // GOOD: state update before external call  
    status = Status.Resolved;
    payable(winner).transfer(amount);
    

Fund Safety

  • No funds can be permanently locked
  • Failed transfers don’t brick the contract
  • Consider pull-over-push for withdrawals
  • Test with contracts as recipients (not just EOAs)

Arbitration Configuration

Court Selection

CheckStatus
Court ID exists on target network
Court handles your dispute type
Minimum stake is acceptable
Court timing fits your use case
Kleros V2 Court IDs (Arbitrum One):
Court IDNameTypical Use
1General CourtDefault for most disputes. Good starting point.
2BlockchainTechnical blockchain/smart contract disputes
3Non-TechnicalBusiness, marketing, and content disputes
4Token ListingToken curation and listing decisions
5+SpecializedCheck court.kleros.io for the current tree
Court IDs on Arbitrum Sepolia (testnet) differ from Arbitrum One (mainnet). Always verify IDs from the kleros-v2 deployments for your target network.
Court parameters (minStake, feeForJuror, session timings) are governed and can change via governance proposals. Query them from the courts(courtID) getter on KlerosCore rather than hardcoding.

Extra Data

  • extraData correctly encodes court ID and juror count
    bytes memory extraData = abi.encodePacked(
        uint96(1),   // courtID
        uint256(3)   // minJurors
    );
    
  • Juror count is odd (avoids ties)
  • Juror count appropriate for dispute value

Fee Handling

  • Always fetch fresh arbitrationCost() before creating dispute
  • Handle fee increases between check and execution
  • Refund excess fees to sender
  • Document who pays arbitration fees
Kleros-specific fee details:
  • If paying in ERC20: verify the token is in acceptedFeeTokens on KlerosCore
  • If paying in ERC20: call arbitrationCost(extraData, feeToken) (not the ETH overload)
  • If paying in ERC20: approve KlerosCore to spend the fee amount before createDispute()
  • Account for appeal costs each appeal round roughly doubles the juror count and cost
  • For cross-chain disputes: the Foreign Gateway mirrors arbitrationCost() locally, but sync may lag after governance changes add a buffer or retry mechanism

Dispute Template

Content Quality

  • Question is clear and unambiguous
  • All ruling options are mutually exclusive
  • Ruling descriptions explain consequences
  • No option can be interpreted multiple ways
  • “Refuse to arbitrate” scenario documented

Technical

  • Template JSON is valid
  • Template stored on permanent storage (IPFS pinned / Arweave)
  • Template URI accessible and resolves correctly
  • policyURI points to valid policy document
  • arbitratorAddress matches deployment
  • arbitratorChainID matches deployment

Example Template Validation

{
  "title": "Clear, specific title",           // ✓ Not generic
  "description": "Full context...",           // ✓ Sufficient detail
  "question": "Single, answerable question?", // ✓ Yes/no or clear options
  "answers": [
    {"id": "0x1", "title": "Option A", "description": "What happens if chosen"},
    {"id": "0x2", "title": "Option B", "description": "What happens if chosen"}
  ],
  "policyURI": "/ipfs/Qm...",                // ✓ Accessible
  "arbitratorChainID": "42161",              // ✓ Correct chain
  "arbitratorAddress": "0x9C1dA9A04925bDfDedf0f6421bC7EEa8305F9002"  // ✓ KlerosCore on Arbitrum One
}

Policy Document

Every dispute should reference a policy document that jurors use to make decisions. This is distinct from the template the template defines the UI; the policy defines the rules.
  • Policy document clearly states criteria for each ruling option
  • Policy covers edge cases (partial fulfillment, ambiguous evidence)
  • Policy states what happens on “Refuse to Arbitrate” (ruling 0)
  • Policy is written for a non-technical audience (jurors are general public)
  • Policy is hosted on pinned IPFS or Arweave (not a URL that can change)
  • Policy has been reviewed for ambiguity by someone unfamiliar with your protocol
Study existing Kleros policies from live products. Escrow V2 and Curate V2 policies are good models. You can find them on IPFS via the dispute template’s policyURI field inspect live disputes on court.kleros.io to see examples.

Dispute Lifecycle Timing

Kleros disputes follow a fixed phase sequence. Understand the timing impact on your application:
PhaseDuration (General Court)Notes
Evidence~3 daysEither party can submit evidence
Voting~3 daysJurors cast encrypted votes (Shutter)
Appeal~5 daysLosing party can fund an appeal
Total (no appeal)~11 daysMinimum time to ruling
Per appeal round+~11 daysEach round adds roughly the same
  • Your application tolerates the dispute resolution timeline (1–4 weeks typical)
  • Funds or state remain locked during the dispute without creating issues
  • Users are informed about expected timelines in your UI
  • You handle the case where appeals extend the timeline significantly

Events & Indexing

  • DisputeRequest emitted with correct parameters
  • Ruling emitted after executing ruling
  • Evidence events use correct evidenceGroupID
  • Events are indexed for efficient querying
  • Subgraph indexes your contract (if using one)

Gas Optimization

Benchmarks

FunctionTargetYour Contract
createDispute()< 200k gas
rule()< 100k gas
submitEvidence()< 50k gas

Optimizations Applied

  • Use immutable for arbitrator address
  • Pack structs efficiently
  • Avoid redundant storage reads
  • Use calldata instead of memory where possible

Deployment

Contract Verification

  • Source code verified on block explorer
  • Constructor arguments documented
  • All dependencies verified
  • License specified in source

Address Verification

ContractExpectedDeployed
KlerosCore0x33d0b8879...▢ Match
Your Arbitrable▢ Verified
Dispute Template▢ Accessible

Initialization

  • Arbitrator address is correct for network
  • Extra data encodes correct court
  • Template URI resolves correctly
  • Any admin roles assigned correctly
  • Contract funded if required

Operational Readiness

Monitoring

  • Alert on DisputeRequest events
  • Alert on Ruling events
  • Monitor arbitration fee changes
  • Track dispute resolution times
  • Dashboard for active disputes
Kleros-specific monitoring:
  • Monitor AppealDecision events from KlerosCore appeals change timeline and cost
  • Subscribe to court parameter changes via CourtModified events fee changes affect your cost calculations
  • Track your disputes on court.kleros.io filter by your contract’s address
  • If cross-chain: monitor Vea bridge claim/challenge status on veascan.io
  • Set up alerts for the KlerosCore Paused event a paused arbitrator blocks new disputes and rulings

Documentation

  • User guide for creating disputes
  • Evidence submission instructions
  • Explanation of ruling outcomes
  • FAQ for common questions
  • Support contact for issues

Incident Response

  • Plan for paused arbitrator scenario
  • Plan for unexpected ruling
  • Emergency contact at Kleros (if needed)
  • Upgrade path documented (if upgradeable)
Kleros-specific failure scenarios:
  • Arbitrator paused: KlerosCore has an emergency pause mechanism controlled by the Guardian role. If paused, no new disputes can be created and no rulings can be delivered. Your contract should handle a createDispute() revert gracefully and inform users.
  • Ruling 0 (Refuse to Arbitrate): This ruling means jurors found the dispute invalid. Your contract must handle this case explicitly do not treat it the same as any party winning.
  • Tied vote: In the current DisputeKitClassic, a tied vote defaults to ruling 0 (refuse to arbitrate) unless overridden by appeal. Design your ruling-0 handler to produce a reasonable fallback (e.g., refund both parties).
  • Contract address change: V2 contracts are upgradeable proxies. After security audits, addresses may be preserved but implementation changes. If you store the arbitrator address as immutable, you cannot adapt consider using a governor-updatable address.
  • Vea bridge delay (cross-chain only): In the unhappy path, ruling delivery can take ~7 days via the native Arbitrum bridge. Ensure your locked state can persist for at least 2 weeks without causing user issues.

Kleros V2 on Mainnet

Kleros V2 (Neo) is live on Arbitrum One mainnet. Court access is open to all — users can stake PNK and participate as jurors. These items are still relevant for integrators:
  • Contract upgrades: V2 contracts use UUPS proxy patterns and may be upgraded via governance. Subscribe to the Kleros blog and Discord for announcements.
  • Subgraph stability: Pin to a specific subgraph deployment version in production. Check the kleros-v2 subgraph README for the latest stable deployment IDs.
  • SDK status: @kleros/kleros-sdk is under active development. For maximum stability, you can interact with contracts directly via ABIs from @kleros/kleros-v2-contracts.
  • Testnet first: Always deploy and test a full dispute lifecycle on Arbitrum Sepolia before mainnet. Testnet disputes may take longer to resolve due to limited juror participation.
Contact the Kleros team via Discord or integrations@kleros.io before your first production deployment. The team can review your dispute template and advise on court selection.

Final Sign-Off

ReviewCompleted ByDate
Code review
Security audit
Testnet deployment
Testnet dispute test
Gas benchmarks
Documentation

Quick Reference

Mainnet Contract Addresses (Arbitrum One)

ContractAddress
KlerosCore (proxy)0x33d0b8879368acD8ca868e656Ade97bBcfeB12BA
SortitionModuleVerify from deployments
DisputeKitClassicVerify from deployments
DisputeTemplateRegistryVerify from deployments
HomeGatewayVerify from deployments
// Querying court parameters on-chain
IKlerosCore core = IKlerosCore(0x33d0b8879368acD8ca868e656Ade97bBcfeB12BA);

// Get arbitration cost for General Court (ID 1) with 3 jurors
bytes memory extraData = abi.encodePacked(uint96(1), uint256(3));
uint256 cost = core.arbitrationCost(extraData);

// Get court parameters
(
    uint96 parent,
    bool hiddenVotes,
    uint256 minStake,
    uint256 alpha,
    uint256 feeForJuror,
    uint256 jurorsForCourtJump,
    uint256[4] memory timesPerPeriod,
    ,
    ,
) = core.courts(1); // courtID = 1
Always verify addresses from the official repository before mainnet deployment. During beta, addresses may be updated through proxy upgrades.

Common Last-Minute Issues

Double-check you’re not using testnet addresses on mainnet or vice versa. KlerosCore on Arbitrum Sepolia has a different address than on Arbitrum One.
IPFS content disappears if not pinned. Use Pinata, Infura, or Arweave for permanence. Kleros hosts a pinning service at cdn.kleros.link but you should pin independently as well.
Never hardcode arbitration fees. Always fetch dynamically. Fees change through governance and vary per court.
If user overpays arbitration fee, refund the excess.
V2 uses abi.encodePacked(uint96(courtID), uint256(jurorCount)) note the uint96 for court ID, not uint256 as in V1. Using uint256 will encode a different court than intended.
Always use an odd number of jurors (3, 5, 7) to avoid tied votes. Tied votes in DisputeKitClassic default to ruling 0 (refuse to arbitrate), which may not be what your users expect.
If using templateId, make sure setDisputeTemplate() was called on the DisputeTemplateRegistry and you stored the returned templateId. If the ID is wrong, the Court UI won’t display your dispute correctly and jurors will see missing context.