Soroban contracts that answer one question about a Stellar-issued asset: how much of what is circulating is backed, according to whom, and how recently?
Circulating supply is computed from public ledger data and posted on-chain with a hash of its breakdown. Reserve figures are posted by the issuer's own account, a registered auditor, or a registered transcriber, and the signer sets the report's tier. Any contract can then call:
is_covered(asset, min_bps, max_age, min_tier) -> booland refuse an asset that is under-backed or whose figures are out of date.
| Repo | What it is |
|---|---|
| plimsoll-contracts (this repo) | Rust/Soroban contracts |
| plimsoll-app | Web app and TypeScript SDK |
| plimsoll-indexer | Go service: supply poster, event indexer, read API |
Live app: https://plimsoll-app-gths-amber.vercel.app/ (mirror: https://plimsoll-protocol.github.io/plimsoll-app/)
Docs: https://plimsoll-protocol.gitbook.io/plimsoll-protocol-docs/
| Maintainer | GitHub | Contact |
|---|---|---|
| dunnidev | @dunnidev | GitHub Discussions |
Questions, ideas and contributor coordination happen in Discussions.
| Contract | Responsibility |
|---|---|
reporter-registry |
Who may speak: auditors, transcribers and supply posters, managed by an admin. |
coverage-ledger |
What was said: asset listings, supply snapshots, reserve reports, and the coverage / is_covered queries. |
covered-vault |
Reference consumer: refuses deposits while the asset is not covered; never blocks withdrawals. |
plimsoll-types |
Shared types, basis-point math and cross-contract client traits. |
reporter-registry ◀── role_of ── coverage-ledger ◀── is_covered ── covered-vault
│
└── name() ──▶ Stellar Asset Contract
The full function-by-function specification is in docs/SPEC.md, and the event topic and data layouts every off-chain reader depends on are in docs/EVENTS.md.
- Issued assets are a protocol feature. Supply for any classic asset can be summed from trustlines, claimable balances, liquidity pools and contract balances, with no cooperation from the issuer.
- The issuer account can sign Soroban auth. A report signed by
asset.issueris unforgeable, which is what the Issuer-signed tier relies on. - Every asset has a built-in Stellar Asset Contract whose
name()isCODE:ISSUER.list_assetaccepts only genuine SACs and parses that name, so a look-alike token cannot be listed under a real issuer.
| Tier | Value | Who signs |
|---|---|---|
| Auditor-signed | 3 | Address registered with the Auditor role |
| Issuer-signed | 2 | The asset's issuer account |
| Transcribed | 1 | Address registered with the Transcriber role |
| Contract | Address |
|---|---|
| reporter-registry | CCZCBR7MGSW5LGIEUQR5TU5Z7JQWBDEESB3RGPTRPMCBUMOISRPY7GOB |
| coverage-ledger | CC2QQ7R4FLPHZP7KA5AO4IASXXICTG6N4GQXCXITTEBNYQEYTYXKMN4D |
| covered-vault | CC3SUECAJSAC4PBV4QLUII7X5Y4WNVL5YGTBAQ4CHCAH6R3QLRYJUZHQ |
Listed demo assets: PUSD (demo issuer, SAC CCHPT4…HIM7S) and Circle's testnet USDC
(SAC CBIELT…DAMA). Full record: deployments/testnet.json.
Requirements: Rust stable with the wasm32v1-none target, and
Stellar CLI 28 or later.
git clone https://github.com/plimsoll-protocol/plimsoll-contracts
cd plimsoll-contracts
cargo test # 58 tests across all crates
stellar contract build # wasm in target/wasm32v1-none/release/
python3 scripts/check_wasm_sizes.py # each wasm against its size budgetThe size budgets live in size-budgets.json and CI enforces them, so a contract cannot quietly grow towards the install limit. Raising one is fine; doing it in the commit that needs the room is the point.
Deploy your own copy to testnet (creates and funds the identities it needs):
./scripts/deploy-testnet.shQuery the live deployment:
stellar contract invoke --network testnet --send=no \
--id CC2QQ7R4FLPHZP7KA5AO4IASXXICTG6N4GQXCXITTEBNYQEYTYXKMN4D -- \
coverage --sac CCHPT4TEJDPZQDSUVW3NP6A35ROGV4WEFZKT45SKWYCFZSUB7R5HIM7S --min_tier 1A reserve report expires. The PUSD vault asks for one at most 7 days old, so
the demo needs its reserves restated or it quietly stops demonstrating
anything — is_covered starts answering false for a well-backed asset, and
both demo vaults refuse deposits for different reasons that look identical
from outside.
./scripts/refresh-demo-reserves.sh verify # does the demo still say what it claims?
./scripts/refresh-demo-reserves.sh post # restate the reserves (needs the reporter keys)verify needs no keys: it asks the contract whether PUSD is covered and QUSD
is refused, and checks that every report's doc_hash still matches the
document it points at. The Demo coverage workflow
runs it twice a day and restates the reserves when they are more than three
days old, which keeps the standing statements in
docs/demo matching what is on chain.
Issues labelled good first issue
are scoped for a first pull request. Read CONTRIBUTING.md before you start,
and comment on an issue to have it assigned.
Security reports: see SECURITY.md. Do not open public issues for vulnerabilities.