Proposal preview
Panel
Ask models from different labs one question and get one EIP-712 signed verdict that a contract can verify.
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"
votetakes the answer most members gave.judgelets one member pick among the answers;agreedcounts 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, version1, the chain id, and the consumer contract asverifyingContract. One signature is good for one contract on one chain. answerType: 0 bool, 1 address, 2 bytes32, 3 uint256, 4 string. Fixed-width answers areabi.encode(value), exactly 32 bytes; a string is its UTF-8 bytes.questionHashis keccak256 of the question's UTF-8 bytes.membersHashis 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.