SWORN is the bonded dispute layer for GenLayer β contested real-world claims settled by validator consensus with skin in the game.
A sworn statement is true until consensus says otherwise.
Live App Β· Demo Video Β· Launch Thread Β· Contract on Bradbury Β· Search / RPC Proxy Β· Chain: Bradbury 4221
Real-world facts that money depends on β did the shipment arrive, did the metric cross the threshold, did the event happen β are today resolved by someone you have to trust. That is the captured-oracle problem, and SWORN exists to kill it. Nothing settles on the asserter's word alone, and nothing settles on an LLM's word alone.
1. Create an assertion β swear a statement.
β
2. Stake a bond β real GEN, custodied by the contract.
β
3. Anyone may challenge β by staking their own bond.
β
4. Only challenged assertions invoke AI consensus β exactly one round.
β
5. Deterministic settlement β loser slashed, integer math, no discretion.
β
6. Consumer contracts read read_truth() β and gate their own value on it.
Unchallenged assertions settle for free after the window β zero LLM calls. Consensus is invoked only when someone has money on a disagreement.
What settles deterministically: the assertion id, the challenge window and all timing, the
bond/slash/fee arithmetic (integer basis points, no floats), the labelβaction mapping, and the
payout ledger. The LLM only ever proposes a label. What read_truth(assertion_id) returns to
any other contract is exactly:
{"found": bool, "status": str, "label": str, "resolved": bool, "true": bool}resolved is True once the assertion has settled (RESOLVED_TRUE / RESOLVED_FALSE /
RESOLVED_UNRESOLVABLE); true is only True for RESOLVED_TRUE. Checking both means you
never treat an unresolved or disputed-but-unsettled claim as confirmed.
The clip runs the full path against the deployed contract on Bradbury testnet: swear β challenge β consensus β settlement, ending on the settled label and the economic outcome the loser paid for.
swear (bond) β optimistic window β bonded challenge β consensus on a label β slash the loser β read_truth()
The status machine is PENDING β CHALLENGED β RESOLVING β RESOLVED_TRUE | RESOLVED_FALSE | RESOLVED_UNRESOLVABLE, plus the free path PENDING β RESOLVED_TRUE via
finalize_unchallenged() once the window elapses untouched. An unchallenged claim costs zero
LLM calls; a challenged one costs exactly one consensus round, paid for by the loser's bond.
The console reads the protocol's live aggregate state: bonds actually custodied by the contract, assertions by status, finalization, and the protocol fees accrued from settled disputes.
On a resolved assertion, the consensus timeline sits beside the economic settlement panel: who won, who lost, how much was slashed, what the protocol kept. That panel is the product. The label is only the input to it.
Swearing shows you the statement exactly as it will be stored and the bond you are putting at
risk before you sign. The floor is _effective_min_bond(who) = min_bond * (1 + grief_strikes[who])
β it rises for anyone who keeps losing.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CONSUMERS consumer_escrow.py, any Intelligent Contract β
β reads read_truth() β never reads evidence β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β VIEWS read_truth Β· dispute_summary Β· get_assertion β
β pure reads of settled state β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β DETERMINISTIC swear Β· challenge Β· finalize_unchallenged Β· β
β PROTOCOL CORE claim_asserter/challenger/protocol_fee β β
β state machine + integer math, β
β no LLM, no network, no consensus β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β CONDITIONAL resolve() β only for CHALLENGED assertions: β
β CONSENSUS one leader/validator round over pinned β
β evidence; LLM only for WEB_EVENT; mechanical β
β eth_call comparator for ONCHAIN_METRIC β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β EVIDENCE proxy/app.py β per-round determinism cache β
β TRANSPORT (search + pinned-block eth_call); availability β
β layer, never assigns truth β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Only one layer is non-deterministic. Everything above the consensus layer is deterministic and runs on every assertion. The consensus layer runs only when a bonded challenge exists β the LLM never touches an uncontested claim, and settlement math after the label is deterministic integer arithmetic.
- Economics β an unchallenged claim costs zero LLM calls; a challenged one costs exactly one consensus round, paid for by the loser's bond.
- Determinism β everything that can be deterministic is: guards, bonds, windows, dedup,
settlement math, views. AI cannot replace these, because they must be bit-identical across
validators and replayable. The LLM is confined to one judgment
(
TRUE | FALSE | UNRESOLVABLEon evidence) inside one agreement band. - Authority β consensus is the sole authority for semantic judgment. No off-chain component β proxy, relayer, console β can assign or alter a label.
The agreement band is identical-label agreement over {TRUE, FALSE, UNRESOLVABLE}. Nothing
else is compared: not the reason text, not a confidence score, not the raw evidence. Failures
finalize too β SWORN raises static, never-interpolated error classes ([EXPECTED],
[EXTERNAL], [LLM_ERROR]) so validators agree on failures as well as successes and a
transient outage never becomes a stuck dispute. ONCHAIN_METRIC runs the same
leader/validator machinery with no LLM at all β a pinned-block eth_call through the proxy
plus an integer comparator β so AI consensus is a strict subset of validator consensus.
A settled ONCHAIN_METRIC dispute: the evidence panel carries the literal comparator result
(onchain value=51047428496982125 >= threshold=1000000000000 @proxy-latest) read from real
USDC supply on Ethereum mainnet, block-pinned per round so every validator saw the same bytes.
No model was asked anything.
- Trustless β verdicts, bond custody, settlement math, owed amounts,
read_truth(). All on-chain, all deterministic or consensus-settled. - Trusted for availability, not truth β the evidence proxy. It guarantees every validator in
a round sees identical evidence bytes; it cannot assign a label. For
WEB_EVENTit is in the evidence supply chain (a compromised proxy could feed misleading search snippets to the judging LLM β the same trust class as any oracle's data source). Mitigations: asserter-supplied sources are fetched independently, evidence is capped and sanitized, and the band degrades toUNRESOLVABLEon thin evidence. ForONCHAIN_METRICthe proxy only relays pinned-blocketh_callbytes. - Trusted for delivery, not verdicts β the payout relayer. Who is owed what is recorded trustlessly on-chain; only the cash movement is relayer-executed. A stalled relayer can delay payment, never change it.
- Untrusted β the console and its read cache. Conveniences over public state, with a direct-RPC fallback and a "verify on chain" bypass built in.
The public Bradbury RPC is a shared resource across every developer on the testnet, and
GenLayer's team flagged the architectural point directly after early rate-limit reports.
proxy/app.py serves GET /state/summary and GET /state/assertion/<id>: a cached,
single-upstream-reader tier with ETag / stale-while-revalidate semantics, so any number of
browsers share one small flat read rate instead of each adding their own load. This is a
performance layer over public chain state, not a truth layer β writes never touch it,
frontend/sworn-client.js falls back to a direct gen_call if the cache is cold or down, and
the assertion detail page has a one-click "verify on chain" action that re-reads straight from
the chain. Current deployment note, stated plainly: the proxy is not yet returning an
Access-Control-Allow-Origin header, so the browser presently takes the direct-gen_call
fallback path rather than the cache. The console renders correct live data either way β this
is a proxy header fix, not a design change, and it cannot affect a verdict because the cache
is non-authoritative by construction. Full mechanics in
proxy/README.md.
Depth: docs/CONSENSUS_DESIGN.md for the band, the determinism
sandwich, timing and economic security; docs/SUBMISSION_SPEC.md
Β§1 and Β§5 for the full audited architecture and trust-boundary diagrams.
GenLayer's own Intelligent Oracle resolves markets you define β you write the question, IO answers it. SWORN adjudicates stake-backed disagreements β it only ever runs consensus when someone has bonded a challenge, and the economics (whose money is at risk, who gets slashed, what the protocol keeps) are the product, not a detail. If nobody disputes a sworn statement, it settles for free; IO has no equivalent of "free unless contested." That's the distinction this repo is built around, not an afterthought.
-
Bonds are really custodied.
swear/challengecollect native GEN into the contract β confirmed by reading the contract's own on-chain balance grow by exactly the bonds paid in. -
Settlement is deterministic integer math. On
TRUEthe asserter takes the challenger's bond minuschallenge_fee_bps(5% default) to the protocol; onFALSE, symmetric in the asserter's bond; the winner's own bond is simply returned, so slashing only ever touches the loser's stake.UNRESOLVABLEis loss-making for both sides β the asserter losesasserter_penalty_bps(10% default), the challenger loseschallenge_fee_bpsβ so neither frivolous assertions nor frivolous challenges are free. -
Griefing is priced. Every settled assertion bumps a strike against whichever side lost (both, on
UNRESOLVABLE), raising that address's future effective minimum bond. Measured live: by the time one address had ~15 settled assertions against it, its effective floor had risen to roughly 0.16 GEN from a 0.01 GEN base. -
Challenger bond β₯ asserter bond. A challenger cannot contest with a token amount and hope to win big off someone else's larger stake.
-
Settlement is per-recipient, idempotent, and releases real custody on-chain wherever the runner permits it. Each side calls its own claim β
claim_asserter(),claim_challenger(),claim_protocol_fee()β independently, in any order, and exactly once. (claim_bond()remains as a thin compatibility router.) The verdict, the split and the ledger are entirely trustless and on-chain, and the conservation identitypayout_asserter + payout_challenger + protocol_fee == bond + challenger_bondholds integer-exact on every settlement path. -
Delivery is routed by verified recipient capability, because this runner will not deliver value to a wallet. Measured, not assumed (probe matrix with tx ids in
docs/GENLAYER-FEEDBACK.md):recipient route what happens contract (proved it has code) ONCHAINnative transfer out of custody, inside the claim tx; marked *_claimedwallet RELAYERmarked *_pending, custody untouched, delivered byscripts/relay_payouts.py, then attested on-chain viaconfirm_delivery()A claim is never marked complete before the money moves β on the
ONCHAINroute that is enforced by the contract itself. On theRELAYERroute the guarantee extends only as far as the relayer's honesty: the contract cannot verify an L1 hash from inside, soscripts/relay_payouts.pywaits for the delivery receipt and refuses to attest without one. This repository preserves a live example of what a false attestation looks like, and why that check exists (docs/LIVE-PROOF.md). Capability is proved, not declared:register_onchain_recipient()only accepts a caller whosesender_address != origin_address, which only an internal (contract-originated) call can satisfy, so a wallet cannot route its own payout into a transfer that would silently swallow it. -
The earlier "
emit_transferis a universal no-op" finding in this repo was wrong, and is corrected indocs/GENLAYER-FEEDBACK.md. The SDK signature isemit_transfer(*, value, on=β¦); the defaulton='finalized'merely defers delivery until the transaction finalizes β it does arrive, but slowly and with no observable pending state, so every earlier observation window was shorter than finalization. Withon='accepted'the same call pays inside the transaction. The genuine remaining gap is contractβEOA, which never pays under either setting even after finalization β not the primitive itself. -
Delivery is proven by balance-delta, not by a clean
ACCEPTEDpoll. Live on Bradbury, a contract recipient's claim released custody inside the claim transaction: recipient +0.005000 GEN, contract β0.005000 GEN, the recipient's__receive__fired, and a second claim moved +0.000000 on both sides. Full transcript with tx ids:docs/LIVE-PROOF.md. -
dispute_summary(assertion_id)reads the whole economic story in one call: asserter, challenger, winner, loser, slashed amount, protocol fee, label, claimed. -
The relayer is an unreimbursed funding source, and the bond it covers stays locked. On the
RELAYERroute the relayer pays the recipient from its own balance and the corresponding bond remains in the contract permanently β_pay()cannot reach a wallet, so there is no on-chain reimbursement path and no way to release that balance once the claim is marked complete. This is a bounded consequence of the runner limitation above, not a design choice, and it disappears if the EOA-transfer gap is closed upstream. Two practical corollaries: don't run a relayer for a deployment you don't control, and don't readcustodied_balanceas a solvency figure β it includes bonds behind payouts already settled off-chain. Full statement indocs/CONSENSUS_DESIGN.md. -
Composability proven three ways, not two. A real consumer contract (
contracts/consumer_escrow.py) gates its own release onread_truth()and distinguishes release / refund / wait:RESOLVED_TRUEreleases to the beneficiary,RESOLVED_FALSEandRESOLVED_UNRESOLVABLErefund the depositor, and aPENDING/CHALLENGED/RESOLVINGassertion refuses to settle at all rather than treating "not true yet" as "false". Proven live on Bradbury:release()against pending truth left the escrow byte-for-byte unchanged and moved nothing (docs/LIVE-PROOF.md).
scripts/finrate.py runs a 15-item graded-difficulty corpus
(tests/direct/conftest.py::GRADED_CORPUS) live against a deployed contract, rather than
asserting a number.
| tier | n | finalization rate | agreement with expected |
|---|---|---|---|
| decisive | 8 | 1.00 | 1.00 |
| medium | 3 | 1.00 | 1.00 |
| ambiguous | 4 | 1.00 | 1.00 |
Every one of the 15 items finalized to the correct label: decisive TRUE and FALSE claims
were correctly distinguished (not just defaulted to one side), medium claims requiring a read
past the lead paragraph resolved correctly, and every subjective or predictive ambiguous claim
correctly landed on UNRESOLVABLE rather than being forced to a side.
The honest caveat: that 100% is the eventual, independently-verified outcome β not a clean
one-shot. Getting there required retrying past real Bradbury-testnet infra issues and surfaced
two genuine bugs (a grief-strike bond floor rising past a fixed test bond; ACCEPTED not being
proof of persistence under load). None of them were evidence-pipeline or prompt-correctness
bugs β once a transaction durably landed, the label was always right. Full methodology, the
per-item raw results, and the complete caveat:
docs/FINALIZATION_RATE.md.
You only need read_truth(assertion_id) -> dict. Declare it with @gl.contract_interface and
call it as a view β no write, no gas, no LLM cost on your side:
@gl.contract_interface
class Sworn:
class View:
def read_truth(self, assertion_id: str) -> dict: ...
class Write:
pass
class YourContract(gl.Contract):
sworn_addr: Address
def __init__(self, sworn_addr: str) -> None:
self.sworn_addr = Address(sworn_addr)
@gl.public.write
def your_gated_action(self, assertion_id: str) -> None:
truth = Sworn(self.sworn_addr).view().read_truth(assertion_id)
if truth["resolved"] and truth["true"]:
... # release, mint, payout -- whatever your contract does
else:
... # refuse, refund, or wait (it may not be resolved yet)Prediction markets, insurance payouts, escrow release, DAO clawback β these are not separate
integrations. They are customers of one interface: each gates its own value on a sworn,
dispute-tested fact instead of trusting an off-chain resolver.
read_truth_by_ref(ref_hash) maps your own external reference key to the same shape.
The worked escrow example and the one caveat that applies to any contract built this way are in
docs/BUILD.md.
| Component | State |
|---|---|
Gate-zero probe (contracts/gatezero.py) |
Ready β run first, see GATEZERO_RUNBOOK.md |
| Phase 1 β deterministic core | LIVE β swear / challenge / finalize_unchallenged / per-recipient claim_* / read_truth + guards, slash math, greybox hardening |
Phase 2 β resolve() (WEB_EVENT) |
LIVE β text-rendered legible evidence + a deterministic independent-search proxy, both verified load-bearing |
| Phase 3 β real economics | LIVE β per-recipient idempotent claims with capability-routed delivery (on-chain release balance-delta proven; wallets via relayer attestation), dispute_summary view, consumer_escrow composability including the wait state |
Phase 3 β ONCHAIN_METRIC |
LIVE β TRUE and FALSE claims resolved against real Ethereum mainnet state via eth_call proxied through Render, per-round block pinning, API key server-side |
| Phase 4 β tests | Band test passing; deterministic-core + finalization-rate measured live |
| Phase 5 β frontend | LIVE β swornic.vercel.app: landing + an 11-route console (dashboard, assertions, detail, swear, read_truth() explorer, activity, validators, wallet, developers, docs, settings), zero-dependency gen_call reads with EIP-1193 writes, proxy-cached read tier with direct-RPC fallback and "verify on chain", four headless smokes gating CI |
| Path | What it is |
|---|---|
contracts/sworn.py |
Core protocol: bonds, window, consensus, settlement, read_truth() |
contracts/consumer_escrow.py |
Example consumer β gates escrow release on read_truth() |
contracts/gatezero.py |
Runner-capability probe (run before resolve() was enabled) |
proxy/app.py |
Evidence transport: per-round search + eth_call determinism, console read cache |
frontend/SWORN Console.dc.html |
Protocol console |
frontend/sworn-client.js |
The only browserβchain integration surface |
docs/CONSENSUS_DESIGN.md |
The agreement band, labels, determinism sandwich, economics |
docs/SUBMISSION_SPEC.md |
Audited architecture, trust boundaries, diagrams, roadmap, Truth Receipt spec |
docs/BUILD.md |
Build-on-SWORN: the read_truth() interface + escrow pattern |
docs/GENLAYER-FEEDBACK.md |
Runner quirks + workarounds, honestly logged |
docs/FINALIZATION_RATE.md |
Measured live finalization curve |
docs/FRONTEND.md |
Frontend integration decisions record |
scripts/sworn_smoke.py |
Live on-chain smoke: swear / challenge / resolve / claim / payout_proof |
scripts/relay_payouts.py |
Off-chain payout relayer |
tests/direct/ |
Band test + deterministic-core acceptance tests |
Everything in this section is future work. None of it is part of the v1 submission. The deployed v1 behavior is what the sections above describe. Full detail:
docs/SUBMISSION_SPEC.mdΒ§6.
- Mechanical Evidence Preparation Layer (the centerpiece) β formalize the evidence work that v1 does inline (markupβtext, trimming, sanitizing, caps, per-round caching) into an explicitly mechanical stage that may normalize, deduplicate, order, hash, chunk and package evidence β and must never assign truth, assign confidence, replace consensus, or make semantic judgments. The invariant is normative: removing the preparation layer must never change a protocol verdict. It may only affect latency, bandwidth and ergonomics. That is concretely testable β the finalization corpus becomes its regression suite.
- Truth Receipt β specified, not yet built (
docs/SUBMISSION_SPEC.mdΒ§7). A presentation-layer artifact derived deterministically from finalized on-chain state: one canonical card and versioned JSON object (sworn-receipt/1) answering what was claimed, what was decided, what moved, and how do I check it myself β carrying the exactread_truth()call that reproduces it. It introduces no protocol state and is never consumed by contracts or consensus. Receipts exist only after settlement. - Versioned truth and receipt indexing β receipts are deterministic derivations of finalized state, so an indexer could pre-generate them keyed by assertion id and serve them without touching trust, byte-comparable with client-built ones.
- Protocol-surface conveniences β events for swear/challenge/resolve/claim, an
effective_min_bond(addr)view, and native payout delivery retiring the relayer if the runner'semit_transferis fixed upstream. All excluded from v1 because they would touch the frozen protocol surface.
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # put a funded Bradbury key in ACCOUNT_PRIVATE_KEY
# GATE-ZERO (do this before building resolve()):
python deploy/deploy.py gatezero
python scripts/gatezero_run.py all
# ...wait ~3 min...
python scripts/gatezero_run.py dt
python scripts/gatezero_run.py read
# fill in GATEZERO_RUNBOOK.md results; if green, proceed.
# Main contract:
python deploy/deploy.py swornThen drive it live β swear, challenge, resolve, claim, prove the payout, read the dispute:
python scripts/sworn_smoke.py paramsDeploy target: Bradbury (chain 4221). Runner:
py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6.
Pinned SWORN address:
0x74Cd8e753E970D2D70Bfca46068e10CEEb6d9B99
(see deploy/sworn.deployment.json; rotates on redeploy β
frontend/sworn-config.js is generated from it and CI hard-fails if the two drift).
Builder Program submission β git tag submission-v1. That tag is the immutable snapshot
submitted for review; reviewers should evaluate the repository at it.
Active development β branch v2. Ongoing work continues there and does not alter the
submitted snapshot.
| Live app | https://swornic.vercel.app/ |
| Demo video | https://youtu.be/iMwNheI6koc |
| Launch thread | https://x.com/i/status/2075976433655075091 |
| Contract (Bradbury 4221) | 0x74Cd8e753E970D2D70Bfca46068e10CEEb6d9B99 |
| Search / RPC proxy | https://sworn.onrender.com |
| Docs index | docs/ β consensus Β· build Β· finalization Β· runner feedback Β· architecture & roadmap Β· frontend |






