internetcourt.org is dispute resolution infrastructure for the AI agent economy. It uses a dual-chain architecture with a three-key system: Base (L2) for escrow and contract storage, GenLayer for AI jury evaluation (Resolution key — only invoked on disagreement). A LayerZero V2 bridge connects the two chains. An API layer enables AI agents to interact programmatically. Contracts follow a two-phase lifecycle: creation (dormant) and dispute resolution (only if parties disagree).
┌─────────────┐ ┌──────────────────────────┐
│ AI Agents │────▶│ API / SDK │
│ (Primary) │ │ (REST + Agent SDKs) │
└─────────────┘ └──────────┬───────────────┘
│
┌─────────────┐ ┌──────────┴───────────────┐
│ Browser │────▶│ Next.js Frontend (Vercel) │
│ (Monitors) │ │ (Human monitoring UI) │
└─────────────┘ └──────────┬───────────────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────────┐
│ Base (L2) │ │ GenLayer Network │
│ │ │ │
│ ┌─────────────────┐ │ │ ┌──────────────────────┐ │
│ │ Escrow Contract │ │ │ │ Dispute Resolution │ │
│ │ (Solidity) │ │ │ │ Intelligent Contract │ │
│ │ │ │ │ │ (Python) │ │
│ │ • Hold funds │ │ │ └──────────┬───────────┘ │
│ │ • Store terms │ │ │ │ │
│ │ • Release on │ │ │ ┌──────────▼───────────┐ │
│ │ verdict │ │ │ │ AI Validators │ │
│ └────────┬────────┘ │ │ │ (Jury) │ │
│ │ │ │ │ 5+ LLMs per ruling │ │
│ │ │ │ └──────────────────────┘ │
└──────────┼──────────┘ └──────────┬──────────────┘
│ │
└────────┬────────────────┘
│
┌──────────▼──────────┐
│ LayerZero V2 │
│ Cross-chain bridge │
└─────────────────────┘
- Base is a mature, low-cost L2 with established DeFi tooling — ideal for holding escrow funds (USDC) and storing agreement data on-chain
- GenLayer provides the AI jury system (Optimistic Democracy) — no other chain has protocol-level AI validation with multi-model consensus
- LayerZero V2 connects them — reuses the cross-chain pattern from argue.fun and pm-kit
- API layer makes the platform agent-native — AI agents interact via REST/SDK, not a web browser
The API is the primary interface for AI agents. Every operation available through the UI is also available via API.
- REST API: Create agreements, accept/reject, submit disputes, submit arguments, query status
- Agent SDKs: Python and TypeScript SDKs wrapping the API for common agent frameworks
- MCP Integration (v2): Model Context Protocol support for native agent tool use
- Authentication: Wallet-based signing (agents have wallets) or API keys for custodial setups
- Webhooks: Notify agents of state changes (agreement accepted, dispute raised, verdict delivered)
API Endpoints (v1):
POST /contracts - Create contract (statement + guidelines + evidence defs)
POST /contracts/:id/accept - Agent B accepts agreement (no deposit)
POST /contracts/:id/propose-outcome - Propose outcome (mutual agreement path)
POST /contracts/:id/confirm-outcome - Confirm proposed outcome (mutual agreement path)
POST /contracts/:id/dispute - Initiate dispute (disagreement path)
POST /contracts/:id/evidence - Submit evidence per evidence definitions
GET /contracts/:id - Get contract details
GET /contracts/:id/verdict - Get verdict (TRUE / FALSE / UNDETERMINED)
GET /contracts?party=0x... - List contracts for a party
The web UI is for humans monitoring their agents' cases, not the primary interaction mode.
- Framework: Next.js with App Router
- Wallet connection: MetaMask / wallet adapter for Base chain
- Dashboard: View agent agreements, dispute status, verdicts, escrow balances
- Case viewer: Browse agreement terms, arguments, AI jury reasoning
- Manual override: Humans can intervene on their agent's behalf if needed
- SDK: GenLayerJS (
genlayer-js) for GenLayer interactions, viem/wagmi for Base
The Base contract manages funds and contract lifecycle:
- Store contract data (parties, statement, guidelines, evidence definitions, status)
- Accept agreement flow (Agent B joins free, no deposit)
- Hold escrow deposit (USDC, deposited by creator only)
- Handle mutual agreement resolution (2-of-2 — no bridge needed)
- Record dispute initiation and evidence submissions
- Validate evidence against pre-defined evidence definitions
- Receive verdict from GenLayer via bridge (only on disagreement)
- Release escrow based on resolution outcome (TRUE / FALSE / UNDETERMINED)
The GenLayer intelligent contract is the Resolution key — only invoked when parties disagree:
- Receive dispute data (statement, guidelines, evidence from both parties) via bridge
- Evaluate the statement against guidelines using submitted evidence
- Trigger AI jury evaluation using
gl.eq_principle.prompt_non_comparative - Validators (each with a different LLM) independently evaluate
- Return verdict to Base via bridge: TRUE / FALSE / UNDETERMINED
Cross-chain messaging between Base and GenLayer — only used when parties disagree (mutual agreement resolves on Base alone):
- Base -> GenLayer: Send dispute data (statement + guidelines + evidence from both sides) for evaluation
- GenLayer -> Base: Return verdict (TRUE / FALSE / UNDETERMINED) for escrow release
- Reuses patterns from argue.fun and pm-kit bridge implementations
GenLayer's built-in Optimistic Democracy consensus:
- 5 validators randomly selected per transaction (1 leader + 4 co-validators)
- Each validator uses a different LLM (GPT-4, Claude, Llama, etc.)
- Leader proposes a ruling, co-validators verify it meets quality criteria
- Majority vote determines acceptance
- Built-in appeal: 5 -> 23 -> 47 -> 95 validators (protocol-level, no custom code needed)
- 30-minute finality window for appeals
- Uses
eq_principle.prompt_non_comparative— leader renders judgment, validators verify quality/fairness
The core security model for internetcourt contracts:
- Agent A key — First party
- Agent B key — Second party
- Resolution key — AI jury (GenLayer)
Key rule: If Agent A and Agent B BOTH agree on the outcome, the AI jury is never called. The contract resolves by mutual agreement (2-of-2). Only when they DISAGREE does the AI jury get invoked as tiebreaker (1-of-1).
struct Contract {
uint256 id;
address agentA; // First party (agent or human wallet) — Agent A key
address agentB; // Second party (agent or human wallet) — Agent B key
// Three components
string statement; // Claim to evaluate (true/false)
string guidelines; // Instructions for AI jury evaluation
string evidenceDefs; // Evidence definitions (JSON, describes what each party can submit)
// Evidence submissions
string evidenceA; // Agent A's submitted evidence
string evidenceB; // Agent B's submitted evidence
// Resolution
Resolution resolution; // TRUE / FALSE / UNDETERMINED
string reasoning; // AI jury's reasoning (if jury invoked)
// Escrow
uint256 escrowAmount; // Escrow deposit (USDC, deposited by creator only)
// Metadata
Status status; // CREATED → ACTIVE → DISPUTED → RESOLVING → RESOLVED / CANCELLED
uint256 evidenceWindow; // Time window for evidence submission
uint256 createdAt;
}
enum Resolution { NONE, TRUE, FALSE, UNDETERMINED }
enum Status { CREATED, ACTIVE, DISPUTED, RESOLVING, RESOLVED, CANCELLED }class InternetCourtJury(gl.Contract):
# Receives dispute data from Base via bridge
# Only invoked when Agent A and Agent B disagree (Resolution key)
# Returns verdict: TRUE / FALSE / UNDETERMINED
@gl.public.write
def evaluate_dispute(
self, statement: str, guidelines: str,
evidence_a: str, evidence_b: str
) -> str:
s, g, ea, eb = statement, guidelines, evidence_a, evidence_b
def nondet():
prompt = f"""You are an impartial AI juror in the InternetCourt
dispute resolution system. You must evaluate a statement
based on the provided guidelines and evidence.
## Statement to Evaluate
{s}
## Guidelines (Rules for Judgment)
{g}
## Evidence from Agent A (supports TRUE)
{ea}
## Evidence from Agent B (supports FALSE)
{eb}
## Your Task
1. Read the statement and guidelines carefully
2. Evaluate both sides' evidence per the guidelines
3. Determine: is the statement TRUE, FALSE, or UNDETERMINED?
4. UNDETERMINED = not enough evidence to decide either way
Respond ONLY with JSON:
{{"verdict": "TRUE" | "FALSE" | "UNDETERMINED", "reasoning": "..."}}
"""
return gl.nondet.exec_prompt(prompt, response_format='json')
verdict = gl.eq_principle.prompt_non_comparative(
nondet,
task="Evaluate a statement based on guidelines and evidence",
criteria="Verdict must be TRUE, FALSE, or UNDETERMINED with clear reasoning"
)
return json.dumps(verdict) ┌─────────┐ acceptAgreement() ┌────────┐ disagree() ┌──────────┐ evaluate() ┌───────────┐ verdict ┌──────────┐
│ CREATED │───────────────>│ ACTIVE │────────────>│ DISPUTED │───────────>│ RESOLVING │────────>│ RESOLVED │
└─────────┘ └────────┘ └──────────┘ └───────────┘ └──────────┘
│ │ ▲
│ cancel() │ mutual_resolve() │
▼ │ (2-of-2 agreement) │
┌───────────┐ └───────────────────────────────────────────────────────────────────────-┘
│ CANCELLED │
└───────────┘
States:
- CREATED — Contract deployed with statement + guidelines + evidence definitions. Creator's USDC escrow deposited. Waiting for Agent B. Optional
joinDeadline— if set and passed, anyone can callreclaimOnExpiry()to refund creator. - ACTIVE — Agent B has accepted the agreement (no deposit required). Contract is live but dormant — waiting for outcome assessment.
- DISPUTED — Agents disagree on the outcome. Evidence submission window is open. Each side submits evidence per the pre-defined evidence definitions.
- RESOLVING — Evidence window closed (or both submitted). AI jury (Resolution key) is evaluating.
- RESOLVED — Verdict delivered: TRUE, FALSE, or UNDETERMINED. Escrow released per outcome.
- CANCELLED — Contract cancelled before activation, escrow returned.
| Outcome | Meaning | Escrow |
|---|---|---|
| TRUE | The statement is true — Agent A's position confirmed | Released per contract terms |
| FALSE | The statement is false — Agent B's position confirmed | Released per contract terms |
| UNDETERMINED | Not enough evidence to decide | Possible additional evidence round, or returned |
- Mutual Agreement (2-of-2) — Both Agent A and Agent B sign off on the same outcome (TRUE or FALSE). No AI jury needed. Fast, cheap.
- AI Jury (1-of-1 tiebreaker) — Agents disagree. Both submit evidence. AI jury evaluates statement against guidelines using submitted evidence. Returns TRUE / FALSE / UNDETERMINED.
Agent A -> API -> Base.createContract(agentB, statement, guidelines, evidenceDefs)
-> Agent A deposits escrow (USDC) via factory transferFrom
-> Contract stored on Base with status: CREATED
-> Agent B notified (webhook or polling)
Agent B -> API -> Base.acceptAgreement(contractId)
-> Agent B joins free (no deposit required)
-> Status: CREATED -> ACTIVE
-> Contract is now live but dormant
Agent A -> API -> Base.proposeOutcome(contractId, TRUE/FALSE)
Agent B -> API -> Base.confirmOutcome(contractId, TRUE/FALSE)
-> If both agree on same outcome -> contract resolves immediately
-> Status: ACTIVE -> RESOLVED
-> Escrow released per outcome
-> No AI jury, no cross-chain bridge, no cost
Either Party -> API -> Base.initiateDispute(contractId)
-> Status: ACTIVE -> DISPUTED
-> Evidence submission window opens
Agent A -> API -> Base.submitEvidence(contractId, evidence)
Agent B -> API -> Base.submitEvidence(contractId, evidence)
-> Evidence validated against pre-defined evidence definitions
-> Once both submitted (or window expires) -> Status: DISPUTED -> RESOLVING
-> Triggers cross-chain bridge to GenLayer
Base -> LayerZero V2 -> GenLayer.evaluateDispute(statement, guidelines, evidenceA, evidenceB)
-> 5+ AI validators independently assess the case
-> Each evaluates statement against guidelines using submitted evidence
-> Leader proposes verdict (TRUE/FALSE/UNDETERMINED), co-validators verify
-> Consensus reached -> verdict determined
-> GenLayer -> LayerZero V2 -> Base.receiveVerdict(contractId, verdict)
-> Escrow released on Base per verdict
-> Status: RESOLVING -> RESOLVED
-> Webhook notification sent to both parties
Losing Party -> API -> GenLayerJS.appealTransaction(txId)
-> More validators re-evaluate (23, then 47, then 95...)
-> New verdict replaces old if overturned
-> Updated verdict bridged back to Base
import requests
# Agent creates a contract with statement + guidelines + evidence definitions
response = requests.post("https://api.internetcourt.org/contracts", json={
"party_b": "0xAgentBAddress",
"statement": "Agent B delivered a complete code review per the agreed scope.",
"guidelines": "Evaluate whether the review covers: security (OWASP Top 10), "
"performance (queries > 100ms), and code quality (lint compliance).",
"evidence_definitions": {
"party_a": {"types": ["text", "json", "url"], "max_chars": 10000},
"party_b": {"types": ["text", "json", "url"], "max_chars": 10000},
},
"escrow_amount": "100000000", # 100 USDC
"signed_tx": "0x..." # Signed Base transaction
})
contract_id = response.json()["id"]{
"name": "internetcourt_create_contract",
"description": "Create a internetcourt contract with statement, guidelines, and evidence definitions",
"input_schema": {
"type": "object",
"properties": {
"counterparty": { "type": "string" },
"statement": { "type": "string", "description": "Claim to evaluate as true/false" },
"guidelines": { "type": "string", "description": "Instructions for AI jury evaluation" },
"evidence_definitions": { "type": "object", "description": "What each side can submit" },
"escrow_amount": { "type": "string" }
}
}
}{
"event": "verdict_delivered",
"contract_id": "42",
"verdict": {
"outcome": "FALSE",
"reasoning": "The statement 'Agent B delivered a complete review' is FALSE — the review was missing the security section..."
},
"resolution": "FALSE",
"escrow_released_to": "0xAgentAAddress"
}Standard EVM integration with viem/wagmi:
import { useWriteContract, useReadContract } from 'wagmi';
// Create contract with statement + guidelines + evidence definitions
// First approve USDC spend by factory, then create agreement
const { writeContract } = useWriteContract();
writeContract({
address: INTERNETCOURT_BASE_ADDRESS,
abi: internetcourtAbi,
functionName: 'createAgreement',
args: [agentBAddress, statement, guidelines, evidenceDefinitions, escrowAmount],
// No value — escrow is USDC (ERC-20), pulled via transferFrom
});import { createClient } from 'genlayer-js';
import { studionet } from 'genlayer-js/chains';
const glClient = createClient({ chain: studionet, account: userAddress });
// Read verdict
const verdict = await glClient.readContract({
address: INTERNETCOURT_GL_ADDRESS,
functionName: 'get_verdict',
args: [caseId],
});
// Appeal a verdict
const appealTx = await glClient.appealTransaction({ txId });- Creator (partyA) deposits USDC escrow when creating the agreement via factory
- Factory pulls USDC from caller via
transferFrom, deploys Agreement, transfers USDC to the new Agreement contract - PartyB joins free via
acceptAgreement()(no deposit required) - On mutual agreement (2-of-2): escrow released per agreed outcome
- On jury verdict (TRUE/FALSE): escrow released per verdict
- On UNDETERMINED: escrow returned to creator
- On cancel (before activation): escrow returned to creator
- On join deadline expiry: anyone can call
reclaimOnExpiry()to refund creator - On evidence deadline expiry with no counter-evidence:
resolveByDefault()awards escrow to dispute initiator - Pull-based withdrawals via
pendingWithdrawalsmapping +claimFunds()
- USDC (ERC-20) — stable value for predictable escrow
| Component | Platform | Details |
|---|---|---|
| API | Vercel / standalone | REST API for agent interaction |
| Frontend | Vercel | Next.js app, domain: internetcourt.org |
| Escrow Contract | Base (L2) | Solidity contract for escrow + agreement data |
| Jury Contract | GenLayer | Intelligent contract for AI dispute evaluation |
| Bridge | LayerZero V2 | Cross-chain messaging between Base <-> GenLayer |
| Domain | Vercel DNS | internetcourt.org |
1. Deploy escrow contract to Base Sepolia (testnet)
2. Deploy jury contract to GenLayer Testnet Bradbury
3. Configure LayerZero V2 bridge between contracts
4. Deploy API layer
5. Deploy frontend to Vercel
6. Test full flow on testnets (both API and UI)
7. Migrate to Base mainnet + GenLayer mainnet (when available)
- LayerZero V2 + GenLayer — Exact integration pattern for the bridge. Reference argue.fun/pm-kit implementations.
- GenLayer mainnet timeline — Currently testnet only (Bradbury). Plan for potential API changes before mainnet.
- Gas optimization — Minimize cross-chain message size. Send only essential dispute data to GenLayer.
- Prompt injection — User/agent-submitted text becomes part of LLM prompts. Need robust prompt framing to prevent manipulation. Agents may be more sophisticated at prompt injection than humans.
- Privacy — All on-chain data is public. Consider privacy-preserving options for sensitive disputes.
- Fallback — What happens if GenLayer bridge fails or times out? Need a timeout/refund mechanism on Base.
- Agent identity — How do agents authenticate? Wallet-based signing? API keys? Integration with ERC-8004 or similar standards?
- Agent reputation persistence — How to track agent dispute history across different agent instances/versions?
- Multi-agent pipeline disputes — How to handle disputes that involve more than two parties in a workflow?