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.

Core Data Structures

Item Struct

struct Item {
    Status status;                         // Current state
    uint256 sumDeposit;                    // Total locked deposits
    uint256 requestCount;                  // Number of requests
    mapping(uint256 => Request) requests;  // Request history
}

Request Struct

struct Request {
    RequestType requestType;         // Registration or Clearing
    uint64 submissionTime;           // When submitted
    uint24 arbitrationParamsIndex;   // Preserves original arbitrator settings
    address payable requester;       // Who submitted
    address payable challenger;      // Who challenged (if any)
}

Arbitration Parameters

struct ArbitrationParams {
    IArbitratorV2 arbitrator;        // Kleros Court contract
    bytes arbitratorExtraData;       // Court ID + juror count
    EvidenceModule evidenceModule;   // Evidence storage contract
}

Enumerations

enum Status {
    Absent,                // Item does not exist
    Registered,            // Item is active in registry
    RegistrationRequested, // Pending addition
    ClearingRequested      // Pending removal
}

enum RequestType {
    Registration,  // Adding item
    Clearing       // Removing item
}

Initialization

Initialize Function

function initialize(
    address _governor,                       // Admin address
    IArbitratorV2 _arbitrator,              // Kleros Core
    bytes calldata _arbitratorExtraData,    // Court config
    EvidenceModule _evidenceModule,          // Evidence storage
    address _connectedList,                  // Optional parent list
    TemplateRegistryParams calldata _templateRegistryParams,
    uint256[4] calldata _baseDeposits,       // Deposit amounts
    uint256 _challengePeriodDuration,        // Challenge window
    address _relayerContract,                // Direct operations
    string calldata _listMetadata            // JSON string
) external

Base Deposits Array

IndexPurpose
0Submission base deposit
1Removal base deposit
2Submission challenge deposit
3Removal challenge deposit

Example Initialization

// Court ID must be uint96, juror count uint256 — matches IArbitratorV2 spec
const extraData = ethers.solidityPacked(
  ["uint96", "uint256"],
  [1, 3] // Court ID 1 (General Court), 3 jurors
);

await curate.initialize(
  governorAddress,
  klerosCore.target,
  extraData,
  evidenceModule.target,
  ethers.ZeroAddress,           // No connected list
  {
    templateRegistry: templateRegistry.target,
    registrationTemplateParameters: [template, mappings],
    removalTemplateParameters: [template, mappings]
  },
  [
    ethers.parseEther("0.001"), // submission
    ethers.parseEther("0.001"), // removal
    ethers.parseEther("0.001"), // submission challenge
    ethers.parseEther("0.001")  // removal challenge
  ],
  3600,                         // 1 hour challenge period
  relayerAddress,
  JSON.stringify(metadata)
);
Challenge Period Guidelines:
  • Testnet: 3600 seconds (1 hour)
  • Mainnet: 259200-604800 seconds (3-7 days)

Item Data Format

Items in Curate V2 are stored as a JSON string. The registry’s listMetadata defines the schema via a columns array, and each item matches those columns:
{
  "columns": [
    { "label": "Name",        "description": "Token name",            "type": "text",    "isIdentifier": true },
    { "label": "Address",     "description": "Contract address",      "type": "address", "isIdentifier": true },
    { "label": "Symbol",      "description": "Token ticker symbol",   "type": "text" },
    { "label": "Decimals",    "description": "Number of decimals",    "type": "number" },
    { "label": "Logo",        "description": "IPFS URI of token logo","type": "image" }
  ]
}
A submitted item must be a JSON object with one key per column label:
{
  "Name": "Dai Stablecoin",
  "Address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
  "Symbol": "DAI",
  "Decimals": 18,
  "Logo": "/ipfs/QmDAI..."
}
The itemID is keccak256(abi.encodePacked(itemJSON)) — the hash of the exact JSON string submitted. The data mappings system uses this column structure to extract {{itemName}} and {{itemDescription}} template variables.

arbitrationParamsIndex — Versioning

Each Request stores a uint24 arbitrationParamsIndex that snapshots the registry’s arbitration parameters at submission time. This is critical: if the governor updates the arbitrator or extraData after a request is submitted, the original parameters are preserved for that request’s challenge and dispute lifecycle. This means:
  • Challengers must use the same arbitrationParamsIndex as the original submission — fetch it from the request before challenging
  • Cost calculations must use the arbitration params at request.arbitrationParamsIndex, not the current params
// Get the arbitration params for a specific request
(uint256 reqCount,) = curate.getItemInfo(itemId);
(,, uint24 paramsIndex,,) = curate.getRequestInfo(itemId, reqCount - 1);
(IArbitratorV2 arb, bytes memory extraData,) = curate.arbitrationParamsAt(paramsIndex);
uint256 cost = arb.arbitrationCost(extraData);

Registration vs Removal — Separate Dispute Templates

Curate V2 uses two separate dispute templates: one for registration requests and one for removal (clearing) requests. They have different answer logic:
TemplateQuestionAnswer 0x01Answer 0x02
Registration”Does item comply with criteria?”Yes, Add ItemNo, Reject Item
Removal”Should item be removed?”Yes, Remove ItemNo, Keep Item
Both are set at initialization via TemplateRegistryParams.registrationTemplateParameters and removalTemplateParameters. You must provide both, and they must be consistent with your policy document.

Item Submission

Add Item

function addItem(string calldata _item) external payable returns (bytes32 itemID)
Parameters:
  • _item: JSON string matching the registry’s column schema (see Item Data Format above)
Returns:
  • itemID: keccak256 hash of the item string

Calculate Submission Cost

// Get required deposit
const deposit = await curate.submissionBaseDeposit();

// Get arbitration cost
const arbitrator = await curate.getArbitrator();
const extraData = await curate.getArbitratorExtraData();
const arbCost = await arbitrator.arbitrationCost(extraData);

// Total required
const totalCost = deposit + arbCost;

Submission Example

const itemData = {
  address: "0x1234...",
  name: "Token Name",
  symbol: "TKN"
};
const itemString = JSON.stringify(itemData);

// Calculate item ID
const itemID = ethers.keccak256(ethers.toUtf8Bytes(itemString));

// Submit item
const tx = await curate.addItem(itemString, { value: totalCost });
await tx.wait();

console.log("Item ID:", itemID);

Item Removal

Remove Item

function removeItem(bytes32 _itemID, string calldata _evidence) external payable
Parameters:
  • _itemID: Hash of the item to remove
  • _evidence: JSON evidence string supporting removal

Removal Example

const evidence = JSON.stringify({
  name: "Removal Request",
  description: "This item violates the policy because...",
});

const deposit = await curate.removalBaseDeposit();
const arbCost = await arbitrator.arbitrationCost(extraData);
const totalCost = deposit + arbCost;

const tx = await curate.removeItem(itemID, evidence, { value: totalCost });
await tx.wait();

Challenging Requests

Challenge Request

function challengeRequest(bytes32 _itemID, string calldata _evidence) external payable
Parameters:
  • _itemID: Item with pending request
  • _evidence: JSON evidence supporting challenge

Challenge Example

// Determine challenge deposit based on request type
const [status] = await curate.getItemInfo(itemID);
const challengeDeposit = status === 2 // RegistrationRequested
  ? await curate.submissionChallengeBaseDeposit()
  : await curate.removalChallengeBaseDeposit();

const arbCost = await arbitrator.arbitrationCost(extraData);
const totalCost = challengeDeposit + arbCost;

// Prepare evidence
const evidence = JSON.stringify({
  name: "Invalid Submission",
  description: "This item violates the list policy because...",
  supportingInfo: "Reference: [link or details]"
});

// Submit challenge
const tx = await curate.challengeRequest(itemID, evidence, {
  value: totalCost
});
await tx.wait();
Challenges must be submitted during the challenge period. Check timing before challenging.

Executing Requests

Execute Request

function executeRequest(bytes32 _itemID) external
Executes an unchallenged request after the challenge period has passed.

Execution Example

// Check if challenge period has passed
const [status, requestCount] = await curate.getItemInfo(itemID);
const lastRequestIndex = requestCount - 1;

const requestInfo = await curate.getRequestInfo(itemID, lastRequestIndex);
const challengePeriod = await curate.challengePeriodDuration();
const now = Math.floor(Date.now() / 1000);

if (now > requestInfo.submissionTime + challengePeriod) {
  const tx = await curate.executeRequest(itemID);
  await tx.wait();
  console.log("Request executed - deposit refunded");
}

Query Functions

Get Item Info

function getItemInfo(bytes32 _itemID) external view returns (
    Status status,
    uint256 requestCount
)

Get Request Info

function getRequestInfo(bytes32 _itemID, uint256 _requestID) external view returns (
    RequestType requestType,
    uint64 submissionTime,
    bool disputed,
    bool resolved,
    address requester,
    address challenger
)

View Contract Functions

The CurateView contract provides enhanced batch queries:
// Get complete registry configuration
function fetchArbitrable(address _curate) external view returns (
    address governor,
    address relayerContract,
    uint256 submissionBaseDeposit,
    uint256 removalBaseDeposit,
    uint256 submissionChallengeBaseDeposit,
    uint256 removalChallengeBaseDeposit,
    uint256 challengePeriodDuration,
    uint256 arbitrationCost
)

// Get item with latest request
function getItem(address _curate, bytes32 _itemID) external view returns (
    Status status,
    bool disputed,
    uint256 sumDeposit,
    address requester
)

// Get all requests for an item
function getItemRequests(address _curate, bytes32 _itemID) external view returns (
    Request[] memory
)

View Contract Example

const view = await ethers.getContractAt("CurateView", VIEW_ADDRESS);

// Get complete registry configuration
const config = await view.fetchArbitrable(curateAddress);
console.log("Challenge period:", config.challengePeriodDuration.toString());
console.log("Submission deposit:", ethers.formatEther(config.submissionBaseDeposit));

// Get item details
const item = await view.getItem(curateAddress, itemID);
console.log("Status:", ["Absent", "Registered", "RegRequested", "ClearRequested"][item.status]);
console.log("Disputed:", item.disputed);

// Get all requests
const requests = await view.getItemRequests(curateAddress, itemID);
requests.forEach((req, i) => {
  console.log(`Request ${i}:`, {
    disputed: req.disputed,
    resolved: req.resolved,
    requester: req.requester
  });
});

Governor Functions

Configuration Updates

// Change arbitrator settings
function changeArbitrator(
    IArbitratorV2 _arbitrator,
    bytes calldata _arbitratorExtraData
) external onlyGovernor

// Update base deposits
function changeBaseDeposits(uint256[4] calldata _baseDeposits) external onlyGovernor

// Modify challenge period
function changeChallengePeriodDuration(uint256 _challengePeriodDuration) external onlyGovernor

// Set connected list (for hierarchical registries)
function changeConnectedList(address _connectedList) external onlyGovernor

// Update list metadata
function changeListMetadata(string calldata _listMetadata) external onlyGovernor

// Change relayer contract
function changeRelayerContract(address _relayerContract) external onlyGovernor

Direct Item Management

// Add item bypassing challenge period (governor/relayer only)
function addItemDirectly(string calldata _item) external onlyGovernorOrRelayer

// Remove item bypassing challenge period (governor/relayer only)  
function removeItemDirectly(bytes32 _itemID) external onlyGovernorOrRelayer

Events

Item Lifecycle Events

// New item submitted
event NewItem(
    bytes32 indexed itemID,
    string data,
    bool addedDirectly
);

// Item status changed
event ItemStatusChange(
    bytes32 indexed itemID,
    bool updatedDirectly
);

// Request submitted
event RequestSubmitted(
    bytes32 indexed itemID,
    uint256 requestID
);

Dispute Events

// Dispute created in Kleros Court
event DisputeRequest(
    IArbitratorV2 indexed arbitrator,
    uint256 indexed disputeID,
    uint256 externalDisputeID,
    uint256 templateId,
    string templateUri
);

// Ruling received from arbitrator
event Ruling(
    IArbitratorV2 indexed arbitrator,
    uint256 indexed disputeID,
    uint256 ruling
);

Configuration Events

// Connected list updated
event ConnectedListSet(address indexed connectedList);

// Metadata updated
event ListMetadataSet(string listMetadata);

Access Control

Governor Modifier

modifier onlyGovernor() {
    require(msg.sender == governor, "Must be governor");
    _;
}

Relayer Modifier

modifier onlyRelayer() {
    require(msg.sender == relayerContract, "Must be relayer");
    _;
}

Combined Access

modifier onlyGovernorOrRelayer() {
    require(
        msg.sender == governor || msg.sender == relayerContract,
        "Must be governor or relayer"
    );
    _;
}

Security Considerations

  • Uses .send() with 2300 gas limit for refunds
  • State updates occur before external calls
  • No direct .call() usage for ETH transfers
  • Solidity 0.8.24 has built-in overflow protection
  • uint24 arbitrationParamsIndex allows 16,777,215 changes
  • Challenge periods use block.timestamp
  • Can be manipulated ±15 seconds by miners
  • Use sufficiently long periods (hours/days minimum)

Best Practices

// Always validate item doesn't exist before submission
const [status] = await curate.getItemInfo(itemID);
if (status !== 0) {
  throw new Error("Item already exists");
}

// Check challenge period before challenging
const [, requestCount] = await curate.getItemInfo(itemID);
const request = await curate.getRequestInfo(itemID, requestCount - 1);
const challengePeriod = await curate.challengePeriodDuration();
const now = Math.floor(Date.now() / 1000);

if (now > request.submissionTime + challengePeriod) {
  throw new Error("Challenge period expired");
}

// Handle transaction errors
try {
  const tx = await curate.addItem(itemData, { value: totalCost });
  await tx.wait();
} catch (error) {
  if (error.code === 'INSUFFICIENT_FUNDS') {
    console.error("Not enough ETH");
  } else if (error.message.includes("Item must be absent")) {
    console.error("Item already exists");
  }
  throw error;
}