Quickstart
Fetch an attestation, verify it yourself, and branch on it — three steps, no key.
Argued in full in the whitepaper at §7.
Three steps end to end: fetch, verify, branch. The whole point of the design is that step three is cheap enough to sit inside a deployment path.
Fetch it#
curl -s https://dapp.cleaton.xyz/api/v1/pool/0xbeeff033f34c046626b8d0a041844c5d1a5409ddNo key and no account. Reads are free and unauthenticated, and the endpoints §11 names as public goods stay that way when metering starts. What is metered later is freshness — causing a new attestation to be computed — not access to one that exists.
The response#
{
"pool": "0xbeeff033f34c046626b8d0a041844c5d1a5409dd",
"chainId": 4663,
"poolKey": "4663:0xbeeff033f34c046626b8d0a041844c5d1a5409dd",
"symbol": "steakUSDG/USDG",
"protocol": "Morpho",
"horizon": 90,
"confidence": "0.65",
"issuedAt": 1787325423,
"expiry": 1787411823,
"expired": false,
"outcome": "pending",
"signature": "0xd810beb6...4f201b",
"attester": "0xb6486340a930e21b7ee505111188e929120d9eda",
"signed": true,
"sample": false
}That reads as: this pool is expected to hold at least 70% of its reference liquidity for ninety days, and the model puts 0.65 weight on its own call.
outcome is pending, held or broke. Every row published so far is pending, and that is the honest state rather than a bug — a ninety-day claim is answerable on day ninety and not before.
Branch on it#
const a = await (await fetch(`${API}/pool/${pool}`)).json();
// 1. The claim is worth exactly what the signature is. See "Verify a signature".
if (!(await recovers(a, ATTESTER))) throw new Error("not signed by Cleaton");
// 2. expiry is about the ATTESTATION, not the pool. Default validity is 24h.
if (a.expiry < now()) throw new Error("stale — request a fresh attestation");
// 3. Silence is not approval. 84 attestations against 558 tracked pools:
// most pools return no_attestation, and that is not a pass.
if (a.error === "no_attestation") return skip(pool);
// 4. Compare against YOUR lock, not against a generic threshold.
if (a.horizon < lockDays) return skip(pool);
if (Number(a.confidence) < 0.6) return skip(pool);
await allocate(pool, amount);Or let an agent do it: pre_deploy_check over MCP takes the pool and your lock and returns the same comparison with the reason named.
Three mistakes to avoid#
| Mistake | Why it bites |
|---|---|
Treating horizon and expiry as the same expiry | A 90-day horizon issued once would otherwise be quoted for 90 days against conditions that changed on day two. Validity defaults to 24 hours. |
Reading no_attestation as a pass | Most tracked pools have no horizon: scoring needs observed fee income, and most sources do not report it. Absence means nobody has looked, which is the opposite of an all-clear. |
Re-parsing confidence as a number before hashing | It is a fixed two-decimal string because the signature covers it as text. Turn it into a float and back and the signature stops recovering. |