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
Index Purpose 0 Submission base deposit 1 Removal base deposit 2 Submission challenge deposit 3 Removal 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)
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:
Template Question Answer 0x01 Answer 0x02 Registration ”Does item comply with criteria?” Yes, Add Item No, Reject Item Removal ”Should item be removed?” Yes, Remove Item No, 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 ;
}