Supported Chains
Individual Verifications issue SoulBound Tokens (SBTs) on multiple blockchain networks, allowing users to verify once and prove their identity across different ecosystems.
Overview
| Chain | Gov ID | Phone | Biometrics | Clean Hands |
|---|---|---|---|---|
| Optimism | âś… | âś… | âś… | âś… |
| Stellar | âś… | âś… | - | - |
Contract Addresses (Optimism Mainnet)
| Contract | Address |
|---|---|
| Hub | 0x2AA822e264F8cc31A2b9C22f39e5551241e94DfB |
| Sign Protocol | 0x945C44803E92a3495C32be951052a62E45A5D964 |
Contract Address (Stellar)
| Contract | Address |
|---|---|
| SBT Contract | CCNTHEVSWNDOQAMXXHFOLQIXWUINUPTJIM6AXFSKODNVXWA4N7XV3AI5 |
How Multi-Chain SBTs Work
- Single Verification: User completes identity verification once (KYC, phone, biometrics, etc.)
- Chain Selection: User chooses which blockchain to receive their SBT
- SBT Issuance: A non-transferable SoulBound Token is minted on the selected chain
- Cross-dApp Usage: The SBT can be verified by any application on that chain
For enhanced privacy, users can pay verification fees from a different wallet than the one receiving the SBT. This prevents timing correlation attacks.
Optimism
Optimism is the primary chain for Individual Verification SBTs, with full support for all verification types.
Supported Verifications:
- Government ID (KYC)
- Phone
- Biometrics
- Proof of Clean Hands
For Optimism integration details, see the Developer Documentation .
Stellar
Individual Verifications support SBT issuance on Stellar via Soroban smart contracts.
Stellar SBT Contract: CCNTHEVSWNDOQAMXXHFOLQIXWUINUPTJIM6AXFSKODNVXWA4N7XV3AI5
Supported Verifications:
- Government ID (KYC)
- Phone
User Flow
- Visit the verification page for the desired verification type
- Complete the verification process
- Enter a Stellar address to receive the SBT
- The SBT is minted on Stellar
Off-Chain Verification (TypeScript)
Use the Stellar SDK to query a user’s SBT status:
import {
rpc,
TransactionBuilder,
Networks,
Contract,
scValToNative,
nativeToScVal,
} from '@stellar/stellar-sdk'
type StellarSbt = {
action_nullifier: bigint
circuit_id: bigint
expiry: bigint
id: bigint
minter: string
public_values: Array<bigint>
recipient: string
revoked: boolean
}
type StellarSbtStatus = 'valid' | 'expired' | 'revoked' | 'none'
const sorobanRpcUrl = 'https://mainnet.sorobanrpc.com'
const sbtContractAddress = 'CCNTHEVSWNDOQAMXXHFOLQIXWUINUPTJIM6AXFSKODNVXWA4N7XV3AI5'
async function getStellarSBT(
address: string,
circuitId: string
): Promise<{ sbt?: StellarSbt; status: StellarSbtStatus }> {
const sorobanServer = new rpc.Server(sorobanRpcUrl)
const userAccount = await sorobanServer.getAccount(address)
const contract = new Contract(sbtContractAddress)
const operation = contract.call(
'get_sbt',
nativeToScVal(address, { type: 'address' }),
nativeToScVal(circuitId, { type: 'u256' })
)
const transaction = new TransactionBuilder(userAccount, {
networkPassphrase: Networks.PUBLIC,
fee: '100',
})
.addOperation(operation)
.setTimeout(60)
.build()
const response = await sorobanServer.simulateTransaction(transaction)
if (rpc.Api.isSimulationSuccess(response)) {
const parsed = rpc.parseRawSimulation(response)
const sbt = scValToNative(parsed.result?.retval)
return { sbt, status: 'valid' }
}
const error = response.error
if (error?.includes('Error(Contract, #1)')) return { status: 'none' }
if (error?.includes('Error(Contract, #5)')) return { status: 'revoked' }
if (error?.includes('Error(Contract, #6)')) return { status: 'expired' }
throw new Error(`SBT query failed: ${error}`)
}On-Chain Verification (Soroban)
1. Fetch the SBT contract WASM:
soroban contract fetch \
--id CCNTHEVSWNDOQAMXXHFOLQIXWUINUPTJIM6AXFSKODNVXWA4N7XV3AI5 \
-o ./passport_sbt_contract.wasm \
--network mainnet \
--network-passphrase "Public Global Stellar Network ; September 2015" \
--rpc-url https://mainnet.sorobanrpc.com2. Import and use in your contract:
#![no_std]
use soroban_sdk::{contract, contractimpl, Env, Address, U256};
mod passport_sbt_contract {
soroban_sdk::contractimport!(
file = "../../passport_sbt_contract.wasm"
);
}
#[contract]
pub struct MyContract;
#[contractimpl]
impl MyContract {
pub fn get_sbt(
env: Env,
recipient: Address,
circuit_id: U256
) -> passport_sbt_contract::SBT {
let contract_addr = Address::from_str(
&env,
"CCNTHEVSWNDOQAMXXHFOLQIXWUINUPTJIM6AXFSKODNVXWA4N7XV3AI5"
);
let client = passport_sbt_contract::Client::new(&env, &contract_addr);
client.get_sbt(&recipient, &circuit_id)
}
}Stellar Resources
SBT Status Codes
All chains use consistent status codes when querying SBTs:
| Status | Description |
|---|---|
valid | SBT exists and is active |
expired | SBT has passed its expiration date (1 year from issuance) |
revoked | SBT has been revoked |
none | No SBT found for this address |
Circuit IDs
Different verification types use different circuit IDs. See the API Reference  for details.