On-chain verification and SBTs
An on-chain flow verifies proofs with a smart contract on Celo instead of Self’s servers. Each user who passes receives a soulbound token (SBT): a non-transferable token in their wallet that any contract, wallet, or explorer can read as proof they passed your flow.
You never write, deploy, or fund a contract yourself. The dashboard deploys one per flow, and Self pays all gas.
The contract
When you deploy an on-chain flow, Self deploys a dedicated SBT contract for it, one per environment:
| Environment | Network | Explorer |
|---|---|---|
| Test | Celo Sepolia | Blockscout |
| Live | Celo Mainnet | Celoscan |
Both contracts are created from the same configuration in a single Deploy click. Each one enforces your flow’s rules (age, country lists, OFAC, security level) directly on-chain, so the rules can’t drift from what verifiers see.
The Test and Live tabs show a contract card per environment with the contract address, the scope seed (the flow’s unique proof scope), and the deploy transaction, each linked to the block explorer.
Deploy status
Deploys usually finish in under a minute, and the dashboard polls automatically. The Configure tab shows one combined status:
| Status | Meaning |
|---|---|
| Deploying | At least one network’s contract is still deploying. |
| Deployed | Both contracts are live. |
| Partially deployed | One network succeeded, the other failed. Retry the failed one from its Test or Live tab. |
| Deploy failed | Both failed. Retry from the Test and Live tabs. |
Deploying and retrying require the owner or admin role; members see the status but must ask a manager to retry. Your flow’s configuration is saved either way, a failed deploy never loses work, and share links plus the Try this flow callout simply stay locked until the test contract is live.
The soulbound token
The SBT is an ERC-721 token named Self Verification SBT (symbol SELFSBT). The name and symbol are the same for every flow; the contract address is what identifies your flow.
Its rules are strict by design:
- Non-transferable. It can never be moved, sold, or approved to another address. There is no way to burn it.
- One per person. The proof’s nullifier is recorded on mint. A second verification attempt by the same person fails with “You have already verified your identity. Only one verification is allowed per person.”
- One per wallet. A wallet that already holds the flow’s SBT can’t receive another.
A rejected attempt (wrong document type, nationality not on your allowlist) doesn’t consume the person’s one verification, they can retry with a document that meets the rules.
What your users experience
The hosted page adds two wallet steps before the usual QR code:
- Connect a wallet: the wallet that will receive the SBT. MetaMask, Rainbow, WalletConnect-compatible wallets, and browser-extension wallets are supported.
- Sign a gas-free message: a signature that confirms this wallet as the recipient. It costs nothing and sends no transaction. Switching accounts mid-flow asks for a fresh signature.
Then the user scans the QR code with the Self app as usual. Self submits the mint transaction; the user never pays gas.
Configuration limits
Most rules translate to the contract one-to-one: minimum age, OFAC, security level, and country lists are all enforced on-chain. Two limits apply, checked by the dashboard before you deploy:
- Maximum age isn’t enforceable on-chain. A flow with a maximum-age rule can’t be deployed in on-chain mode.
- Country lists are capped: at most 40 excluded countries or 41 included countries. These are protocol limits and can’t be raised.
If a country allowlist is set, the contract checks the user’s disclosed nationality, exactly as in backend mode.
Reading results
Every surface you already use gains the on-chain details:
- Webhooks:
verification.completedincludesverification_mode: 'onchain', andtx_hashpluscontract_addresswhen a token was minted. See the event catalog. - API: the session detail response includes
verification_modeand anonchainobject with the contract address, scope seed, and network. - Activity log: each row shows the mint transaction, linked to the explorer, and the proof-request popover shows the contract address.
- On-chain: any contract can check
balanceOf(wallet)on your flow’s SBT contract to gate access.
A valid on-chain verification means the transaction confirmed and the token was minted. A verification the contract rejects (an already-verified person, a disallowed document, a nationality outside your allowlist) is invalid; infrastructure failures are error. Billing follows the same rules as off-chain: valid and invalid are charged, error is not.
Related
- Verification modes: choosing between backend and on-chain.
- Test vs. live: Celo Sepolia vs. Celo Mainnet mirrors the test/live split.
- Credits and usage: when on-chain verifications are charged.
Thanks for your feedback!