Light Curate: Integration for Devs
This covers Light Curate (V1). For V2 Curate, see the Developers Curate section.
Overview
Light Curate significantly decreases deployment and operation costs compared to Curate Classic by changing the data storage strategy:- Item data is NOT stored in contract storage. Only the IPFS multihash is stored on-chain. Storage cost is O(1) instead of O(n).
- Item fields are stored in the subgraph via The Graph’s IPFS API. No need for
@kleros/gtcr-encoder. - Uses EIP-1167 minimal proxy for deployments. Deployment cost dropped from ~7M gas to ~700K gas.
Reading Data
Fetching Items via Subgraph
Light Curate useslitems entities (prefixed with l). Pass the TCR address to filter:
props field contains decoded item fields directly. No encoder library needed.
Fetching a Specific Item
Item entities have IDs in the format<itemID>@<listAddress>:
View Contract
A view contract is available for fetching all relevant info in a single call. Gnosis Chain deployment:0x08e58Bc26CFB0d346bABD253A1799866F269805a
Writing Data
Submitting an Item
- Upload item data to IPFS (use both The Graph’s IPFS endpoint and Kleros IPFS node)
- Call
addItem()on the LightGeneralizedTCR contract with the IPFS URI and required deposit
Challenging and Executing Requests
- Challenge: call
challengeRequest()with a deposit - Execute: call
executeRequest()after the challenge period passes unchallenged
MetaEvidence Standard
MetaEvidence is the single on-chain source of truth for both the human-readable policy and the machine-readable item schema. Always resolve it live from chain events. Never hardcode, cache, or infer it.Two independent streams
Light Curate (LGTCR) requires two separate MetaEvidence documents:
Both are normally emitted at deployment (
_metaEvidenceID = 0 for registration, = 1 for clearing by convention), but a registry governor can push new MetaEvidence, incrementing the ID. Never assume only IDs 0/1 exist; always take the latest applicable event per stream.
Retrieval
- Query
eth_getLogsagainst the registry address, filtered on theMetaEvidenceevent topic: - Sort matching logs by
blockNumber, thentransactionIndex, thenlogIndex. - Fetch each
_evidenceIPFS pointer via a gateway (for examplehttps://cdn.kleros.link/ipfs/<hash>). Do not double-prefix/ipfs/. - Classify each JSON as registration or clearing by its
title/description/ruling-option content (“Add” vs “Remove”), not by log order. Some governor transactions emit a registration event immediately followed by a clearing event in the same block. - Use the latest event matching the operation you are performing. If classification is ambiguous, stop and ask a human.
Production MetaEvidence shape
title/description/question, and reuses the same metadata.columns. metadata.columns is the authoritative schema of the registry: every submitted item.json must contain an identical deep copy of this array. Use lowercase common nouns for itemName/itemNamePlural (for example "token", "tokens").
Field requirements
When calling the factory
deploy(...), pass MetaEvidence URIs registration first, clearing second. The constructor assigns _metaEvidenceID positionally.
Hard stops (do not deploy): JSON does not parse; metadata.columns missing or empty; any column uses an unsupported type; fileURI missing; production logoURI missing; policy is not a PDF and the risk was not accepted; both streams not validated.
item.json Standard
item.json is uploaded to IPFS and referenced on-chain via addItem(string _item).
columnsmust be a verbatim deep copy of the activeMetaEvidence.metadata.columns(same labels, descriptions, types,isIdentifierflags, same order). Even a grammar fix violates the contract.valuesis the only dynamic part.Object.keys(values)must exactly equalcolumns.map(c => c.label), in the same order. No missing, extra, renamed, or reordered keys.- Never reconstruct the schema from UI text, screenshots, old docs, or memory; always pull
metadata.columnsfrom the currently active MetaEvidence.
isIdentifier, using type: "url", or submitting partial/placeholder values.
Field Type Standard
Only these type strings are valid for Curate V1 (GTCR) columns. Do not use V2-only spellings unless a live MetaEvidence proves that spelling is already in production for the list.
Forbidden aliases:
url → link; string → text; markdown → long text; bool → boolean; integer/int/float/decimal → number; longText/richAddress/chain (V2 spellings) → long text/rich address. Using an unsupported type surfaces as an interface error such as Unhandled input type url.
Identifier rules: at least one identifier column is required; up to five are supported; image, file, and long text must never be identifiers. For multi-chain token registries, prefer rich address over plain address.
Rich Address Standard
rich address is a CAIP-style value that references an address on a specific chain, avoiding the ambiguity of a bare 0x... string:
eip155 (EVM chains), bip122 (Bitcoin-like), solana, tvm (TON), stacks. Common eip155 reference IDs include Ethereum Mainnet 1, Optimism 10, BNB Smart Chain 56, Gnosis 100, Polygon 137, Base 8453, Arbitrum One 42161, Avalanche 43114, Linea 59144, and Sepolia 11155111.
Never infer the target chain of a rich address from surrounding context; always confirm it explicitly. A bare address resolved under the wrong referenceId silently points to a different account on a different chain.
Deposit Computation
Curate V1 deposits are paid entirely in the chain’s native token (ETH on Mainnet/Sepolia, xDAI on Gnosis). Never hardcode a deposit. Deposit parameters are governance-controlled. Compute live from on-chain reads immediately before building a transaction. The frontend shows only the base deposit; sending only that asmsg.value reverts, because the contract requires base deposit plus arbitration cost in a single value.
arbitrationCost is derived from registry.arbitrator() and registry.arbitratorExtraData(), then IArbitrator(arbitrator).arbitrationCost(extraData).
Appeal funding: requiredForSide = appealCost + appealCost * feeStakeMultiplier / MULTIPLIER_DIVISOR, where the multiplier is sharedStakeMultiplier() (no ruling yet), winnerStakeMultiplier() (side matches the current ruling), or loserStakeMultiplier() (side opposes it). The losing side must fund before the midpoint of the appeal window.
Contract Interface (LightGeneralizedTCR)
Write functions:_side in fundAppeal uses the Party enum: 0 = None, 1 = Requester, 2 = Challenger.
Key read functions: submissionBaseDeposit(), removalBaseDeposit(), submissionChallengeBaseDeposit(), removalChallengeBaseDeposit(), challengePeriodDuration(), arbitrator(), arbitratorExtraData(), winnerStakeMultiplier(), loserStakeMultiplier(), sharedStakeMultiplier(), MULTIPLIER_DIVISOR(), getItemInfo(bytes32), getRequestInfo(bytes32, uint256).
Key events:
deploy(address _arbitrator, bytes _arbitratorExtraData, address _connectedTCR, string _registrationMetaEvidence, string _clearingMetaEvidence, address _governor, uint256[4] _baseDeposits, uint256 _challengePeriodDuration, uint256[3] _stakeMultipliers, address _relayContract), emitting NewGTCR(address). Do not hardcode the factory address; verify it per chain.
View helper: LightGeneralizedTCRView.fetchArbitrable(address) returns all deposit parameters in a single call. Prefer it for aggregated reads; use direct contract reads as final truth for critical values.
Registry Verification
Before touching MetaEvidence or deposits for an unfamiliar address:- Confirm the
chainIdmatches intent (Mainnet1, Sepolia11155111, Gnosis100). - Call
eth_getCode(listAddress)on that chain. If the result is0x, it is not a contract there; stop. - Perform a hallmark read (
submissionBaseDeposit()orarbitrator()). If it reverts, this is not a valid LGTCR instance; stop.
Submit an Item (end-to-end)
- Fetch the latest registration MetaEvidence.
- Read the policy at
fileURIbefore building anything. - Build
item.jsonfrommetadata.columns; run a schema-drift audit against a recentNewItemsample if this is your first submission. - Upload
item.jsonto IPFS to obtain/ipfs/<hash>. - Compute the live deposit:
submissionBaseDeposit() + arbitrationCost. - Simulate the transaction with identical calldata and
msg.value. - Call
addItem("/ipfs/<hash>")withmsg.value = deposit.
Deploying a list does not make it visible on the Curate frontend. The public list-of-lists mechanism uses Curate Classic (
GeneralizedTCR), not Light Curate’s addItem(string); submitting a new list for discoverability is a separate, optional workflow.Subgraph Conventions
Addresses must be lowercase in queries. The subgraph does not understand checksummed addresses.
The hosted service has a 1000 item limit per query. For larger registries, paginate using
skip or id_gt.
Subgraph Deployment
If you deployed a list using the factory, it already has a subgraph deployed and available. For custom deployments, see The Graph documentation.Resources
Light Curate Contracts
Source code
Query Examples
More GraphQL query examples