developers · build a consumer · live on Somnia Shannon

Inherit the base. Implement one function.

VerdiktCourt is a reusable on-chain arbitration primitive. Inherit VerdiktConsumerBase, implement _settle, and your contract or agent can open a dispute, get a byte-identical AI verdict, and settle it programmatically — no admin, no human in the loop.

<50 lines for a complete consumer contract
1 function to implement: _settle(ref, verdict)
0 EOAs required in the settlement path
setup · foundry

Install once. Import two names.

The Solidity side is a Foundry library: VerdiktConsumerBase plus the IVerdiktCourt interfaces. Install the repo, import the base and the Verdict enum, and you are wired to the court.

install bash
$ forge install Ridwannurudeen/verdikt
imports solidity ^0.8.20
import {VerdiktConsumerBase} from "verdikt/VerdiktConsumerBase.sol";
import {Verdict} from "verdikt/interfaces/IVerdiktCourt.sol";
quickstart · the reference consumer

SimpleEscrow, line by line.

The complete reference integration — a working two-party escrow on the AI jury, shown verbatim from the repo and validated end-to-end in test/SimpleEscrow.t.sol. Graded SPLIT and UNDECIDABLE verdicts are handled for free by the base.

01

Inherit the base

VerdiktConsumerBase(court_) wires your contract to the shared court: the onlyCourt callback guard, the caseId↔ref mapping, and fee handling all come with it.

02

Hold the funds

createDeal(payee) escrows msg.value against a deal id — plain contract state, nothing court-specific yet.

03

Open the dispute

Either party calls dispute(id, evidence). The base's _openDispute quotes the court fee, opens the case, maps caseId↔id, and credits any overpayment as a pull-refund.

04

Map the verdict to money

The court finalizes and calls back into _settle(ref, verdict) — the one function you implement. _payeeShareBps turns the label into a basis-point share: PAYEE 10000, SPLIT graded, PAYER and UNDECIDABLE 0.

05

Settle by pull, not push

Funds are credited to a pending[] ledger and each party calls withdraw() — so a reverting recipient can never brick finalize().

src/examples/SimpleEscrow.sol solidity ^0.8.20
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {VerdiktConsumerBase} from "../VerdiktConsumerBase.sol";
import {Verdict} from "../interfaces/IVerdiktCourt.sol";

contract SimpleEscrow is VerdiktConsumerBase {
    struct Deal {
        address payer;
        address payee;
        uint256 amount;
        bool settled;
    }

    mapping(uint256 => Deal) public deals;
    mapping(address => uint256) public pending;
    uint256 public nextId = 1;

    constructor(address court_) VerdiktConsumerBase(court_) {}

    function createDeal(address payee) external payable returns (uint256 id) {
        require(msg.value > 0 && payee != address(0) && payee != msg.sender, "bad deal");
        id = nextId++;
        deals[id] = Deal(msg.sender, payee, msg.value, false);
    }

    function dispute(uint256 id, string calldata evidence) external payable {
        Deal storage d = deals[id];
        require(msg.sender == d.payer || msg.sender == d.payee, "not a party");
        require(!d.settled, "settled");
        _openDispute(id, evidence, msg.sender);
    }

    function _settle(uint256 ref, Verdict verdict) internal override {
        Deal storage d = deals[ref];
        require(!d.settled, "settled");
        d.settled = true;
        uint256 toPayee = (d.amount * _payeeShareBps(ref, verdict)) / 10000;
        pending[d.payee] += toPayee;
        pending[d.payer] += d.amount - toPayee;
    }

    function withdraw() external {
        uint256 amt = pending[msg.sender];
        require(amt > 0, "nothing to withdraw");
        pending[msg.sender] = 0;
        (bool ok,) = msg.sender.call{value: amt}("");
        require(ok, "withdraw failed");
    }

    receive() external payable {}
}
the base · src/VerdiktConsumerBase.sol

What the base does for you.

VerdiktConsumerBase handles the boilerplate every consumer otherwise repeats. The court calls back through onVerdict, which the base guards with onlyCourt and routes to your _settle.

_openDispute(ref, evidence, refundTo) Quotes court.quoteOpen(), requires the fee in msg.value, opens the case, records the caseId↔ref mapping, and credits any excess as a pull-refund.
_openDispute(ref, evidence, refundTo, trialPanel) Same, but selects the trial panel size — in [court.MIN_TRIAL_PANEL, 5] — so the dispute degrades to the validator set currently available instead of reverting.
_settle(ref, verdict) Abstract — the one function you implement. Invoked via onVerdict, guarded by onlyCourt and nonReentrant.
_payeeShareBps(ref, verdict) Resolves any verdict to the payee's share in basis points: PAYEE = 10000, SPLIT = the court's graded splitBps, PAYER / UNDECIDABLE / NONE = 0.
withdrawRefund() Pull-withdraw for any fee overpayment credited at dispute-opening time.
caseToRef / refToCase Public mappings between the court's caseId and your own opaque reference — a dealId, a policyId, a hash.
opening a case · the lifecycle

From open to final — four moves.

Every signature below is the real one from src/interfaces/IVerdiktCourt.sol. Nothing is invented.

1 · open

quoteOpen() returns the exact fee; pass it as msg.value to openCase. The court convenes the trial panel (5 agents) and returns a caseId. Quotes change with panel pricing — read quoteOpen() at call time, never hardcode the fee.

2 · ruling

The panel callback sets status to Ruled and fills verdict + rulingTime. Read it with getCase(caseId). If the verdict is SPLIT, read splitBps(caseId) — a graded court may return 2500 / 5000 / 7500 rather than assuming 50/50.

3 · appeal

While status == Ruled and before appealDeadlineOf(caseId), the case's original consumer can call appeal{value: quoteAppeal(caseId)}(caseId, newEvidence). A larger panel (9 agents) re-tries with the appended evidence.

4 · finalize

Once the appeal window has passed, anyone — a keeper, the counterparty, an agent — calls finalize(caseId). The court flips status to Final and invokes your onVerdict(escrowRef, verdict). Permissionless, so no human is needed to settle. If the panel errors, the consumer can retry(caseId) with a fresh deposit.

js / ts · agent sdk

Read cases, watch verdicts, run a keeper.

The SDK is a thin viem client. createVerdiktClient reads cases and subscribes to verdicts; createVerdiktAgent gives a wallet-backed agent the court actions — appeals, finalize, and a decentralized keeper sweep. Both ship in sdk/.

sdk/index.mjs · read & watch viem
import { createVerdiktClient, payeeShareBps, VERDICT }
  from "verdikt/sdk/index.mjs";

const verdikt = createVerdiktClient({ publicClient, court: "0x..." });

const fee = await verdikt.quoteOpen();
const c   = await verdikt.getCase(1);    // { verdictLabel, statusLabel, ... }
const bps = await payeeShareBps(verdikt, 1); // graded-SPLIT aware share

const unwatch = verdikt.watchVerdicts((v) =>
  console.log(v.caseId, v.verdict, v.receiptId)
);
sdk/agent.mjs · autonomous agent viem
import { createVerdiktAgent } from "verdikt/sdk/agent.mjs";

const agent = createVerdiktAgent({
  publicClient, walletClient, account, court: "0x..."
});

// finalize every case whose appeal deadline has passed
await agent.runKeeper();

// contest a ruling (auto-quotes the larger panel)
await agent.appeal(caseId, "new evidence");

// settle a case AND claim the keeper bounty
await agent.finalizeForBounty(bounty, caseId);

The read client exposes COURT_ABI, quoteOpen, quoteAppeal, getCase, splitBps, payeeShareBps and watchVerdicts. The agent adds appeal, finalize, runKeeper and finalizeForBountyfinalize is permissionless, so anyone can run a keeper and earn the bounty.

live deployment · somnia shannon

Point your consumer at the live court.

The full stack is deployed on testnet. Pass the VerdiktCourt address to your constructor — or to createVerdiktClient({ court }) — and verify each address on the explorer.

chain id 50312 · rpc https://api.infra.testnet.somnia.network/

verdikt · developers

Build the seventh consumer.

Inherit the base, implement _settle, point at the live court — six protocols already settle here.