Bridge API
Solidus Bridge carries the status of a Solidus credential to the chains a holder chose, so an application there can check a condition without seeing the data behind it. On the testnets it also gates a USDC transfer on a credential: only an address that holds one can receive it. The API reads what the bridge observed. It does not sign, and it does not move anything.
Base URL: https://bridge.solidus.network
Network: testnet. The /v1/routes response says "network": "testnet". Everything here runs on
testnets and has no monetary value.
Conventions
- Read only. Every route is a
GET. Anyone may call them. The one exception isGET /v1/me/exports, which needs a Solidus session token (Authorization: Bearer ...). nullmeans the bridge did not measure it. It never means zero and it never means healthy. A heartbeat age ofnullis a chain the bridge has heard nothing from.signed,deliveredandappliedare observation times.signedrecords when the bridge first saw the signature threshold reached, so it is bounded below by the polling interval.deliveredandappliedrecord when the gateway first saw the applied event, after the destination block is final by that chain’s rule, so they trail the block by the finality wait. Do not compute a signing or delivery latency from them without that wait.queuedandcommittedare Solidus chain times.- Delivery and application are the same transaction, so
deliveredandappliedare equal. - Anyone may deliver a message. The signatures are the authority, not the sender. Read them from
GET /v1/attestations/{domain}/{seq}and submit them to the destination mirror. The message bytes come from the Solidus RPC methodsolidus_getBridgeMessages({"domain", "fromSeq", "limit"}athttps://rpc.solidus.network). Their keccak256 hash is themessageId. - Errors are
application/problem+jsonwithtype,title,status,codeanddetail.
curl https://bridge.solidus.network/v1/statusRoutes
GET /health
Answers {"status":"ok"} when the API process is up. It says nothing about the chains.
GET /v1/routes
The chains the bridge is configured for, read from the live configuration.
{
"network": "testnet",
"solidusDomain": 50002,
"chains": [
{ "key": "sepolia", "name": "Ethereum Sepolia", "vm": "evm", "domain": 11155111,
"chainId": 11155111, "explorer": "https://sepolia.etherscan.io",
"functions": ["credentials", "usdc"], "contracts": { "mirror": "0x...", "registry": "0x..." } }
]
}functions lists what each chain supports. contracts lists the deployed addresses for that chain.
GET /v1/status
What the bridge observed, per chain.
| Field | Meaning |
|---|---|
solidus | Whether the Solidus RPC is reachable, the latest committed height and when it was checked |
validators | signing of total validators, and the threshold a message needs |
chains[].heartbeatAgeSecs | Seconds since the bridge first saw the chain’s latest heartbeat applied, an observation made after the destination block is final. It is not the age a gate measures on chain (a gate adds its own maxAge to the heartbeat’s timestamp and compares with the block time). null if it has seen none |
chains[].nextDomainSeq | The sequence number the next message for this chain will carry |
chains[].sequenceGap | How many messages for this chain are queued but not yet applied |
supply | Escrow, synthetic and in-flight totals, their difference, an alert field and when it was checkedAt |
GET /v1/stats?range=24h
Counts over a window. range is 24h, 7d, 30d or all. An unknown range answers over 24h and
says so in the range field.
Fields: range, exports, messagesDelivered, medianDeliverySecs, sampleSize and a byChain
array. medianDeliverySecs is null when there is no sample, never 0, and sampleSize says how many
deliveries it covers.
GET /v1/messages?limit=10
The newest messages first. limit is clamped to 1 to 50 and defaults to 10.
{ "messages": [ { "messageId": "0x...", "domain": 1399811151, "chainKey": "solana",
"domainSeq": 1035, "kind": "heartbeat", "solidusHeight": 5655587, "state": "applied",
"timestamps": { "queued": "...", "committed": "...", "signed": "...", "delivered": "...", "applied": "..." },
"deliveryTx": "0x..." } ] }GET /v1/messages/{id}
One message by messageId. 404 if the bridge has not seen it.
GET /v1/attestations/{domain}/{seq}
The validator signatures for one message, ready to submit to a destination.
{ "domain": 1399811151, "domainSeq": 1035, "messageId": "0x...", "solidusHeight": 5655587,
"threshold": 3, "signatures": [ { "signer": "0x...", "signature": "0x..." } ] }domain and seq are integers, or the route answers 400. 404 when no signatures exist yet.
GET /v1/credentials/{exportId}
The status of one credential export as the bridge last read it from Solidus: exportId, domain,
chainKey, holder, issuerDidHash, credentialTypeHash, status (active, revoked, expired or
unbound), validUntil, lastMessageId and updatedAt. It follows the newest message queued on
Solidus, so it can say revoked before the destination applies it. To see whether the destination has,
look up the message named by lastMessageId with GET /v1/messages/{id}. 404 with
code: export_not_found for an unknown id.
GET /v1/me/exports
The exports of the caller’s credentials. Needs Authorization: Bearer <Solidus session token>.
GET /v1/quotes
A quote for a gated USDC transfer on the testnets. Query parameters:
| Parameter | Meaning |
|---|---|
route | <fromKey>:<toKey>, two different chain keys from /v1/routes, for example sepolia:fuji |
token | usdc |
amount | A positive integer in base units (6 decimals, so 1000000 is 1 USDC) |
fastFill | true to ask for a fast fill quote (optional) |
{ "lines": [
{ "id": "protocol_fee", "amount": "0", "token": "USDC", "reason": null },
{ "id": "circle_fee", "amount": "100", "token": "USDC", "reason": null },
{ "id": "destination_gas", "amount": "62650400", "token": "AVAX", "reason": null } ],
"receive": "999900", "fastFillAvailable": false, "estimatedSecs": null, "sampleSize": 2 }Every fee is its own line. A line the bridge cannot compute has amount: null and a reason, and
receive is null whenever any line that reduces it is null, so a total is never built on a guess.
estimatedSecs is null until enough comparable transfers exist.
GET /v1/transfers?address=...
The USDC transfers for one address: an EVM address or a 32-byte holder. address is required, or the
route answers 400 with code: bad_address.
GET /v1/transfers/{id}
One transfer. The id is <sourceTx>:<logIndex>, with the colon URL-encoded. Fields include kind,
originDomain, destinationDomain, sender, recipient, amount, fee, state, sourceTx,
destinationTx, messageHash and timestamps.
GET /v1/escrows/{hash}
The escrow of a gated USDC transfer, held on the destination until the recipient claims it or it is refunded.
| Field | Meaning |
|---|---|
hash, domain, chainKey | Which escrow, and on which chain |
finalRecipient, refundAddress | Who can claim it, and where a refund goes (null if none) |
amount, deadline | The amount in base units, and the deadline |
credentialTypeHash, issuerDidHash | The credential the escrow names, or null until it does |
state | held, released or refunded |
gateReason | The reason the destination gate would give right now: 1 no export, 2 not active, 3 issuer not accredited, 4 expired, 5 stale. null when the escrow does not name a credential yet |