SDK docs · sdk/07

Substrate reads

Two public reads — substrate status and attestation standing — that answer from proven chain state without a presentation, and establish an issuer’s standing without taking its word.

Two reads, no presentation

Two reads answer questions about the chain, and about one issuer-signed record, without a presentation, without the proof engine and without you being a registered verifier: GET /v1/status and GET /v1/attestations/{ref}/standing?issuer_id=<64 hex>. They are additive to the frozen verifier-service.v1 contract and versioned separately at v0 — the three v1 operations keep their semantics and their version. Both are public and cacheable for a few seconds; a route a deployment is not configured for answers 404.

Related-party disclosure: the operator of the first issuer and the first verifier, and the first product built on them, are related parties of MintID. Nothing measured there is independent adoption.

Substrate status

One aggregate of what the verifier already holds: the node’s head, the proven registry and root state of every issuer it watches, and the chain’s published root policy — metadata only, no holder, attestation, witness or presentation.

substrateMeansCommits you to
okthe node produces blocks and every watched issuer has a fresh root on an operational, bonded registry recordonly “ask now” — standing can still be unknown for a record no root holds yet
degradedat least one of those failed; detail names whichan operational alarm; standing reads are answering unknown meanwhile
unknownthe node itself could not be readnothing — you have no substrate signal at all

Per watched issuer: active (registry record operational), bond_ok (live bond at or above the governed minimum), root_epoch / root_height (the current root and the block that finalized it), root_age_seconds (now minus when the issuer generated it), fresh (inside its declared window and this verifier’s height-lag policy) and reason, from a closed set: ok, issuer_unknown, issuer_not_active, bond_insufficient, root_missing, root_expired, height_stale, state_unavailable. A last object, verifier, reports the deployment’s own id, origin, height-lag policy and build pins.

The promise object carries chain parameters, not the service’s own configuration. That is why it is published: a cadence read off the chain the verifier proves its reads against is a fact you can check, where the same numbers in a service-level assertion would only be a claim.

Attestation standing

ref is a 32-byte opaque reference in hex. Besides statusactive, suspended, revoked or unknown — you get fresh, issuer_active, bond_ok, the proven root with its epoch and height, and latest_height. reason_code is ok when every check passed, and status is then the witnessed one; every failed check degrades to unknown:

reason_codeWhat happened
state_unavailablea chain read could not be proven
issuer_unknownno registry record for this issuer
issuer_not_activethe issuer is not operational
status_root_staleno current root, or its validity window has passed
height_stalethe root was finalized outside the height-lag window
witness_unavailable · witness_malformedno witness served for this reference; or it did not parse, or names another reference
witness_root_mismatch · witness_invalidthe witness claims a different root or epoch; or it did not recompute to the proven root
bond_insufficientthe witnessed status is still returned, with bond_ok: false

bond_insufficient is the one code that does not degrade: you get the real status, and what a short bond means is your policy.

Standing without taking the issuer’s word

The checks run in trust order and the issuer is consulted last: (1) its registry record, read from the chain with an inclusion proof; (2) its live bond against the governed minimum, from the same proven read; (3) its current status root, that root’s validity window and the height at which it was finalized; (4) only then the witness — fetched from the issuer, recomputed locally, accepted only if it lands on the proven root at the proven epoch. The issuer supplies the path, never the answer: one that lies about a status serves a witness that does not recompute, one that says nothing gets witness_unavailable, never a default yes. Every chain read is proven against a trust anchor, and claimed absence of a record fails closed.

The witness construction is published so you can recompute it yourself. status_byte is 0 active, 1 suspended, 2 revoked. A witness is {ref, status, status_epoch, root, index, path[]}: fold the path from the leaf, taking the sibling on the left while the running index is odd and halving that index each step, then apply the root hash. The construction is off-chain and issuer-internal.

Freshness, degradation, and the arithmetic you need

Roots publish every heartbeat_seconds and are accepted up to max_root_age_seconds; on the first deployment, 30 s and 180 s. Nothing the issuer does is visible before its next root, so time to first verdict is one root period plus your own polling interval — 30 s plus a 60 s poll is up to 90 s. A newly issued reference reads witness_unavailable until that root: the expected first answer, not a failure, and polling faster than a minute buys nothing. max_root_age_seconds is a fail-closed bound, not a delivery promise; the gap above the heartbeat exists so a routine deploy of the issuer’s service does not turn every read unknown, and a revocation still lands in the next root.

When the substrate is behind, fail closed. Never map unknown to yes, never keep serving a cached active past its root’s window, and never retry until a check passes: a loop against state_unavailable or status_root_stale accepts unproven state slowly.

The failure never arrives as an HTTP error. Anything the route accepts returns 200, with the verdict in substrate or in status/reason_code. 400 covers only an issuer_id that is not 32 bytes of hex; a ref that is not 64 hex characters misses the route and gets 404. There is no 5xx for a degraded substrate — alert on the body.

What a re-initialised deployment changes under you

A deployment can be re-initialised: the operator starts a new chain carrying the registry and the issuer’s book across, rather than migrating state in place. Hostnames, issuer id, verifier id, references and statuses survive; the chain identifier changes, block heights restart and root_epoch counts from 1 again. Witnesses regenerate at the first heartbeat and standing answers unknown until the new chain’s first root is finalized — the ordinary degradation path. Persist the chain identifier next to any root_epoch you keep: epochs are ordered only within one chain.

What these reads do not do

They are not presentations and carry no zero-knowledge claim. Standing tells you about a record an issuer signed and placed in a root; it says nothing about whoever holds a credential. substrate: ok is a statement about one chain and its watched issuers at one instant, not a service-level commitment or a substitute for your own checks. Neither read makes you a verifier.

One gap to know about before you meet it: a status root can also carry reserved entries that are not attestations, used to anchor material the rest of the protocol needs. The rule for recomputing them is not published yet. If you are recomputing roots yourself rather than reading standing, ask for it rather than inferring it from a sample.

Next: credentials and holders — how the records behind a root come to exist; and the reason codes — the separate, frozen vocabulary of the presentation decision.