Mechanical trap-scanner and on-chain safety gate for Stellar assets.
Assay answers one question: can the issuer of this asset take or freeze my tokens after I hold them? It answers it from the ledger's own rules — the issuer's authorization flags — not from reputation, community reports, or a domain blocklist.
Safety registry CBK4FBIHMDTXCUPE4E3ZDVSFJSCY5FJETTKNIQPN4LFJIKKIBLKIXQ73
Network Testnet (Test SDF Network ; September 2015)
get_safety(asset) is callable now, by any Soroban contract, atomically.
docs/integrating.md has a copy-pasteable gate and a
worked example contract that is also deployed on testnet
(CAL5VYSWLKG367D5IYGI57XH7EMN5PLJ4CD6K3MO2HJBYYKEKPG3NKRX);
docs/deployment.md has the transaction hashes, the
attested assets, and the live fail-closed checks.
Stellar already has a reputation layer, and Assay does not rebuild it. StellarExpert publishes asset ratings, a curated address directory, and a malicious-domain blocklist; SEP-0037 defines the directory format; the SCF runs a scam-flagging process. Assay consumes those as attributed input signals and never re-derives them.
What no tool in the ecosystem does is the other half:
- Trap mechanics from the ledger itself. Authorization flags are consensus-enforced facts about what the issuer is able to do. They are true whether or not anyone has reported the asset yet, which means a brand-new asset with no reputation at all is still fully classifiable.
- An on-chain gate.
get_safety(asset)is designed to be callable by another Soroban contract, atomically, in the same transaction as the action it protects.
Detecting a flag is trivial. Judging it is not.
auth_clawback_enabled is not inherently malicious — it is the mechanism
regulated issuers use to meet legal obligations like sanctions enforcement
and court-ordered reversal. A scanner that paints every clawback-capable
asset red is wrong about every well-run regulated stablecoin, and a scanner
that is wrong about the biggest assets on the network gets ignored.
So Assay's engine reports severity plus reasoning, never a boolean, and carries an explicit, evaluated position on legitimate clawback use. See docs/severity-model.md for the model and docs/eval.md for the labelled set it is measured against.
Every document under docs/ appears in exactly one table below, grouped by
who reads it. CI fails if a document is added without a row here, or if a row
links a file that does not exist (see internal/docindex).
Call get_safety, run the binary, consume the API.
| Document | What's in it |
|---|---|
| docs/integrating.md | How your contract calls get_safety and gates on both severity and the bitset |
| docs/contract-interface.md | get_safety(asset) design and the evidence_hash encoding |
| docs/checks.md | Each mechanic Assay checks, and what it can and cannot conclude |
| docs/cli.md | The assay binary: scan, attestation, verify, diff, history, serve |
| docs/api.md | Every HTTP endpoint, and the JSON each one returns |
| docs/api-versioning.md | What /api/v1 guarantees: additive vs breaking changes and the deprecation process |
| docs/caching.md | How the scanner caches the signals it consumes (StellarExpert directory and blocklist), and for how long |
| docs/asset-lists.md | Consuming SEP-0042 Stellar Asset Lists as a second curation source, and why listing is never a safety signal |
| docs/timestamps.md | Observation timestamp semantics: which clock each stored time comes from, and what each one means |
| docs/freshness.md | How old an attestation may be: measured flag-change rates and window guidance per use class |
| docs/glossary.md | Every state Assay reports — severity, accountability, verdict state, undetermined — and the state each is most often confused with |
| docs/consumer-integration-research.md | Which get_safety consumer has the strongest evidence for a reference integration, and why |
Check an attestation, investigate a verdict, audit what Assay claims.
| Document | What's in it |
|---|---|
| docs/verifying.md | How a third party verifies a downloaded binary, then an attestation, end to end, without reading the source |
| docs/trust.md | What you are trusting: verifiable facts vs. relayed claims, and consequence matrix |
| docs/severity-model.md | The judgment layer: severity levels, the legitimate-use carve-out, and why |
| docs/severity-accountability.md | Why severity and accountability are never blended into one score, and how every surface must present both |
| docs/temporal.md | The temporal trust model: what an attestation says about one instant, and why freshness is the caller's policy |
| docs/transitions.md | What changed between two observations: capability, severity, accountability and evidence movement |
| docs/history.md | The observation history API: what is retained, for how long, and what an empty history means |
| docs/differential.md | Differential derivation: an independent re-derivation of ledger facts, compared against what the scanner produced |
| docs/debugging-a-verdict.md | A systematic procedure for investigating a surprising verdict back to its source |
| docs/fail-closed.md | Every place a failure could influence the answer, classified as fail-closed or not, with the evidence |
| docs/ever-escalated-marker.md | Design (not implemented): an on-chain record that closes the reputation/attribution threat class |
| docs/threat-model.md | The actors, what each can do, and which attacks Assay defends against, accepts, or scopes out |
| docs/offline-bundle.md | Archiving the exact source bytes behind an attestation, and verifying them without a network |
| docs/eval.md | Labelled trap/legitimate set and current results |
| docs/severity-rating-comparison.md | Dated comparison of Assay severity with StellarExpert's rating for the top-50 fixture and scanned sample |
| docs/wallet-clawback-survey.md | What Freighter, LOBSTR, xBull, StellarX and Albedo warn a user about before adding a clawback-enabled trustline (surveyed 2026-10-20) |
Read or change the code, or review a design.
| Document | What's in it |
|---|---|
| docs/first-15-minutes.md | Start here as a contributor: clone, test, scan one asset, read its report against the glossary, run the eval, find a starter issue |
| docs/architecture.md | The Go scanner and the two Soroban contracts, and how the pieces fit together |
| docs/adding-a-check.md | How to write a new mechanic check |
| docs/pipeline.md | The full ledger-fact to on-chain-verdict path for a single asset |
| docs/attestation-run.md | Every asset scanned, what each check returned, and what the run exposed about the scanner |
| docs/attestation-writer.md | Design (not implemented): the attestation writer pipeline, answering the open questions in #8 |
| docs/multi-attestor.md | Design (for review): approaches to multi-attestor writes, answering #88 |
| docs/testing.md | Package-level testing guide: which test belongs where, and how CI runs them |
| docs/mutation-catalogue.md | The mutation catalogue and baseline: which mutations were applied by hand, and what the tests caught |
| docs/sep-asset-safety-attestation.md | Draft SEP for the asset safety attestation interface |
| docs/sessions/2026-08-10-mvp-core.md | Session log: empty repo to a working scanner, eval and contract |
| docs/sessions/2026-08-15-testnet-deployment.md | Session log: the contract from passing local tests to live on testnet |
| docs/sessions/2026-09-17-audit-and-provenance.md | Session log: an integrity audit of what is merged and how to reproduce it |
Run the scanner and the registry.
| Document | What's in it |
|---|---|
| docs/deployment.md | Deployed addresses, attested assets, transaction hashes |
| docs/attester-key.md | Attester key custody, loss/compromise response, rotation limits |
| docs/attester.md | assay-attester: the keyless dry-run and the issuer watcher that run ahead of the attestation writer |
| docs/attestation-correction.md | Runbook: treating a wrong attestation as an incident and correcting it |
| docs/source-outage.md | Runbook: what to do when a scan reports an upstream source failure |
| docs/reproducibility-mismatch.md | Runbook: the reproducibility job reports a mismatch |
| Document | What's in it |
|---|---|
| docs/i18n/ | French and Spanish translations of the status section, glossary and integration guide; English is authoritative |
The mechanics engine, the HTTP API, and the on-chain gate all work. The gate is
deployed to testnet, 18 real assets have been scanned and 10 attested from live
scans, and the round trip — scan, attest, get_safety — is reproducible end to
end: re-checked on 2026-09-17, a fresh scan reproduced the stored evidence_hash
for every attested asset, with the limits recorded in
#24.
docs/verifying.md is the procedure a third party follows to
check one of those attestations from scratch, including what a mismatch does and
does not prove. It begins by verifying the scanner itself against its published
checksum and build provenance, because a verifier comparing evidence hashes is
otherwise trusting the binary they downloaded.
Four things are worth knowing before you rely on any of it:
- Coverage is 10 attested assets. Everything else on the network returns
None. That is the correct answer — an asset nobody has scanned is unknown, not safe — but it means the registry is not yet useful as a general lookup, and a correctly written gate will refuse nearly everything. - An attestation is only as fresh as its
attested_at. Nothing is refreshing them on a schedule. Issuer flags can change after an attestation is written, so pass amax_age_secsyou would actually accept rather than assuming someone is keeping the registry current. docs/freshness.md measures how often flags actually change and recommends windows per use class. - This is testnet, not mainnet. One key can write any attestation, and
testnet is periodically reset. The live registry's entries are archived: they
are restored on read with their original
attested_at, and they do not read as missing (docs/deployment.md). Nothing here is ready for money. - Severity is capability, not prediction. It says what an issuer can do, never what they are likely to do. A regulated stablecoin with clawback and a scam with clawback score the same, on purpose — see the severity model.
The gaps are not left implicit. docs/threat-model.md names the actors, states which attacks are defended (with the test that holds them), which are accepted risks, and which are out of scope — including the one that matters most, that a single compromised attester key can currently cause an admission.
Apache-2.0. See LICENSE.