Skip to Content

SoulBound Tokens (Legacy)

⚠️

SBTs are being phased out in favor of attestations. For new integrations, use the API Reference or Attestation Protocols.

SoulBound Tokens (SBTs) are non-transferable tokens that were the original method for recording Individual Verifications on-chain. While SBTs are still issued for backward compatibility, attestations are now the primary verification record.

Overview

SBTs are:

  • Non-transferable: Cannot be sent to another address
  • Tied to identity: Represent a verified individual
  • Time-limited: Expire after 1 year and must be renewed

When to Use SBTs

Consider querying SBTs only if:

  • You have an existing integration that depends on SBT contracts
  • You need to verify historical verifications issued before the attestation migration

For all new integrations, use Attestation Protocols instead.


Contract Addresses

Optimism (EVM)

ContractAddress
Hub V30x2AA822e264F8cc31A2b9C22f39e5551241e94DfB

Optimism Sepolia (Testnet)

ContractAddress
Hub V30x71712Ac158C5b25519Aad43eB9f496ae16892211

Stellar (Soroban)

ContractAddress
SBT ContractCCNTHEVSWNDOQAMXXHFOLQIXWUINUPTJIM6AXFSKODNVXWA4N7XV3AI5

SBT Status Codes

All chains use consistent status codes when querying SBTs:

StatusDescription
validSBT exists and is active
expiredSBT has passed its expiration date (1 year from issuance)
revokedSBT has been revoked
noneNo SBT found for this address

Querying SBTs 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.com

2. 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) } }

Circuit IDs

Different verification types use different circuit IDs when querying SBTs:

Verification TypeCircuit ID
Government ID (KYC)0x729d660e1c02e4e419745e617d643f897a538673ccf1051e093bbfa58b0a120b
Phone0xbce052cf723dca06a21bd3cf838bc518931730fb3db7859fc9cc86f0d5483495
Biometrics0x0b5121226395e3b6c76eb8ddfb0bf2f2075e7f2c6956567e84b38a223c3a3d15
ePassport0xf2ce248b529343e105f7b3c16459da619281c5f81cf716d28f7df9f87667364d

Migration to Attestations

If you have an existing SBT integration, we recommend migrating to attestations:

  1. API users: No changes needed - the API will automatically use attestations
  2. Direct contract queries: Switch to Sign Protocol queries

For migration assistance, contact us .


Additional Resources

Last updated on