Skip to content

Proposal preview

Panel

Ask models from different labs one question and get one EIP-712 signed verdict that a contract can verify.

POST/api/proposal/panel

A Panel puts one question to three to nine models from at least three labs, compares their answers and signs the verdict. The body is a chat completion with one extra field, panel, and the reply is a chat completion with the verdict in panel.

Proposal preview

Panel runs on Sepolia and is not part of deference.si yet. Without a model key, members answer in example mode and the response says "mode": "example". The attestation is still signed by the real Panel signer, so contracts can check it.

Request

curl https://deference.dea.web.id/api/proposal/panel \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deference/panel",
    "question": "Did team Blue win the final?",
    "panel": {
      "members": ["anthropic/claude-sonnet-5.5", "openai/gpt-6.1", "google/gemini-4-pro"],
      "quorum": "majority",
      "mode": "vote",
      "answer_type": "bool"
    }
  }'
questionstring
The question. Or send messages; the last user turn is the question.
panel.membersstring[]
Three to nine model ids from at least three families. The family is the part before the slash.
panel.quorum"majority" | number
How many members must agree. A number must be a strict majority of the members. Default "majority".
panel.mode"vote" | "judge"
vote takes the answer most members gave. judge lets one member pick among the answers; agreed counts the members behind its pick.
panel.answer_type"bool" | "uint256" | "string"
How answers are read and compared. Default bool.

Response

{
  "id": "panel_01JB…",
  "object": "chat.completion",
  "model": "deference/panel",
  "mode": "example",
  "choices": [{ "index": 0, "message": { "role": "assistant", "content": "true (3 of 3 agree, quorum 2)" }, "finish_reason": "stop" }],
  "panel": {
    "status": "agreed",
    "answer": "true",
    "answer_type": "bool",
    "size": 3,
    "quorum": 2,
    "agreed": 3,
    "members": [{ "model": "openai/gpt-6.1", "answer": "true", "content": "…", "agrees": true }],
    "attestation": {
      "requestId": "0x…",
      "chainId": "11155111",
      "questionHash": "0x…",
      "answerType": 0,
      "answer": "0x0000…0001",
      "size": 3,
      "quorum": 2,
      "agreed": 3,
      "membersHash": "0x…",
      "issuedAt": "1792130000",
      "expiresAt": "1792133600"
    },
    "signature": "0x…",
    "signer": "0x0bD8424F58a021FE8f1068e7B174CE537b79CeB8",
    "verifying_contract": "0xD55Db52c37B4eE31804F6F30f0f5e1B446615Fc7",
    "chain_id": 11155111
  }
}

status is agreed when agreed reaches quorum, else disagreed. A disagreed verdict is still signed when there is an answer; a contract refuses it with reason 5.

GET /v1/panel/signer gives the signing address, which a contract stores as the signer it trusts.

Attestation

The signature is EIP-712 over this type, in this order:

PanelAttestation(bytes32 requestId,uint256 chainId,bytes32 questionHash,uint8 answerType,bytes answer,
  uint16 size,uint16 quorum,uint16 agreed,bytes32 membersHash,uint64 issuedAt,uint64 expiresAt)
  • Domain: name Deference Panel, version 1, the chain id, and the consumer contract as verifyingContract. One signature is good for one contract on one chain.
  • answerType: 0 bool, 1 address, 2 bytes32, 3 uint256, 4 string. Fixed-width answers are abi.encode(value), exactly 32 bytes; a string is its UTF-8 bytes.
  • questionHash is keccak256 of the question's UTF-8 bytes. membersHash is keccak256 of the member ids joined by commas, in panel order.
  • An attestation is valid for one hour after issuedAt.

Verify in Solidity

import {PanelAttestation} from "./PanelAttestation.sol";

contract Settles {
    using PanelAttestation for PanelAttestation.Attestation;
    address public panelSigner;

    function settle(PanelAttestation.Attestation calldata a, bytes calldata signature) external {
        PanelAttestation.Attestation memory verdict = a;
        verdict.verify(signature, panelSigner);
        bool won = verdict.decodeBool();
        // act on the verdict
    }
}

verify reverts unless the signature recovers to the signer, the quorum is a strict majority, the quorum was met, the attestation has not expired and it names the current chain. check runs the same checks without reverting and gives a reason code: 0 valid, 1 no signer, 2 wrong chain, 3 expired, 4 quorum not a majority, 5 quorum not met, 6 unknown answer type, 7 wrong answer length, 8 malformed signature, 9 wrong signer.

On Sepolia, ContestJudge.checkAttestation(attestation, signature) at 0xD55Db52c37B4eE31804F6F30f0f5e1B446615Fc7 runs check against the Panel signer, so anyone can check an answer with eth_call. The Panel page does this from the browser.