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

How to export a credential issued by Verify to another chain, and what Solidus checks on the way

An export copies the status of one credential to one account on one destination chain, so a contract there can read it. This page follows an export from the holder's side and names each check. What a credential status bridge is says why a status has to travel, and check a condition without seeing the data shows how a contract reads it. Everything here runs on testnets.

Before you start

  • A credential issued by Verify. Verify exports only what it issued. Solidus refuses a revoked credential.
  • An account you control on the destination. The holder key is your wallet account. Sepolia, Fuji, Arc and Solana devnet accept credentials today.
  • A wallet that signs the consent. EVM wallets sign typed data. Solana wallets sign raw bytes.
  • Solidus Identity. It holds your DID key and proves the credential is yours.

The steps

  1. Choose a credential. The export page gets a one-time challenge from Verify and sends you to Solidus Identity. Identity signs a proof over it with your key and returns it after the # in the address, which a browser never sends to a server. The page calls the proof valid for five minutes.
  2. Choose a destination. The page lists chains that carry credentials. A chain with no mirror contract cannot be selected.
  3. Approve the consent in your wallet.
  4. Submit. The page posts to Verify, which submits to Solidus as the issuer. Your browser sends no Solidus transaction.

Verify checks, in order: the proof matches the challenge, the presenter is the credential's subject, this Verify issued the credential (issuer_not_served otherwise), the destination is registered, and the consent is valid. Solidus repeats the checks that matter and refuses a repeat export to the same holder and domain.

The request is POST /v1/bridge/exports on Verify, not on the read-only Bridge API. The body takes exactly these fields:

  • presentation and challenge: the proof from Identity, and the challenge it signed.
  • credentialId and domain: the credential, and the destination (the chain ID on EVM).
  • holder: 0x plus 64 hex characters: your address padded with zeros on EVM, your public key on Solana.
  • validUntil: when the destination stops honoring the export, in Unix seconds. Zero means none.
  • consentSignature and consentExpiry: your signature, and when it ends.

What you sign and why

The consent says: this credential, to this account, on this chain, until this time. On EVM your wallet signs EIP-712 typed data whose domain is SolidusBridge, version 1, bound to the destination's chain ID and the mirror contract. On Solana it signs bytes that bind the same values to the mirror program. Nothing else is signed.

{
  "domain": {
    "name": "SolidusBridge",
    "version": "1",
    "chainId": 11155111,
    "verifyingContract": "0x0a5a0d19206f436d67B7b87BF4c889d5b769517A"
  },
  "primaryType": "ExportConsent",
  "message": {
    "credentialId": "urn:solidus:credential:EXAMPLE",
    "domain": 11155111,
    "holder": "0x0000000000000000000000001111111111111111111111111111111111111111",
    "consentExpiry": "1900000000"
  }
}

This is exportConsentTypedData from @solidus-network/bridge 0.2.0, run offline with placeholders and without its types list. The page builds the signed data from the values it shows you.

The page sets the expiry 15 minutes ahead. Verify and Solidus both reject an expired consent and a signature that did not come from the holder key. Solidus's check is the one that binds. The destination and the mirror are inside the signed data, so a consent for one chain fails on another. Destination contracts never see it. The destination is inside what is signed explains the same idea for validator signatures.

Follow an export

Verify answers 202 with txHash and exportId. A 202 means Solidus accepted the transaction, not that the destination applied it. The export ID is a keccak256 hash of the credential ID, the domain and the holder, and exportId() in the npm package computes it.

GET /v1/credentials/{exportId} (API reference) returns the holder, the two hashes, status, validUntil and lastMessageId, among others. An ID the bridge has not read yet answers 404 with export_not_found. The status follows the newest message the bridge has read from Solidus, so check lastMessageId to see whether the destination applied it. The credentials page lists a signed-in holder's exports. Status shows delivery health per chain.

The status is one of four values:

  • active. A gate can pass.
  • revoked. The issuer revoked the credential. A revoked status is queued for every destination where the export was active, in the same Solidus block. Signing, delivery and apply follow.
  • expired. In the message format. A gate also reports expired once validUntil passes. The Solidus code writes only active, revoked and unbound.
  • unbound. The export was taken back.

What the destination learns

One status message carries an export ID, a hash of the issuer, a hash of the credential type, the holder key, a status, an expiry and an issuer-accredited flag, under an 18-byte header. It carries no DID, no credential ID, no claim and no document.

The hashes are guessable. The issuer hash is keccak256 of the issuer's DID, and the type hash is keccak256 of the type's name. Few issuers and types exist, so anyone can hash the likely ones and compare. The destination learns that this address holds this type of credential from this issuer, and the chain's history keeps it. That is why consent exists: you decide whether the link goes public.

Take it back

An unexport asks the issuer to mark the export unbound. The request is POST /v1/bridge/unexports on Verify, with presentation, challenge, credentialId, domain and holder. It needs no new consent, but it needs a fresh proof from Identity, so only the credential's subject can ask. Solidus accepts it only for an active export and queues an unbound status. A gate then reports not active. The mirror replaces its record, and the chain's log keeps the earlier event. You can export again to the same account with a new consent. The faq covers revocation, and security the mechanisms.

See it in one command

Ask the Bridge API for an export that does not exist:

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

Then list the chains you may export to, the ones whose functions include credentials:

curl -sL https://bridge.solidus.network/v1/routes \
  | jq -c '.chains[] | select(.functions | index("credentials")) | {key, domain, vm}'
# {"key":"sepolia","domain":11155111,"vm":"evm"}
# {"key":"fuji","domain":43113,"vm":"evm"}
# {"key":"arc","domain":5042002,"vm":"evm"}
# {"key":"solana","domain":1399811151,"vm":"svm"}

Both outputs are from 2026-10-08. All four are testnets.

Keep reading