sigil
All patterns
intermediatecookbook

AI-Judged Dispute Resolution

Smart contract escrow where an LLM rules on disputes, in a TEE.

Two parties stake collateral. If they disagree, an on-chain LLM call reads both submissions and rules. The TEE attestation makes the ruling verifiable.

Architecture


   ┌──────────┐    stake     ┌─────────┐
   │ Buyer    │ ───────────▶ │ Escrow  │ ◀── stake ──┐
   └──────────┘              └────┬────┘             │
                                  │ dispute     ┌────┴────┐
                                  ▼             │ Seller  │
                            ┌─────────────┐    └─────────┘
                            │ LLM 0x0802  │
                            │ rules in TEE│
                            └──────┬──────┘
                                   │ release
                                   ▼ to winner

Code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {PrecompileConsumer} from "./utils/PrecompileConsumer.sol";

contract DisputeEscrow is PrecompileConsumer {
    address constant LLM = address(0x802);

    struct Case { address a; address b; string evidence; uint256 stake; bool resolved; }
    mapping(uint256 => Case) public cases;
    uint256 public nextCase;

    function judge(uint256 id, address executor) external {
        Case storage c = cases[id];
        require(!c.resolved, "done");

        string memory messages = string.concat(
            '[{"role":"system","content":"You are a fair judge. Reply with EXACTLY \"A\" or \"B\"."},',
            '{"role":"user","content":"', c.evidence, '"}]'
        );

        // 30-field LLM ABI — minimal valid encoding.
        bytes memory enc = _encodeLLM(executor, messages);
        bytes memory out = _executePrecompile(LLM, enc);

        (bool hasError, bytes memory completion, , , ) =
            abi.decode(out, (bool, bytes, bytes, string, (string, string, string)));
        require(!hasError, "llm error");

        // Naive parse: reply starts with "A" or "B".
        bytes1 verdict = bytes(string(completion))[0];
        address winner = verdict == "A" ? c.a : c.b;
        payable(winner).transfer(c.stake * 2);
        c.resolved = true;
    }

    function _encodeLLM(address executor, string memory messages)
        internal pure returns (bytes memory) {
        // truncated for brevity — see /playground/llm for full ABI
    }
}