Solidus Bridge runs on testnet. Everything shown here is test data with no monetary value.

What happens between an issuer revoking a credential and a contract on another chain refusing it

An issuer revokes a credential on Solidus. A contract on Sepolia keeps answering "valid" until its mirror hears otherwise. This page follows that gap: what Solidus queues, what a destination does with it, and what a gate answers meanwhile. What a credential status bridge is gives the overview, and export a credential covers the step before.

The trigger

One event starts it: the issuer's revoke transaction. Solidus refuses it when the credential is unknown, already revoked, or the sender is not its issuer. A refused revoke leaves the credential as it was and queues nothing.

When the revoke succeeds, the chain marks the credential revoked and, in the same execution, runs the bridge fan-out: the step that copies a change to every destination that must hear it.

What is queued, and for whom

The fan-out walks the credential's exports. For each export whose status is active, it queues one status message with status 2 (revoked) for that export's destination, in the same Solidus block as the revoke. Each message takes the next sequence number for its destination.

The unit is the export, not the chain. An export id includes the destination, so a credential exported to Sepolia and to Fuji has two ids and gets two messages.

Nothing is queued when the credential has no exports, or when every export is already unbound. An unbound export has already been taken back, so it hears nothing.

Revoking an accreditation is a second case. The accreditation credential is what makes an issuer count as accredited. Revoking it queues an issuer status message, "not accredited", for every registered destination. Once it applies, a gate answers reason 3 (ISSUER_NOT_ACCREDITED) for that issuer's credentials, unless an earlier check fails first.

What happens next

  1. Sign. After the block commits, each validator signs each message. The API reported 4 validators and a threshold of 3 on 2026-10-08.
  2. Deliver. The gateway collects the signatures and calls submit on the mirror, in sequence. Anyone else may call it too.
  3. Apply. In one transaction the mirror checks the sequence and the signatures, then overwrites the export's record with status 2.
  4. Report. The API marks the message applied once that destination block is final.

How validators sign and the gateway delivers has the mechanics. The delivered and applied times are observations, so they trail the block. One Sepolia heartbeat (not a revocation) was mined at 16:45:36Z on 2026-10-08 and recorded applied at 16:59:13Z.

What a contract sees while it waits

The mirror stores a status as a number: 1 active, 2 revoked, 3 expired, 4 unbound. It stores only what has applied and has no pending state, so a contract cannot see a queued revocation. The old status stays until the new one applies.

Moment Status on the mirror A gate answers
Before the revoke 1 OK, while the other checks pass
Revoke block on Solidus, message queued 1 OK
Being signed, delivered, or waiting behind an earlier message 1 OK, then STALE (5) once the last heartbeat is older than maxAge
Every call after the apply transaction 2 NOT_ACTIVE (2)

After the apply, the gate keeps answering 2 if the bridge goes quiet, because it checks the status before staleness.

Row 3 is bounded by the heartbeat. The mirror applies messages in strict order, so a stuck revocation holds back every later heartbeat. The gate then answers OK until the last applied heartbeat is older than maxAge, and STALE after. Silence closes the gate covers choosing maxAge.

GET /v1/credentials/{exportId} (API reference) follows the newest message the bridge has read from Solidus, so it can say revoked before the mirror does. A contract reads the mirror. To see whether the mirror has applied it, read the state of the message named by lastMessageId.

What does not change

  • An unbound export. It stays unbound (4), and a later revoke queues nothing for it. It cannot return, because Solidus refuses to export a revoked credential. The gate answers 2 for revoked and unbound alike, so read exportOf to tell them apart.
  • An expiry. The revoked message repeats the export's validUntil, and revocation does not move it. No Solidus path writes status 3: a gate reports EXPIRED by comparing validUntil with the destination's block time, and a revoked record answers 2 first.
  • A different credential. A revoke touches one credential's exports. The mirror finds a holder's export through a pointer, latestExport, keyed by holder, type hash and issuer hash. The pointer moves only when an active status applies.

Take two credentials of one type from one issuer, exported to the same holder on one chain. They share a key. Revoke the export that applied first and the pointer still names the later one, so the gate stays open. Revoke the later one and the pointer does not move back. The gate reads the later record, finds it revoked, and answers 2 although the first export is active, until a new active status for the key applies. The Solana devnet program uses the same rule.

See it in one command

These reads show the shape of the answers, not a revocation. The export id below is computed from a placeholder credential and holder on Sepolia, and belongs to no export.

RPC=https://ethereum-sepolia-rpc.publicnode.com
MIRROR=0x0a5a0d19206f436d67B7b87BF4c889d5b769517A

CRED=$(cast from-utf8 'urn:solidus:credential:EXAMPLE')
HOLDER=$(cast pad --left --len 32 0x00000000000000000000000000000000000000a1)
ID=$(cast keccak $(cast concat-hex $CRED 0x00aa36a7 $HOLDER))
echo $ID
# 0x8da5482e9a5c68cebb3ccd317e11aac90ccd35845fbdb1a612bd25cae58887c0

cast call $MIRROR 'exportOf(bytes32)((bytes32,bytes32,bytes32,uint8,uint64,uint64))' $ID --rpc-url $RPC
# (0x0000...0000, 0x0000...0000, 0x0000...0000, 0, 0, 0)

curl -sL -A 'Mozilla/5.0' -w '\nHTTP %{http_code} %{content_type}\n' https://bridge.solidus.network/v1/credentials/$ID
# {"type":"about:blank","title":"export not found","status":404,"code":"export_not_found","detail":"no export 0x8da5482e9a5c68cebb3ccd317e11aac90ccd35845fbdb1a612bd25cae58887c0"}
# HTTP 404 application/problem+json; charset=utf-8

Run on 2026-10-08, with the zero words shortened. 0x00aa36a7 is the Sepolia domain, 11155111, as four bytes. On Fuji (domain 43113) the same inputs hash to a different id, 0x8c3ffed8...6253.

The mirror returns zeros for an unknown id and does not revert. A stored record never has status 0, so a 0 means no record. Read the mirror contract shows how to build a gate key, and check a condition without seeing the data lists the gate's reasons. Live delivery health is on status and the trust model on security.

Keep reading