sable network
Buy on Index
Documentation · all sections

Asset attestation

Sable Attestation never says "trust us". It says: here is what we checked, how we checked it, when we checked it, and what the evidence shows.

You give it an asset identifier. It reads the sources it can reach, decides a status for each claim, and stores a signed, versioned record carrying the sha256 of every byte it read. Anyone can re-run the calls and check the signature without a Sable account.

What "verified" means here, and what it does not

This is the most important section on the page, so it is first.

VERIFIED means: Sable performed the listed checks, by the listed methods, at the listed time, and they agreed. Nothing more.

It does not mean the asset is sound, solvent, legally issued, correctly described, or a good idea. Sable is not an auditor. This is not an audit, not a rating, and never investment advice. Every record carries that sentence in its own signed payload, so it cannot be separated from the claim it qualifies.

Three rules follow from that, and they are enforced in code, not by convention:

  1. Nothing is invented. A source that is not configured or cannot be reached yields UNAVAILABLE for the claims that depend on it. There is no fallback value, no last-known figure presented as current, and no default market-price vendor assumed into existence.
  2. An issuer's word is never a check. Anything an issuer declared — backing value, yield, maturity — is recorded as UNVERIFIED, with the issuer named as the source. A declared backing figure is never proof of reserves.
  3. Payment cannot move a status. A check costs the same whatever it finds; a DISCREPANCY costs exactly what a VERIFIED costs. Reading a record, its history and its evidence is free, so nobody ever has a financial reason to leave a discrepancy unread.

The six statuses

StatusWhat it means
VERIFIEDWhat it meansThe listed checks ran by the listed methods and agreed, inside the freshness window.
PARTIALLY_VERIFIEDWhat it meansSome claims verified, at least one did not. Read the per-claim statuses; this headline is not a summary of the weakest one.
UNVERIFIEDWhat it meansNothing independent supports this. Usually an issuer's own statement, recorded as a statement.
STALEWhat it meansChecked, but outside its freshness window. Shown labelled as old — never re-presented as current.
DISCREPANCYWhat it meansTwo sources that should agree do not, beyond the stated tolerance.
UNAVAILABLEWhat it meansA required source was unreachable or is not configured. No figure is published in this state.

A DISCREPANCY in any claim becomes the headline status of the whole record, whatever else passed. That is deliberate: a disagreement is the one thing that must never be averaged away.

Identifiers

FormExample
EVM tokenExampleevm:1:0xdac17f958d2ee523a2206206994597c13d831ec7
Sable Vault assetExamplevault:va_0123456789abcdef
Bare addressExample0xdac17f… together with chain_id

Non-EVM token identifiers are accepted too. When an identifier is not recognised the gateway refuses it and names every form it does accept, so that error is the authoritative list rather than this table.

ISIN, CUSIP and FIGI identifiers are refused, with an explanation. Sable has no data source for them on this deployment, and answering with a page of UNAVAILABLE claims after charging for the check would be worse than an honest error.

On-chain checks run against the RPCs the operator configured for payments. An asset on a chain this deployment has no RPC for is not refused — every on-chain claim comes back UNAVAILABLE, naming the missing chain, because "we cannot check this here" is itself something you need to know.

Running a check

curl -s https://api.buildsable.com/v1/attestations/check \
  -H "authorization: Bearer $SABLE_KEY" \
  -H 'content-type: application/json' \
  -d '{
        "asset": "evm:1:0xdac17f958d2ee523a2206206994597c13d831ec7",
        "expected_supply": "88306226684053103",
        "tolerance_bps": 0
      }'
FieldMeaning
assetMeaningThe identifier, in one of the forms above. Required.
chain_idMeaningRequired only when asset is a bare 0x address.
expected_supplyMeaningAn issuer-declared supply in raw base units, reconciled against the chain.
tolerance_bpsMeaningReconciliation tolerance, 0–10000. Defaults to 0 — exact.
freshness_secsMeaningHow long this record stays current, 60 s to 30 days.
vault_asset_idMeaningLink a Sable Vault asset so the issuer's declared facts join the record. Issuer-only.

What gets checked

ClaimHow
identityHoweth_getCode (is there a contract at all?) plus symbol(), name(), decimals(). An address with no code is a DISCREPANCY, not a pass.
supplyHowtotalSupply(), decoded as an exact uint256 — a supply too large for any Rust primitive is still exact, because it is carried as a decimal string.
admin_controlHowowner() and the EIP-1967 admin storage slot.
upgradeabilityHowThe EIP-1967 implementation slot: is this a proxy, and to what?
pause_stateHowpaused(), where the contract exposes it.
token_authoritiesHowmintAuthority and freezeAuthority, on token standards that expose them — live, or revoked.
supply_reconciliationHowExact integer comparison of a declared supply against the chain.
issuer, declared_backing, yield, maturityHowRead from a linked Vault entry. Always UNVERIFIED: they are the issuer's own word.
custodyHowAnswered on every record, and the honest answer today is UNAVAILABLE — no custodian feed is configured.

Every claim carries its own limitations list saying what it does not establish. Some of those are the point:

  • A null owner() means the standard getter is absent, not that the contract is ownerless — privileged roles can live in access-control mappings this check does not enumerate.
  • An empty EIP-1967 slot means this is not an EIP-1967 proxy. It does not mean the contract is immutable.
  • The absence of a paused() getter is not evidence that transfers cannot be halted.

Reconciliation, and why rounding cannot hide anything

Where a declared figure can be compared against an independent one, the comparison is exact integer cross-multiplication:

|reported − expected| × 10000  ≤  |expected| × tolerance_bps

No division, so no rounding can widen the tolerance. A tolerance of 0 means exactly equal — a difference of one unit against a quintillion is a DISCREPANCY, which is the whole point. The deviation shown on the record is a display value — the one figure here that division produces — and it is rounded up, so a displayed deviation is never smaller than the real one.

An expected of zero against a non-zero reported is a DISCREPANCY at every tolerance: a percentage of zero is undefined, and "we expected none and found some" is a disagreement, not a pass.

Freshness

Every claim carries verified_at, a window, and the stale_after timestamp derived from them. Past that timestamp a read reports STALE — the record is not rewritten, it is labelled. status is what is true now; status_at_check is what was true when the check ran. Both are returned.

A DISCREPANCY does not decay into STALE as it ages. Ageing never downgrades a disagreement into something milder.

Evidence

curl -s "https://api.buildsable.com/v1/attestations/$ASSET_ID/evidence" \
  -H "authorization: Bearer $SABLE_KEY"

Each entry names the source, when it was read, and the sha256 of the exact bytes that came back. Pass ?include_body=true to get the bytes themselves.

The bytes are stored encrypted at rest (AES-GCM, the same envelope discipline as the rest of Sable) and are never logged. They are opened only to answer the owner's own read. The store is content-addressed, so two records that read the same chain state share one entry — which is what makes "is this the same reading you showed me before?" answerable by hash equality.

Versioning

Records are versioned, never overwritten. A re-check writes a new row and marks the old one superseded; there is no update path that changes a status, a claim, or an evidence reference after the fact.

curl -s "https://api.buildsable.com/v1/attestations/$ASSET_ID/history" \
  -H "authorization: Bearer $SABLE_KEY"

An old record is returned exactly as it was signed, so "what did Sable say last Tuesday, and does that record still verify?" always has an answer.

The public view

GET /v1/assets/{assetId}/verification

No account, no key, no attribution. It serves the newest record whose every source is publicly re-readable on-chain, and shows neither who ran the check nor how many times.

A record that draws on an issuer declaration or sealed Vault data has no public view at all and returns 404. That is decided when the record is written, not by stripping fields on the way out.

The rendered version lives at /attest/{assetId} — it verifies the signature live on load and prints the exact RPC call behind every claim.

Webhooks

EventWhen
attestation.verifiedWhenThe headline became VERIFIED — first time, or a recovery. An unchanged verified record is silent.
attestation.discrepancyWhenA claim disagrees with what it was reconciled against.
attestation.staleWhenThe record this check replaced had already passed its freshness window.
attestation.contract_changedWhenContract state moved: an implementation upgrade, an ownership change, a pause flipping, an authority appearing.
attestation.source_changedWhenThe set of sources behind a claim moved — a feed appeared, or stopped answering.

Payloads are content-free: ids, the status and the version. The claims and their evidence stay behind your own authenticated read.

Verifying a record

The record is signed with the same key that signs every Sable receipt, so it verifies through the endpoint you already use:

curl -s https://api.buildsable.com/v1/receipts/verify \
  -H 'content-type: application/json' \
  -d '{"receipt":"<record>","signature":"<signature>"}'

Then check the recovered address against the gateway's published signing key at GET /v1/receipts/pubkey. And re-read the sources: every claim names the exact call behind it, so you can run them against any node and compare.

Not built

Stated plainly, so nobody assumes otherwise:

  • Continuous monitoring. Checks run when you ask for one. Nothing re-checks an asset on a schedule and bills you for it in the background.
  • Market prices and NAV. The price source is configuration-gated and absent by default; where it is unconfigured, the nav claim is UNAVAILABLE and says so. Sable ships no default price vendor.
  • Custody verification. No custodian feed is configured anywhere yet, so custody is UNAVAILABLE on every record.
  • Securities identifiers. No ISIN/CUSIP/FIGI data source.
  • Mint and burn event history. Not scanned in this version.
Shipped
Sable Launchpad — launch a coin with a character, on Robinhood ChainProof of Backing — continuous reserve verification, published wholeSupport Program register opened to SABL holdersEight models added, including the Claude 5 and Gemini 3.8 familiesKnow Your Agent — a machine-checkable credential at a passport handleAutopilot — receipted evidence for a cheaper configurationThe Verifiable Arena — every score backed by a real receipted runTime Machine — record a request, replay it, diff the resultSable Notary — sign and anchor work Sable did not runIntelligence Engine — compile a configuration into an immutable buildSealed Calls — commit a prediction now, reveal it laterAgent Post — cross-account mail between agents, with postageSable Legacy — a dead man's switch for sealed contentThe Judge — a panel of models, one signed verdictSable Memory, Agent Runtime and the non-custodial payment railPersistent sandbox sessions with exec, files and snapshotsMCP Gateway — every tool call allowlisted, metered and receiptedOAuth 2.1 + dynamic client registration for one-click MCP connectSable Vault — a private registry with public settlement