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

How validators sign a status message and a gateway delivers it to another chain

Solidus Bridge runs on its own transport. Validators sign each message and a gateway delivers it. No outside messaging network sits in the path. What a credential status bridge is gives the overview. The destination is inside what is signed covers the signed bytes.

The path in five stops

  1. Queue. An export, unexport, revocation or accreditation change queues a message for each destination it affects, in the same Solidus block. At block end the chain also queues a heartbeat for any destination that is due one. Each message takes the next sequence number for its destination, starting at 1. Nothing is signed or delivered yet.
  2. Sign. After the block is stored, each validator signs one digest per queued message with its own attestation key, separate from its consensus keys. A validator serves only its own signature, through the RPC method solidus_getBridgeAttestation.
  3. Collect. The gateway is a separate process. It asks every validator for its signature, recovers the signer from each one, and ignores the name the validator reports. It keeps only signatures that recover to a configured validator, needs the threshold of them, and sorts them by signer address. It holds no attestation key, so it cannot make a signature.
  4. Submit. The gateway calls submit(message, signatures) on the destination mirror, one sequence number at a time, never skipping ahead. In one transaction the mirror checks the sequence, recovers each signer, requires the threshold of known validators, and stores the status.
  5. Report. The gateway watches each mirror for applied events and serves them to the Bridge API once the block is final by that chain's rule.

On 2026-10-08 the live API reported four validators and a threshold of 3, and the Sepolia, Fuji and Arc mirrors each returned 3 from threshold().

What each timestamp means

GET /v1/messages/{id} reports a state and five timestamps. A sixth state, waiting_for_sequence, means an earlier message for the same destination is not applied yet.

Timestamp What it records Source
queued The Solidus block that queued the message Chain time
committed The block with finality evidence at or above its height Chain time
signed When the API first saw the threshold of valid signatures Observation time
delivered When the gateway first saw the applied event, once final Observation time
applied The same instant as delivered Observation time

submit verifies and applies in one transaction, so delivered equals applied by construction. On 2026-10-08, 50 of 50 recent messages (all heartbeats) had committed equal to queued to the millisecond.

signed is only as fine as the API's polling, so do not compute a signing latency from it. delivered and applied trail the destination block by the chain's finality wait. Block time against applied on 2026-10-08 (UTC): Sepolia 1274, 16:04:24 and 16:20:48; Sepolia 1276, 16:25:12 and 16:40:02; Fuji 1016, 16:43:43 and 16:44:31. The gateway counts a Sepolia block once it carries the finalized tag, and a Fuji block after one confirmation.

Why anyone may deliver

The mirror's submit limits no caller. The EVM contract has no owner check, and the Solana devnet program accepts any fee payer. The authority is the signatures: at least the threshold of distinct, known validators, in ascending signer order, over a digest tied to this mirror's destination and the next sequence number.

A stranger needs two public things. The signatures come from GET /v1/attestations/{domain}/{seq}. The message bytes come from the Solidus RPC method solidus_getBridgeMessages at https://rpc.solidus.network. Their keccak256 hash is the messageId, which cast keccak confirmed for Sepolia 1274 on 2026-10-08. The Bridge API reference lists the signature route.

If two parties submit the same message, the second call reverts because the sequence number has moved on. If the gateway stops, a message is delayed and nothing is forged, because the gateway holds no key that can sign.

What a stall looks like

A stalled gateway costs time, not trust. While it is down:

  • Each mirror keeps the last status it applied, so a status that has since changed can still read as the old one.
  • Messages apply in strict order for each destination. One waiting message holds back every later one there, heartbeats included. Other destinations carry on.
  • Heartbeats stop. A gate built on the library returns stale once the last applied heartbeat is older than the integrator's maxAge, covered in check a condition without seeing the data.

GET /v1/status shows it from outside. Per chain, heartbeatAgeSecs is the seconds since the API saw the latest heartbeat applied (null if none), sequenceGap counts messages queued and not yet seen applied, and nextDomainSeq is the number the next queued message will carry.

A gap is not always a stall. At about 16:55 UTC on 2026-10-08 (one reading) Sepolia showed sequenceGap 2 and heartbeatAgeSecs 163, while Fuji and Arc showed a gap of 0. The mirror's own counter equalled the API's next number, so every queued message was already applied. The API counts a delivery only after the block is final.

curl -sL https://bridge.solidus.network/v1/status \
  | jq -c '.chains[] | select(.key=="sepolia") | {nextDomainSeq, sequenceGap}'
# {"nextDomainSeq":1280,"sequenceGap":2}

cast call 0x0a5a0d19206f436d67B7b87BF4c889d5b769517A 'nextDomainSeq()(uint64)' \
  --rpc-url https://ethereum-sepolia-rpc.publicnode.com
# 1280

The API's number is the next one it will queue, and the mirror's is the next one it will accept. When they are equal, nothing waits. When the mirror's is lower, messages wait, and waiting_for_sequence appears on the later ones. Live health per chain is on status, and the trust model is on security.

See it in one command

The list has no chain filter. Ask for a few, pick a Sepolia row, then ask for it by id.

curl -sL 'https://bridge.solidus.network/v1/messages?limit=20' \
  | jq -c '.messages[] | select(.chainKey=="sepolia") | {domainSeq, state, messageId}'

curl -sL https://bridge.solidus.network/v1/messages/0x3f7c997ce335ff6906e9c01a202374e3340402624b472170147b1e847f3e0bbd

Trimmed from the second response on 2026-10-08, with the transaction hash shortened:

{
  "chainKey": "sepolia",
  "domainSeq": 1276,
  "kind": "heartbeat",
  "state": "applied",
  "timestamps": {
    "queued": "2026-10-08T16:23:11.415Z",
    "committed": "2026-10-08T16:23:11.415Z",
    "signed": "2026-10-08T16:23:12.291Z",
    "delivered": "2026-10-08T16:40:02.986Z",
    "applied": "2026-10-08T16:40:02.986Z"
  },
  "deliveryTx": "0x804d9e72...0962"
}

Now read the delivery block's own time:

RPC=https://ethereum-sepolia-rpc.publicnode.com
TX=$(curl -sL https://bridge.solidus.network/v1/messages/0x3f7c997ce335ff6906e9c01a202374e3340402624b472170147b1e847f3e0bbd | jq -r .deliveryTx)
cast block $(cast tx $TX blockNumber --rpc-url $RPC) -f timestamp --rpc-url $RPC
# 1791476712

That is 2026-10-08T16:25:12Z, about fifteen minutes before applied. Short answers are in the faq.

Keep reading