{
  "markdown": "# Utuh\n\n**A completeness layer for the Attestcoin Protocol, and undercollateralized credit built on it.**\n\n_utuh_ — Indonesian: whole, intact, with nothing missing.\n\nAny registry on this protocol can tell you what it holds. Ask one whether a borrower has ever been\nliquidated and it can only answer with what someone chose to submit — and the borrower will not be\nsubmitting that. Utuh bonds the claim that a set is all of them, and pays half the bond to anyone\nwho proves one event was left out.\n\nBuilt for BUIDL CTC 2026 Fall on Creditcoin.\n\n**Technical brief:** https://claude.ai/code/artifact/2caca05b-c659-463f-b5ca-28e207f95147\n\nDeployed and verified on Creditcoin CC3 Testnet. **[The console is live at\nutuh.vercel.app](https://utuh.vercel.app/)** — it reads the chain from your own\nbrowser, lets anyone sweep Ethereum and break an incomplete claim, and lets a borrower be\nunderwritten end to end without cloning anything. `npm run web` runs the same page locally.\n\nBuilding something else on Creditcoin that needs a sentence about events that did _not_ happen?\nThe registry is usable on its own — see **[docs/INTEGRATING.md](docs/INTEGRATING.md)**.\n\n---\n\n## The problem\n\nThe Attestcoin Protocol proves that a source-chain transaction _happened_. A Merkle proof places\nthe transaction in a block; a continuity proof anchors that block to an attestation stored on\nCreditcoin. The Block Prover precompile at `0x0FD2` checks both natively, synchronously, inside a\nsingle Creditcoin block.\n\nWhat it cannot prove is that a _set_ of events is complete.\n\nWhoever submits proofs chooses which proofs to submit. Every one of them verifies. Nothing in the\nprotocol notices the ones that were left out.\n\nFor credit, that gap is fatal. The sentence every on-chain credit system needs is:\n\n> _This borrower has never been liquidated._\n\nThat is a statement about events which do not exist, and an inclusion proof can only ever speak\nabout events which do. A borrower assembles their own history, submits the flattering half, and\neach proof checks out.\n\nThis is not hypothetical. Season 1 of this hackathon drew 76 submissions, and more than twenty of\nthem were some form of on-chain credit score or reputation-based lending; one of those, CreditX,\ntook a prize. Every one of them inherits this hole, the winner included — placing well is not the\nsame as closing it, and an inclusion proof gives none of them a way to.\n\n## What this uses of Creditcoin's, and what it deliberately does not\n\n`npm run doctor` ends by making a live request to every Creditcoin-owned surface this depends on,\nso the list below is checkable rather than asserted. Today's run:\n\n```\nCreditcoin ecosystem surfaces\n  ok    USC Oracle dashboard      200 — every verifyAndEmit here is listed by source height\n  ok    testnet indexer           68,160 attestations of Ethereum, chain key 3\n  ok    mainnet indexer           52,463 attestations of Ethereum, chain key 1\n  ok    Creditcoin Mainnet RPC    chain id 102030\n  ok    Blockscout · registry     UtuhRegistry verified\n  ok    Sourcify · registry       chain 102031: match match, creation match\n  ok    Blockscout · credit       UtuhCredit verified\n  ok    Sourcify · credit         chain 102031: match match, creation match\n```\n\nOn top of the protocol itself — both precompiles, both `verifyAndEmit` overloads, `EvmV1Decoder`,\n`@gluwa/usc-contracts` and `@gluwa/usc-sdk`, the hosted Proof Builder under both of its hostnames,\nand `RawProofBuilder` as the path that needs no hosted service at all.\n\nTwo of those lines are worth reading twice. Every append and every refutation goes through\n`verifyAndEmit` rather than its `view` twin, so **Creditcoin's own oracle dashboard is the record**\n— the network logged this project's work, not the other way round. And the **Creditcoin Mainnet**\nindexer is read even though nothing here is deployed there: the console audits mainnet's attestors\nagainst Ethereum itself, because auditing only the network you deployed to leaves the production\noracle unchecked by the one page that can check it.\n\n### What is deliberately not integrated\n\nPenguinBridge, PenguinSwap and PenguinBase are Creditcoin's own consumer applications and none of\nthem appears here. That is a design decision, not an oversight:\n\n- **A bridge would contradict the product.** The whole claim is that nothing bridges — history\n  stays on Ethereum, credit is issued on Creditcoin, and only proof crosses. Wiring a bridge in\n  would make the pitch untrue.\n- **A DEX price would reintroduce the oracle.** Crossing from a source asset to CTC is a price,\n  and the lender states its own rate on-chain precisely so the protocol never pretends to know\n  one. Reading a swap rate would replace a stated assumption with a hidden one.\n\nAn integration that weakens the thesis is not depth. Naming the two we refused is more honest than\nlisting eleven we bolted on.\n\n## Where this sits next to Creditcoin's own example\n\nCreditcoin ships a [loan-flow tutorial](https://github.com/gluwa/attestcoin-protocol-examples/tree/main/loan)\n— `ASCLoanManager` on Creditcoin, an auxiliary contract on Sepolia, an offchain worker between\nthem. (Mid-season, gluwa reorganised that repo and began renaming USC to ASC — Attestcoin Smart\nContracts; the old `USCLoanManager` link now redirects to a 404, and everything below was re-read\nagainst the renamed sources on 2026-09-04. The mechanics are unchanged.) It is a good tutorial and\nit is the right shape for what it teaches. It is also a precise illustration of the gap, because it\nis the reference every builder will start from.\n\nEach event proves itself as it happens. `_markLoanAsFunded` and `_noteLoanRepayment` take one\nproven transaction each; `ASCBase.execute` verifies it through `0x0FD2` and records the query so\nit cannot be replayed. Every _present_ fact is cryptographic, and that part is sound.\n\nTwo things follow from proving one event at a time, and neither is a defect in the tutorial:\n\n- Nothing asks whether the set is complete. That is fine when the loan is already registered on\n  chain and the contract knows exactly which events it is waiting for. It stops being fine the\n  moment the question is _\"has this borrower ever been liquidated\"_ — because that question is\n  about events nobody submitted, and no number of inclusion proofs answers it.\n- Default is declared, not proven. `markLoanAsExpired` is `onlyOwner`. Somebody trusted says the\n  loan went bad. For a tutorial that is the honest simplification; for underwriting a stranger it\n  is the whole problem moved one layer up.\n\nUtuh is the layer that would sit under such a contract: a bonded claim that a set of events is\n_all_ of them, refutable by anyone with one proof of one omitted event. Presence stays\ncryptographic exactly as above. Absence becomes economic, which is the most that can be had.\n\n## What Utuh does\n\nTwo halves, each sound on its own.\n\n**Nothing invented.** Every event enters a claim through `appendBatch`, which runs the Attestcoin\nBlock Prover on it before it is recorded. Members must arrive in strictly ascending\n`(blockHeight, txIndex, logIndex)` order, which the contract enforces rather than trusts. A claim\ncan only ever contain events that provably happened, so its aggregate cannot be inflated.\n\n**Nothing omitted.** The claimant bonds the assertion that the set is complete. Anyone may break\nthe claim by proving a single in-scope event the set does not contain. Absence is never proven — a\nclaim of absence is _refuted by presence_, which Attestcoin does prove.\n\n```\npresence  →  cryptographic   (Merkle + continuity, verified by 0x0FD2)\nabsence   →  economic        (bonded assertion, refutable by one proof)\n```\n\n### What scales, and what does not\n\n_Settling_ a claim is O(1): the registry never verifies a whole set, so a claim spanning ten\nthousand events is broken by a single proof or by none at all.\n\n_Building_ one is not. `npm run gas` measures it rather than reasoning about it — it finds every\ntransaction a registry has ever seen from the registry's own logs, reads the receipts, and fits a\ncost model. No explorer involved. Across the four registries deployed so far, 139 transactions:\n\n| Call                      | Gas (mean) | % of a 75M block |\n| ------------------------- | ---------- | ---------------- |\n| `open`                    | 252,750    | 0.33%            |\n| `seal`                    | 206,010    | 0.27%            |\n| `appendBatch` (1 event)   | 552,956    | 0.73%            |\n| `appendBatch` (2 events)  | 893,903    | 1.19%            |\n| `appendBatch` (10 events) | 2,662,045  | 3.54%            |\n| `refute`                  | 611,556    | 0.81%            |\n| `finalize`                | 209,258    | 0.27%            |\n| `withdraw`                | 205,870    | 0.27%            |\n\nMember count alone does not explain those. One append of **three** events cost 541,464 gas while\nan append of **two** cost 878,903, because the cost follows the _size of the transactions being\nproven_, not how many events sit inside them. A least-squares fit over all 56 appends the published\nregistries have seen, against the call's own calldata gas and its member count:\n\n```\n  290,899 gas fixed\n    1.51 x the call's own calldata gas   (1.00 would be exact)\n   81,427 gas per member on top of its bytes\n  worst residual 294,878 gas, 32% of the mean append\n\neach append carried median 94, 2..394 continuity hashes and median 16, 8..78 Merkle siblings\n  charging the bytes at their own EVM price, 62 gas per continuity hash:\n  5,866 gas at the median, 0.6% of the mean append\n```\n\nThe calldata term is the solid one, and it is the interesting one: **a proven transaction costs\nabout twice its own calldata gas**, because those bytes are not merely paid for at the door — they\nare copied, RLP-decoded by `EvmV1Decoder`, and hashed by the Block Prover. Proving one in-scope\nlog inside a fat mainnet transaction means carrying all thirty kilobytes of it, and that is not a\nchoice the claimant has.\n\nThe per-member term is **not** well determined, and it is worth saying so rather than quoting it.\nAn earlier fit over 25 appends put it at 20,526 gas — almost exactly a cold `SSTORE`, which was a\nsatisfying number and the reason to distrust it. Eleven more appends moved it to 61,265, and twenty\nmore to 81,427 while the calldata multiplier fell from 2.3× to 1.5× — the two terms trading against\neach other, which is what collinear regressors do. Members\nand bytes are correlated in this data (more members generally means more bytes), so separating the\ntwo needs appends this repo has not made: many members with small transactions, and few with large\nones. What the data does support is the shape — fixed cost, a dominant per-byte cost, and some\nper-member cost on top — not a precise value for the last of those.\n\nThe obvious missing term is the continuity proof, and it is instructive that it stays out. The\nprotocol prices verification almost entirely by it: the published figure is `2.3e-5 + 2.9e-7 ×\nhashes` CTC, so at the median proof here the hashes are more than half of what the precompile\ncharges. A fourth regressor for them fits better — worst residual 32% of a mean append down to\n16% — and reports **minus 860 gas a hash**. A hashing operation cannot pay you. Every hash is 32\nbytes of the same calldata the second term already charges for, so given nothing of its own to\nexplain it trades against that term instead, dragging it to 2.21×; reparameterised until both\ncoefficients are positive it prices a root byte at 0.53×, under the floor a byte can cost. So the\nhashes are measured rather than fitted: hold every byte at its own EVM price, fit only what is\nleft, and the hashing comes out at **62 gas each** — three specifications agree to within a gas,\nand a keccak of two words is 42, so the number is the operation rather than an artifact. The\nmedian append carries 94 of them and spends 5,866 gas on them, **0.6% of itself**. The term that\ndominates what the precompile charges is not the term that prices the claim.\n\nThe practical ceiling is therefore set by bytes:\n\n```\n  a 100-event claim:     10 batches,    ~24.5M gas,  0.3 full blocks of it\n  a 1,000-event claim:  100 batches,   ~245.2M gas,  3.3 full blocks\n  a 10,000-event claim: 1000 batches, ~2451.6M gas, 32.7 full blocks\n```\n\nThe asymmetry is still the point — challenging is one proof and a binary search, whatever the claim\nholds — but a claim of ten thousand events is thirty blocks' worth of gas, and that is the number\nthat caps this rather than any argument about storage.\n\n### The subtle part\n\nA challenge window is only meaningful if a watcher could actually have acted inside it. So a claim\nmay not open until its entire block range is already attested on Creditcoin — checked against the\nChainInfo precompile at `0x0FD3`:\n\n```solidity\nif (!CHAIN_INFO.is_height_attested(scope.chainKey, toBlock)) revert RangeNotAttested(...);\n```\n\nWithout that gate, a claimant could cover a range whose tail is not yet attested, and the window\nwould expire on a claim nobody was _able_ to refute. Attestation heights only advance, so once\n`toBlock` is attested the whole range stays provable for the life of the claim.\n\nA second detail: a refuter receives half the slashed bond, not all of it. If they took the whole\nbond, a claimant caught lying could refute their own claim and walk away whole, which would make a\nfalse claim free to attempt. The burned remainder is what puts a price on being wrong.\n\nThat price is smaller than the bond, and the difference matters. A claimant knows which event they\nomitted from the moment they seal, so they can watch for an incoming refutation and send their own\nfrom a second address, taking the refuter's share back. No ordering scheme closes this — an\nearlier draft of these notes claimed commit-reveal would, which was wrong: the claimant holds the\nprivate knowledge, so they simply commit first. What survives is the burn, which nobody can\nrecover.\n\nSo the registry reports `enforceableLoss` rather than the bond, and `isUsable` measures exposure\nagainst that. Sizing a line against the whole bond, as `UtuhCredit` did at first, carried twice\nthe exposure the deterrent actually covered.\n\n## Built on Ethereum mainnet, not Sepolia\n\nCC3 Testnet attests **Ethereum mainnet** (`chainKey 3`) alongside Sepolia (`chainKey 1`), from\ngenesis height 0. Verified live:\n\n```\n$ npm run balance\nattested   sepolia  chainKey 1  height 11530210\nattested   mainnet  chainKey 3  height 25797540\n```\n\nThe mainnet frontier tracks within roughly a hundred blocks of the real chain head. So contracts\non a free testnet can be underwritten on real Aave positions, real USDC flows, and real borrowers,\nwith no capital at risk and nothing simulated.\n\nBudget for the attestation lag when running anything live: a freshly mined block takes on the\norder of ten minutes to become provable on either chain. Historical blocks are immediate, and a\nproof for one 210,000 blocks back still resolves in about seven seconds — it is only the tip that\nyou wait on.\n\nEvery number in the demos comes from Ethereum mainnet.\n\n## UtuhCredit\n\nAn undercollateralized credit line on Creditcoin, underwritten on Ethereum. Nothing bridges: the\nhistory stays on Ethereum, the credit is issued in CTC on Creditcoin, repayment happens back on\nEthereum. The only thing that crosses is proof.\n\nUnderwriting rests on two claims that are adversarial in **opposite** directions, which is what\nmakes the pair sound:\n\n| Claim      | Assertion                                    | Who benefits from a lie | Defence                                     |\n| ---------- | -------------------------------------------- | ----------------------- | ------------------------------------------- |\n| **Volume** | proven Aave USDC repayments                  | inflating it            | every member verified by `0x0FD2` on append |\n| **Clean**  | complete set of liquidations, normally empty | omitting one            | bond, refutable by one liquidation proof    |\n\n```\nlimit = min( 20% of proven volume × the lender's rate , 10 × the bond behind the clean claim )\n```\n\nThe second term is the consumer half of the mechanism. The registry cannot size a bond, because it\ndoes not know what the claim will be used for — only the party about to lend knows its own\nexposure. A line never risks more than a liar stood to lose.\n\n### Two places where units have to be taken seriously\n\nAave's `Repay` carries `amount` in the reserve asset's own decimals. Scoping a volume claim to the\nevent alone would sum WETH's 18 decimals into USDC's 6 and call the total a credit history. So the\nvolume scope **pins the reserve** — `Repay` puts it in topic 1 — and a claim is denominated in\nexactly one asset.\n\nThat leaves a second gap: the claim aggregates USDC at 1e6, a line is CTC at 1e18, and crossing\nbetween them is a price. This contract has no oracle and does not pretend to: the lender fixes\n`VOLUME_UNIT_IN_CTC` at deployment, in the open, where anyone can judge it. A lender wanting a live\nprice puts a feed in front of this contract rather than having the protocol invent one.\n\n### Reading a history is not the same as owning it\n\nUnderwriting reads a public chain. Nothing about reading it proves the reader holds the key that\nwrote it, so before a line opens the borrower must bind their Ethereum address to their Creditcoin\naccount:\n\n```\ncalldata = bytes12(\"utuh:control\") || <creditcoin account>\n```\n\nOne ordinary transaction from the subject address carrying exactly that. `proveControl` verifies\nit through the Block Prover and reads the sender out of the decoded transaction — no signature\nscheme of our own, no trusted relayer. The tag stops the commitment colliding with real calldata,\nand naming the account inside it stops anyone binding a stranger's address to their own account.\n\n**Each commitment may be applied once**, and that is not bookkeeping. A subject can move their\nbinding by sending a second commitment naming a different account — which is how anyone rotates\naway from a Creditcoin account they no longer control. The proof of the _first_ commitment stays\nvalid forever, and anyone may submit it. Without a used-marker the binding is therefore whichever\nproof was replayed most recently, not whichever the subject meant: an attacker holding the\nrotated-away account puts it back at will, including in front of the subject's own `openLine`.\nCreditcoin's own `USCBase` records processed queries for exactly this reason, and this did not\nuntil it was found. `controlIdOf` keys on the chain and the encoded transaction — which carries\nits own signature — and deliberately not on the block height, so a reorg that moved the same\ntransaction cannot make the same commitment usable twice. `npm run control` and `npm run full`\nboth replay the commitment they just used and require `ControlProofAlreadyUsed`.\n\nAny supported source chain will do: an EOA address derives from its public key and is identical on\nall of them, so Sepolia gas proves exactly as much as mainnet gas.\n\nThe terms of a draw are the lender's, never the borrower's. `draw` takes an amount and nothing\nelse; what must come back and by when are computed from policy, converting CTC back through the\nsame rate that produced the limit and rounding up so no draw is small enough to owe nothing.\n\nTwo things about the money are worth stating because both were wrong once. Every rounding in the\ncontract lands against the party carrying the risk: `_repaymentFor` rounds up so no draw is small\nenough to owe nothing, and `backingFor` rounds up so no limit is backed by less than a\n`BOND_MULTIPLE`th of itself. That second one is asked at both ends of a line — when it opens and\nwhen it settles — and it was a bare division at both until it was not.\n\nAnd a lender can name where its own capital goes. `LENDER` is `msg.sender` at construction and\nimmutable, so a lender that is a contract without a payable fallback could `fund` this and never\nget the money back out: `withdraw` would revert with `TransferFailed` forever. `withdrawTo` is the\nway out. The authority check is unchanged — only the lender may call it.\n\nThree smaller rules close the same class of hole. A finalized claim is **spent** when it opens a\nline, so one underwriting funds one line and the cap bounds aggregate exposure rather than each\nline separately. A line's deadline is fixed by its first draw and never moves — otherwise a\nborrower who owes money could buy an unlimited extension by drawing one more wei. And each\nsettlement consumes the source-chain range it rests on, tracked per subject in `settledThrough`,\nbecause marking a _claim_ spent does not stop a _payment_ being spent twice: two lines, two claims\nover overlapping ranges, one transfer inside both.\n\n### One history, one line\n\nA finalized claim can only open one line: `openLine` marks it spent. That is not enough on its own,\nand for a while nothing else was.\n\nThe registry will hold any number of claims over the same range with the same scope, and finalizing\none gives the bond back. So a borrower could build a second claim over the same three repayments,\nfinalize it, open a second line, and draw the limit again — every guard in `openLine` passing each\ntime, because each of them looks at one line in isolation. The bond cap bounded each line and\nnothing bounded the total.\n\n`underwrittenThrough[subject]` is the fix, and it is the same shape as the `settledThrough`\nwatermark that already stopped one payment discharging two debts. Opening a line consumes the range\nit rests on. Borrowing again means new history: a range starting after the last one, still\n`MIN_HISTORY_BLOCKS` long, still inside `MAX_STALENESS_BLOCKS` of the frontier. A credit line that\nrenews on performance, rather than a number that can be spent twice.\n\n### One line at a time, and why that is a rule rather than tidiness\n\n`markDefault` is permissionless and nobody is paid to call it. That was fine while it only wrote a\nstatus, and stopped being fine the moment a standing default started blocking new lines: a borrower\nwhose deadline passed could wait, accumulate a fresh month of history, satisfy\n`underwrittenThrough`, and open the next line with the first one still sitting there overdue and\nunmarked. The guard was resting on a transaction nobody was obliged to send.\n\n`activeLineOf[subject]` removes the dependency. A subject has one line at a time; an overdue line is\nstill `Active`, so it blocks by itself, and `markDefault` goes back to being bookkeeping. Each guard\nthen has exactly one job — the slot says _you have a line open_, the count says _you failed one_.\n\nThe rule needs an exit, or it is a trap. An undrawn line cannot be settled (nothing was borrowed)\nand cannot be defaulted (`markDefault` refuses a `drawn` of zero, correctly — no money went out, so\nnothing was missed), so `closeLine` gives the slot back. It does not give the history back:\n`underwrittenThrough` has already moved, and it should have.\n\n### Whose books you take\n\n`defaultsOf` belongs to one deployment. A borrower who walks away from a line here opens one at the\nlender next door with nothing in the way, and that is the gap a credit bureau fills.\n\nThe tempting shape is a shared contract everyone reports to. It did not survive being designed.\nReports have to be trusted, and a registry anyone may write to is a blacklist with extra steps —\ndeploy a contract, report a rival's borrower as a defaulter, done. Every fix for that is a\npermission, and a permissioned bureau is the centralised thing this whole repository exists to\navoid.\n\nSo there is no bureau. A lender names the peers whose word it takes, in its constructor, and the\nanswer is _pulled_ from the peer's own storage — where the fact was recorded by the contract that\nactually extended the credit. No reports, no writes, nothing to forge: a peer can only ever say what\nhappened on its own books, and the worst a hostile one can do is refuse credit it was never going to\nextend. A lender that names nobody is unaffected by everyone, which is the safe default and has to\nbe a choice rather than an accident.\n\n### Default without proving a negative, and the way back\n\nA drawn line is settled by the borrower proving repayment landed at the lender's Ethereum address.\nIf no finalized claim arrives before the deadline, the line defaults. The contract never\nestablishes that a payment was missed — the burden sits with the only party who could discharge\nit. Silence is the default condition, not an inference.\n\nA default that costs nothing but the line it happened on is not a credit event, though, and that is\nwhat it used to be: `markDefault` set a status and the borrower opened the next line the same block\non a later slice of history. `defaultsOf[subject]` counts defaults that still stand, and `openLine`\nrefuses while any do.\n\n`cure` is the way back. The borrower proves the repayment late, on exactly the terms it was owed —\nsame scope, same watermark, same backing, same amount, every check `settle` makes, sharing one\nfunction with it so the cheaper path cannot drift into existence. The line becomes `Settled`, the\ncount comes down, and the subject can borrow again on history it has not already spent. Nothing is\nforgiven for being late; the deadline has already done its work, which was to record the default\nwhile it stood.\n\nThat distinction is what separates a credit protocol from a blacklist, and it costs one counter.\n\n## Deployed on CC3 Testnet (chain id 102031)\n\nEvery contract below is **verified on Blockscout** — source, ABI and decoded constructor arguments\nare readable at its address. An unverified address is a wall of bytecode, and \"the source is on\nGitHub\" is a different claim from \"this address runs that source\". `npm run verify` republishes\nthem after a redeploy, reading whichever record `DEPLOYMENTS` names — so\n`DEPLOYMENTS=deployments.full.json npm run verify` covers the Sepolia-sourced set including its\nledger, with nothing reconstructed by hand.\n\nDeploying refuses to overwrite an existing record without `REDEPLOY=1`. The addresses below are\nthe ones in it, all verified; `npm run demo` used to begin by replacing them, so following this\nfile to record a demonstration quietly made everything published about them false.\n\nBlockscout reports a _partial_ match: the runtime bytecode agrees and the trailing metadata hash\ndoes not, which is what happens when the compilation environment is not reproduced byte for byte.\nThe code is readable and the functions are callable.\n\n[Sourcify](https://sourcify.dev) disagrees, in the right direction. It compares the metadata hash\ntoo, and reports every contract below as a **full match** — `exact_match` for the decoder and the\nSepolia ledger, `match` for the registry and both credits — from a tree it read itself. `npm run\nverify` now submits to both, because two verifiers that do not share a backend agreeing on the same\nsource is a stronger sentence than one, and because Blockscout forwarding to Sourcify is a thing it\nusually does rather than a thing to rely on: the Sepolia ledger had not arrived until it was sent.\n`repo.sourcify.dev/102031/<address>` has the sources.\n\n### Mainnet-sourced deployment\n\n| Contract       | Address                                                                                                                                                   |\n| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `UtuhRegistry` | [`0x8FA0BD5301D998Be873E31453E53d114929a5Fac`](https://creditcoin-testnet.blockscout.com/address/0x8FA0BD5301D998Be873E31453E53d114929a5Fac?tab=contract) |\n| `UtuhCredit`   | [`0x89FB81b1e453b7Bd18ac1A6AF03C84A40Ce10C57`](https://creditcoin-testnet.blockscout.com/address/0x89FB81b1e453b7Bd18ac1A6AF03C84A40Ce10C57?tab=contract) |\n| `EvmV1Decoder` | [`0x5cab00c032D7d4436f312Dd51ef59Dc5b860df3F`](https://creditcoin-testnet.blockscout.com/address/0x5cab00c032D7d4436f312Dd51ef59Dc5b860df3F?tab=contract) |\n\n`npm run credit` runs against these, on Ethereum mainnet data.\n\n### Sepolia-sourced deployment — the completed loop\n\n| Contract                     | Address                                                                                                                                                   |\n| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `UtuhRegistry`               | [`0x26880c8980Cd54827543bD34c6c613253c69347b`](https://creditcoin-testnet.blockscout.com/address/0x26880c8980Cd54827543bD34c6c613253c69347b?tab=contract) |\n| `UtuhCredit`                 | [`0x0177aDb82152c8673a85271F7F06336B820324b6`](https://creditcoin-testnet.blockscout.com/address/0x0177aDb82152c8673a85271F7F06336B820324b6?tab=contract) |\n| `EvmV1Decoder`               | [`0x084c45552A6c45C7269F4a7041E757ABf4Bcc008`](https://creditcoin-testnet.blockscout.com/address/0x084c45552A6c45C7269F4a7041E757ABf4Bcc008?tab=contract) |\n| `SettlementLedger` (Sepolia) | [`0xC8C9053C4E2c0590df684c12e5f2610EFeC9575B`](https://eth-sepolia.blockscout.com/address/0xC8C9053C4E2c0590df684c12e5f2610EFeC9575B?tab=contract)        |\n\nEverything below is readable at those addresses rather than taken on trust — `claim(id)`,\n`memberCount(id)`, `keyAt(id, i)`, `enforceableLoss(id)`, `line(1)`, `underwrittenThrough(subject)`\nand `settledThrough(subject)` all answer to anyone, and the console at `npm run web` shows them\nwithout a terminal.\n\n| Read                                | Answer                                                                                                                                        |\n| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |\n| `claim(1)`                          | Finalized, 3 members, aggregate 0.003 ETH of proven volume                                                                                    |\n| `keyAt(1, 0..2)`                    | Sepolia blocks 11575883, 11575885, 11575886                                                                                                   |\n| `claim(2)`                          | Finalized, 0 members — the clean claim, and there is nothing to show                                                                          |\n| `claim(3)`                          | Refuted, `enforceableLoss` collapsed to 0                                                                                                     |\n| `claim(4)`                          | Finalized, repayment of 0.000525 ETH                                                                                                          |\n| `claim(5)`                          | Refuted — planted short by one, and broken from a browser                                                                                     |\n| `burned()`                          | 2 CTC, two refuted claimants' halves that nobody collected                                                                                    |\n| `line(1)`                           | Settled, limit 10 CTC, drawn 10 CTC, `repayRequired` 525000000000000                                                                          |\n| `underwrittenThrough(borrower)`     | 11575891 — one past the range that opened the line                                                                                            |\n| `settledThrough(borrower)`          | 11575986 — one past the range that discharged it                                                                                              |\n| `defaultsOf(borrower)`              | 0                                                                                                                                             |\n| `activeLineOf(borrower)`            | 0 — the line settled, so the slot is back                                                                                                     |\n| `claim(6)`, `claim(7)`              | Finalized — 3 members and 0, built **from the browser** by a second borrower                                                                  |\n| `line(2)`                           | **Settled** — opened, drawn, repaid and settled entirely from the console                                                                     |\n| `claim(8)`                          | Finalized, 1 member — the repayment; opened by one run, resumed by the next                                                                   |\n| `claim(9)`, `claim(10)`             | Finalized, unspent, useless — a 4-block range against a 5-block floor; the                                                                    |\n|                                     | page now refuses that before a bond is posted                                                                                                 |\n| `claim(11)`, `claim(12)`, `line(3)` | The second underwriting of the same key, on later history — line 3 Settled — the whole loop, twice, from the page (claim 13 is its repayment) |\n| `peerCount()`                       | 0 — this lender takes nobody else's books, which is the safe default                                                                          |\n\nThe borrower's sweep read `publicnode=3  tenderly=3`: two independent endpoints agreeing, and the\nclaim built on the union rather than on whichever answered first.\n\nLines 2 and 3 belong to `0x0C2ffE823f1b64c975D768c9822F31eFED6f6a83`, a key that has never run a\nscript here. It paid the lender three times on Sepolia and then did everything else **through the\npage** — sent its control commitment (the wallet switched to Sepolia and back), proved it, built\nclaims 6 and 7, waited out their windows, finalized, opened line 2, drew 1 CTC, paid the lender back\nthrough the ledger with the same wallet, built claim 8 over that payment, waited out that window,\nand settled — as `web/tests/borrow.live.spec.ts`, against these contracts. Then it did it again on\nlater history, for line 3. The limit is 5 CTC and not the 12 the volume would justify, because it\nposted the 1 CTC minimum bond and a 1 CTC bond guarantees a 0.5 CTC loss.\n\nClaim 5 is the interesting one. `npm run bait` sealed it deliberately short by one event and told\nnobody. It was found and broken **from the console**, in a browser: the page swept Sepolia across\ntwo endpoints (`publicnode=4  tenderly=4`), checked all four against the claim on-chain, found the\none it omitted, fetched a proof from the hosted builder and sent the refutation itself. That path\nis a Playwright test — `UTUH_LIVE_UI=1 npm run web:test -- refute.live` — so it is a thing that is\nchecked rather than a thing that was done once.\n\nThree figures there are the mechanism, not decoration. The limit is **10 CTC** —\n`enforceableLoss` of 1 CTC times a `BOND_MULTIPLE` of 10 — and not the 12 CTC the 0.003 ETH of\nvolume alone would justify, because a 2 CTC bond only guarantees a 1 CTC loss and it is the\nguarantee that lends. 0.000525 ETH is what drawing 10 CTC obliges at the lender's rate and 105%\nterms; the borrower had no say in the figure. And the two watermarks now read one past the ranges\nthey consumed, which is what stops the same history opening a second line and the same payment\ndischarging a second debt.\n\n`test/Lifecycle.t.sol` reproduces all three of those numbers locally, from the fixture's own\namount and the deployed policy, so they are a property of the code rather than of that afternoon.\n\n**A default, and the way back.** `DEPLOYMENTS=deployments.full.json npm run cure` deploys a second\nUtuhCredit over the same registry with a five-minute repayment window — the recorded run left one\nat [`0x509fab6a2Fd8C1a50dAB8C05cD7C7e53cB29868f`](https://creditcoin-testnet.blockscout.com/address/0x509fab6a2Fd8C1a50dAB8C05cD7C7e53cB29868f) — and underwrites the same\nborrower on the same finalized claims, draws, lets the deadline pass, is marked in default — and\nthen makes it good with the repayment claim the loop already finalized. Claims belong to the\nregistry and `claimSpent` belongs to the credit contract, which is why that costs one deployment\nand one Sepolia transaction rather than a second loop.\n\n**An earlier run, at earlier addresses, did not finish in one go** — twice — and both interruptions\nare worth recording because the chain absorbed them. The first: the process died during the long\nwait for Sepolia's attestation frontier to reach the repayment block. Nothing was lost, because\nnothing was being held in the script; `npm run finish -- <registry> <credit> 1` read the line's\nstate off the chain and closed it. The second was mine. I stopped a resume that looked stuck and\nstarted another, and the first was still running: two processes built the same repayment claim, both\nholding the identical event. Nothing broke, because a settlement consumes both the claim and the\nsource-chain range it rests on — the duplicate could not settle the same line, and the watermark\nmeant that payment could not settle any other. It cost its author a bond locked until its own window\nclosed, and nothing else. Two guards written for a lying claimant turned out to cover a clumsy\nhonest one too.\n\n## Two demonstrations, and why there are two\n\n`npm run credit` reads **Ethereum mainnet**: real Aave positions, real liquidations, real\nborrowers. It underwrites them, refutes a genuinely liquidated address that claims it was never\nliquidated — and then stops, because nobody can prove control of a stranger's address. That\nrefusal is the honest end of that flow.\n\n`npm run full` closes the loop instead. A borrower we control acts on **Sepolia**: they pay a\nlender through `SettlementLedger`, bind their address with a control commitment, get underwritten\non what they actually did, draw CTC on Creditcoin, repay on Sepolia, and settle. Two parties, both\ntransacting for themselves.\n\nThe source-chain contract is not a stand-in for anything under test. The payments are real\ntransfers, the events are real logs in real blocks, and Creditcoin attests them exactly as it\nattests Aave's. A scope is a scope — the registry cannot tell the difference, and does not need\nto.\n\n## The watcher as an MCP server, and why an agent can hold the role\n\n```bash\nnpx utuh-mcp    # published on npm — no clone, no key, no build\nnpm run mcp     # the same server from this repo, for hacking on it\n```\n\n```json\n{ \"mcpServers\": { \"utuh\": { \"command\": \"npx\", \"args\": [\"-y\", \"utuh-mcp\"] } } }\n```\n\nListed in the official [MCP Registry](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.PugarHuda/utuh-mcp)\nas `io.github.PugarHuda/utuh-mcp`, so a client that does not know the package name can still find\nit. The registry hosts metadata only; it proves ownership by fetching the published tarball and\nrequiring its `mcpName` to equal the server name being claimed, which `offchain/build-mcp.ts`\nwrites. `.github/workflows/mcp-registry.yml` publishes the listing from CI over GitHub OIDC — no\nsecret to store — after checking that npm really serves the version being announced.\n\nEvery guarantee here rests on *anyone may refute a claim by proving one in-scope event it left\nout*, and until now \"anyone\" meant a person: at a console, or at the published page. `offchain/mcp.ts`\nputs the same three verbs — look, sweep, refute — behind the Model Context Protocol, which makes\nthe watcher a role an AI agent can hold. An agent needs no account and no capital to look, and the\none that finds an omission is paid half the bond for proving it: a business model that fits inside\na tool call.\n\nFive tools: `tally`, `list_claims`, `sweep_claim`, `refute_claim`, `audit_attestors`. None of them\nis new machinery — each is the same `offchain/lib` function the daemon and the browser console\nalready run, because an MCP server with its own logic would be a third implementation waiting to\ndrift, and \"agents can enforce completeness\" is only credible if agents run the code that\ndemonstrably does.\n\nTools are only one of the protocol's three primitives, and a server that stops there hands a model\nprose it has to re-read every turn. Claims are also **resources** — `utuh://tally`,\n`utuh://claims/{deployment}`, `utuh://claim/{deployment}/{id}` — live JSON a client can attach and\nre-read, carrying the scope a refuter needs and a `refutable` flag that answers the only question\nworth asking first. The watcher's job is a **prompt**, `hold_the_watcher_role`: sweep everything\nstill inside its window, treat \"no gap found\" as provenance rather than proof, and bring a finding\nback rather than spending. And every tool carries its **annotations**, so a client can tell the four\nthat only read from the one that sends a transaction and slashes somebody's bond — the confirmation\nbelongs on exactly one of the five, and now the server says which.\n\n`npm run mcp:test` builds that bundle and speaks the protocol to it — the three listings, a prompt\nfetched, the tally read live off Creditcoin, and `refute_claim` asked to spend with `confirm`\nwithheld, which must refuse. Twenty-nine assertions, no key, and CI runs it on every push: what\nreaches a user is an esbuild bundle with the registry ABI and both deployment records baked in, and\nevery one of those is a thing that can quietly stop being included.\n\nTwo things about it were earned rather than designed. The first client ever connected to this\nserver found the gap in a standing claim and refuted it — a real transaction, a real slashed bond,\n1 CTC of reward — during its own smoke test. That is the pitch demonstrating itself, and it is\nalso why `refute_claim` now demands `confirm: true` before sending: an agent's \"let me just try\nthe tool\" must cost a deliberate second call, not a bond. And stdout is the protocol channel, so\nthe server reroutes every stray print to stderr up front — the SDK's attestation waiter logs\nprogress, and one such line in the middle of a JSON-RPC stream is a corrupted session.\n\n## The console, and why the watcher belongs in a browser\n\n```bash\nnpm run web     # http://127.0.0.1:5173\n```\n\nEverything on that page is read from CC3 Testnet as the page draws it. No server holds a key, no\nindexer stands in between, and there is no seeded state to fall back on — if the chain is\nunreachable the page says so rather than showing the last thing it knew. The ABIs come out of\nforge's own artifacts, so a field the contract stopped having is a load failure rather than a\nplausible-looking zero.\n\nIt shows four things: what Creditcoin says it can attest, read straight off `0x0FD3`; every claim\nin the registry with its bond, its enforceable loss and its remaining window; the lender's policy\nand every line; and a watcher.\n\nThe watcher is the part that had to exist. Every guarantee here rests on one sentence — _anyone may\nrefute a claim by proving one in-scope event it left out_ — and until something is actually\nwatching, that sentence describes a possibility rather than a fact. `npm run watch` is that\nsentence made real for whoever runs a daemon with a funded key, which is a small number of people.\nThe console makes it true for whoever opens a page:\n\n- it rebuilds the claim's scope from what the registry stores, trusting the claimant for nothing;\n- it sweeps the source chain **from the browser**, across independent public endpoints, taking the\n  union rather than a vote, and says how many answered — because \"no gap found\" from one endpoint\n  is not the same claim as \"no gap found\" from two;\n- it checks each event against the claim with `contains`, on chain;\n- and if the claim is short, it fetches one proof from the Proof Builder and sends the refutation.\n\nThat is possible only because the pieces are CORS-open and public: `rpc.cc3-testnet.creditcoin.network`,\nthe source-chain endpoints, and the hosted Proof Builder all answer a browser directly. Nothing\nneeded to be built to make it work, and it means enforcement does not depend on anyone deploying\ninfrastructure. Creditcoin publishes exactly one RPC hostname, though, and it has had bad\nafternoons — on 2026-09-03 its nginx spent half a day refusing large `eth_call` bodies with 413,\nwhich the daily CI probe caught. So the page's reads fail over to Blockscout's `eth-rpc` proxy for\nthe same chain, the one second way onto CC3 that exists; its batch ceiling and burst rationing were\nmeasured before being coded around (`web/chain.ts` has the numbers).\n\nThe sweep is the daemon's own function, imported rather than reimplemented — `scanScopeUnion` in\n`offchain/lib/scope.ts`, bundled into the page. A browser cannot conclude that a claim is complete\non different reasoning than the daemon would.\n\n### Both deployments, and a claim opened up\n\nThe console reads either published deployment — `?deployment=mainnet` for the one that underwrites\nreal Aave history, where the claims about real borrowers live, dozens of them, built by the daily\nlive suite and the mainnet demo. It pages through them twenty-five at a time, and a sweep of a\nmainnet claim runs from the browser against Ethereum mainnet in ten-thousand-block pieces, the\nsize both default endpoints serve; Sepolia's are swept in five-hundred-block pieces because\npublicnode stops answering past that. The daemon uses the same table, so the page and the daemon\ncannot reach different verdicts by asking in different pieces.\n\nA claim has an address. `?claim=N` opens claim N on arrival — what a post, a document or a\nrefuter's message points at — reaching past the first page if it has to, and the address bar\nfollows the picker, so the URL always says what is on screen. And a visitor with two wallets\ninstalled is asked which: the page listens for EIP-6963 announcements and names every wallet that\nanswers, instead of taking whichever one grabbed `window.ethereum` last. A wallet that only does\nthe old thing still connects.\n\nSelecting a claim opens it: every member decoded from the ordering key the registry stores — source\nblock, transaction index, log index — with the block linked into the source chain's explorer and a\npointer to Creditcoin's own oracle dashboard, where the verification the precompile emitted for it\nsits by source height. The registry keeps only the key, so the block is what can be linked; the\ntransaction at that index inside it is the one. \"Read it back yourself\" is a click now.\n\nSeventy-odd links sit between the top of that page and the one button that matters to a keyboard\nuser, so the first Tab lands on a skip link and Enter puts focus on the sweep itself.\n\n### A watcher that is always on, holding nothing\n\n\"A page open in a tab is not a daemon\" is under Known limits, and it stayed true after the console\nshipped: the page makes refuting available to anyone and makes nobody do it. The smallest thing\nthat does it without being paid is `.github/workflows/watch.yml` — every hour, a dry sweep of both\npublished registries, the same union across independent endpoints the daemon and the console run,\nand a red run if a sealed claim is short of an event. The same hour, the same workflow opens the\npublished console in a real browser and asks what a visitor would: it loads, it says which chain it\nis on, the block it shows is this hour's, both deployments read, no pane is broken, and the page\nasked its host for nothing but its own four files. Pages can serve a stale or broken build with\nnothing else in CI noticing; this notices.\n\nIt holds no key. `npm run watch -- --dry` reads and never signs, so it no longer asks for one, and a\npublic repository can run it with nothing in its secrets. The red run is the alert: somebody sealed\na lie and nobody has taken the bond yet, and whoever reads that with a key and a few minutes is\npaid half the bond to act on it. Or give it the key: a fork that sets a `WATCH_PRIVATE_KEY` secret\n(any account with a little CTC for gas) runs the same hourly sweep without `--dry`, refutes what it\nfinds, and is paid the refuter's share — a watcher that costs a repository and nothing else.\n\n### Borrowing from the page\n\nThe scripts could always do this, and that was the problem: being underwritten meant cloning a\nrepository, filling in a `.env` and running TypeScript. The Borrow pane is the same flow with the\nvisitor's own wallet — bind your address (it will send the control commitment on the source chain\nfor you, switching the wallet there and back), build the volume and clean claims, wait out their\nchallenge window, open the line, draw.\n\nNothing there is a shortcut around the protocol. The claims it builds are ordinary claims: swept\nacross independent endpoints, proven event by event through `0x0FD2`, bonded, sealed, refutable by\nthe watcher in the pane above, and finalized only once the window has actually elapsed. The claim\nids are kept in the browser's own storage, so closing the tab during a window costs nothing —\nwhich matters, because a window is measured in blocks and nobody is going to sit and watch one.\n\nThe loop closes in the page too. Step 5 reads what a drawn line owes off the contract — source\nunits, deadline, the exact event and payee a repayment claim must contain — pays the lender through\nthe source-chain ledger with the same wallet when that is how the lender is paid, builds the\nrepayment claim, waits out its window, and settles. A line in default is cured the same way, on the\nterms it was owed. A line never drawn on is given back. And a claim that was opened and never\nsealed — a closed tab, a rejected signature — shows up under Claims with one button that abandons\nit and returns the bond, because the alternative is a borrower who does not know the money is\nthere.\n\nTwo more things the live test taught the page. The precompile saying a block is attested and the\nhosted builder having _indexed_ it are different moments, and a proof request in the gap comes back\n422 — measured on a payment the precompile had attested a minute earlier, and answered 200 by the\nsame builder a minute later. The page now waits on both, the way the SDK does, and treats a 422 as\n\"ask again\" rather than as a failed build. And a build that dies after `open` — that 422 did it\nonce — leaves a claim Open with a bond in it; the page finds that claim again on the next press,\nby its scope and claimant rather than by anything the browser remembered, and resumes it from the\nlast key the registry recorded. Claim 8 on the published registry is the receipt: opened by one\nrun, appended and sealed by the next, one bond.\n\nThe lender has controls too, shown only to the lender: fund, withdraw undrawn. And an overdue line\ncarries a _mark default_ button for anyone, because recording a default is permissionless, unpaid,\nand — since an overdue line blocks the next one by itself — no longer something the guards depend\non. It is still the record peers read, so whoever notices may write it.\n\n### Published without a server\n\n`npm run web:static` bakes the ABIs and the deployment record into the page and writes four files.\nThere is no server in the published build at all, and the browser tests assert exactly that: the\npage boots, reads the live chain, and asks its host for nothing but `index.html`, `main.js`,\n`style.css` and the font they use. A GitHub Actions workflow builds it from each commit's own artifacts, so the ABI the\npage carries is the ABI the contracts were compiled with.\n\nThree more files are written beside those and never requested by the page, because they are for\nother readers. `.well-known/security.txt` is RFC 9116, for a researcher who found the deployment\nrather than the repository. `whitepaper.pdf` is the document. And `llms.txt` is the\n[convention](https://llmstxt.org) an agent reads on arrival — Creditcoin's own Attestcoin docs\npublish one, which is where this project found it. That last one is not a summary of the page: the\npage is for a person, and an agent landing here needs three things in the order it needs them —\nthat the watcher role exists, that holding it costs nothing while it is only looking, and that\n`npx utuh-mcp` lets it hold the role without a browser. A project whose argument is that a watcher\nneed not be a person, leaving nothing at its own URL for a machine to read, is asking one to parse\na screenshot. `static.spec.ts` checks that every tool it advertises is one the server actually has,\nand that the package it points at is really published under the name it gives.\n\n`web/tests/borrow.live.spec.ts` is the test that makes the Borrow pane a claim rather than a hope.\nA fresh key — derived from the operator's, holding nothing but a little Sepolia ETH and a little\nCTC — pays a lender three times on Sepolia, then, **through the page**: sends the control commitment\n(the wallet is switched to Sepolia and back), proves it, builds the volume and clean claims, waits\nout the challenge window, finalizes, opens a line and draws — then pays the lender back through\nthe ledger with the same wallet, builds the repayment claim, waits out that window too, and\nsettles. Every step is a real transaction against the published contracts, and the assertions read\nthe registry and the credit back rather than the page. It resumes: a borrower with a drawn line\nopen picks up at repayment, the way a person would. What stands in for MetaMask is `web/tests/wallet.ts`: a real key in the test process,\nan EIP-1193 provider on the page that routes reads to the real RPC of whichever chain it is on and\nhands every `eth_sendTransaction` back to be signed. It spends money and takes twenty minutes, so it\nis off unless asked for — `UTUH_LIVE_UI=1 npm run web:test -- borrow.live`.\n\n`web/tests/angles.spec.ts` is the console from the angles nobody demos, and two of them found\nthings. In dark mode, with contrast checked: clean. At 375px: nothing sideways, everything\nreachable. From the keyboard alone: Tab to the sweep, Enter, and it sweeps. With a wallet whose\nowner presses _Reject_ on every signature — a real EIP-1193 provider answering 4001 — every write\npath reports the refusal and stays usable. And with `rpc.cc3-testnet.creditcoin.network`\nunreachable from the browser, the page used to sit on _loading_ for as long as anyone cared to\nwait, because a provider pointed at a dead endpoint retries rather than failing; it now says\nCreditcoin is not answering, inside twenty seconds, and shows no number it did not just read.\n\n`web/tests/a11y.spec.ts` runs axe over the rendered page — the real DOM with the chain's answers in\nit — against the WCAG 2.x A and AA rules, and any violation fails the build by name. The console\nexists so that people who would never run a daemon can still refute or borrow, and \"people\" is not\n\"sighted people with a mouse\". It reports zero.\n\n`npm run web:test` drives it in a real browser against the live chain: the chain id it reports has\nto match an independent RPC call, the attestation frontier has to be past genesis, the claims it\nlists have to be the ones the registry holds, and the sweep has to produce a verdict with its\nprovenance attached. With `UTUH_LIVE_UI=1` a further test connects a real wallet, finds a claim\nthat is genuinely short, and refutes it — a real transaction, verified by the real precompile,\nslashing a real bond.\n\n## Layout\n\n```\n.github/workflows/pages.yml the published console, rebuilt from each commit's own artifacts\n.github/workflows/watch.yml hourly: a keyless sweep of both registries, and a browser smoke of the published console\n.github/workflows/codeql.yml CodeQL over the TypeScript that builds, proves, refutes and signs\n.github/dependabot.yml      weekly bumps for npm and the actions; foundry stays pinned by hand\nSECURITY.md                 how to report a way to make a false claim stand\n.github/workflows/ci.yml    fmt, build, tests, gas snapshot, typecheck, slither — and a daily\n                            job that proves real mainnet events against the live precompile,\n                            checks the hosted and local provers still agree, and reports what the\n                            registry has cost. All three need no key and write nothing.\nslither.config.json         which detectors are off, with the reasons next to the code\nknip.json                   what counts as reachable; @gluwa/usc-contracts is imported from\n                            Solidity, which a TypeScript analyser cannot see\n.prettierrc.json            TypeScript formatting, enforced in CI the way forge fmt is\n.gas-snapshot               committed, and CI fails if gas moves more than 5%\nsrc/\n  UtuhRegistry.sol          the completeness layer\n  source/SettlementLedger.sol   deployed on the *source* chain: payments and adverse events\n  UtuhCredit.sol            undercollateralized credit built on it\n  lib/EventScope.sol        which events a claim covers, and how each one counts\n  interfaces/IBlockProver.sol   0x0FD2 — Merkle + continuity verification\n  interfaces/IChainInfo.sol     0x0FD3 — attestation frontier and coverage\ndocs/\n  INTEGRATING.md            using UtuhRegistry from someone else's contract — the registry is\n                            infrastructure, UtuhCredit is one application of it\ntest/\n  EventScope.t.sol          the matcher, ordering key, metrics and leaf identity\n  Consumer.t.sol            a thirty-line consumer that is not Utuh, compiled and tested, so the\n                            claim that the registry is reusable is checked rather than asserted\n  RegistryInvariant.t.sol   four actors, random sequences, and the books have to balance after\n                            every move — every wei escrowed, credited or burned, nothing else\n  UtuhCredit.t.sol          deployment floors, control binding, scope identity, terms, liquidity\n  Lifecycle.t.sol           the whole loop locally — claim, refute, finalize, underwrite, draw,\n                            settle, default, cure — on real Sepolia transaction bytes, with only\n                            the two precompiles' answers substituted\n  fixtures/                 two real Sepolia transactions from a recorded run, and one real\n                            mainnet transaction that reverted — inclusion is not success, and the\n                            check that says so had no test until there were bytes to fail it with\n  SettlementLedger.t.sol    what the source-chain ledger will and will not record as a payment\n  EventScopeKey.symbolic.t.sol  halmos proofs of the ordering key, over every input rather\n                            than 256 samples — `npm run symbolic`\n  CreditRounding.symbolic.t.sol proofs of the money roundings, and a note on the one the\n                            solver could not decide — `npm run symbolic:deep`\noffchain/\n  deploy.ts                 deploy decoder, registry, credit\n  e2e.ts                    honest claim finalized; dishonest claim refuted and slashed\n  creditDemo.ts             underwrite a real Aave borrower; refute a real liquidated one\n  cureDemo.ts               draw, miss the deadline, be marked in default, prove the repayment\n                            late — on chain, against claims the full loop already finalized\n  watch.ts                  the watcher — follows ClaimSealed, sweeps, refutes what is short\n  badClaim.ts               files a deliberately incomplete claim, so the watcher has prey\n  liveTest.ts               the guards unit tests cannot reach, asserted against CC3\n  fullFlow.ts               the whole loop on Sepolia, borrower and lender both acting\n  finishLine.ts             resume an interrupted run — the state lives on-chain, not in the script\n  doctor.ts                 preflight: endpoints, both provers, precompiles, balance\n  verify.ts                 publish sources to Blockscout, constructor args and all\n  proveControl.ts           bind a source-chain address to a Creditcoin account\n  provers.ts                the same proof hosted and locally, compared and time",
  "bytes": 60000,
  "sha": "2cea83fd5290eb19af2443e47f73a5f8fc24f7488a8d5c6c2611369e617d53df",
  "repo_slug": "pugarhuda/utuh",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_pugarhuda_utuh_mcp_e0665feb/readme"
}