Stellar
How to read Human ID SBTs on Stellar
Users can opt to receive SBTs on Stellar. See Using Human ID with Stellar for user-facing instructions.
Off-chain
Use the Stellar SDK to simulate a read transaction to get the user's SBT.
import {
Horizon,
rpc,
TransactionBuilder,
Networks,
Contract,
scValToNative,
nativeToScVal,
} from '@stellar/stellar-sdk'
/// Types ///
type StellarSbt = {
action_nullifier: bigint,
circuit_id: bigint,
expiry: bigint,
id: bigint,
minter: string, // Stellar address
public_values: Array<bigint>,
recipient: string, // Stellar address
revoked: boolean
}
type StellarSbtStatus = 'valid' | 'expired' | 'revoked' | 'none'
type GetStellarSBTRetVal = {
sbt?: StellarSbt
status: StellarSbtStatus
}
/// Constants ///
const horizonServerUrl = 'https://horizon.stellar.org'
const sorobanRpcUrl = 'https://mainnet.sorobanrpc.com'
const stellarSBTContractAddress = 'CCNTHEVSWNDOQAMXXHFOLQIXWUINUPTJIM6AXFSKODNVXWA4N7XV3AI5'
/// Get SBT ///
async function getStellarSBTByAddress(
address: string,
circuitId: string
): Promise<GetStellarSBTRetVal | null> {
const sorobanServer = new rpc.Server(sorobanRpcUrl)
const userAccount = await sorobanServer.getAccount(address)
const contract = new Contract(stellarSBTContractAddress)
const operation = contract.call(
'get_sbt',
nativeToScVal(address, { type: 'address' }),
nativeToScVal(circuitId, { type: 'u256' })
)
const transaction = new TransactionBuilder(userAccount, {
Networks.PUBLIC,
fee: '100',
})
.addOperation(operation)
.setTimeout(60)
.build()
const simulationResponse = await sorobanServer.simulateTransaction(transaction)
const parsed = rpc.parseRawSimulation(simulationResponse)
// Happy path. User has the desired SBT.
if (rpc.Api.isSimulationSuccess(simulationResponse)) {
const _parsed = parsed as rpc.Api.SimulateTransactionSuccessResponse
if (!_parsed.result) {
throw new Error('Unexpected: Could not get "result" field from parsed Stellar transaction simulation for SBT query')
}
const sbt = scValToNative(_parsed.result?.retval)
return {
sbt,
status: 'valid'
}
}
// Error cases. User does not have the SBT.
else if (rpc.Api.isSimulationError(simulationResponse)) {
const _parsed = parsed as rpc.Api.SimulateTransactionErrorResponse
// Error code 1 is "SBT not found"
if (_parsed.error.includes('HostError: Error(Contract, #1)')) {
return { status: 'none' }
}
// Error code 5 is "SBT revoked"
if (_parsed.error.includes('HostError: Error(Contract, #5)')) {
return { status: 'revoked' }
}
// Error code 6 is "SBT expired"
if (_parsed.error.includes('HostError: Error(Contract, #6)')) {
return { status: 'expired' }
}
throw new Error(`Stellar transaction simulation for SBT query failed: ${_parsed.error}`)
} else {
throw new Error('Unexpected: Stellar transaction simulation for SBT query returned an unexpected response')
}
}
On-chain (Soroban)
1. Fetch the wasm for the Human ID SBT contract.
2. Incorporate into your Soroban contract.
This example contract has a get_sbt function that simply wraps the function from the SBT contract.
Check out the full Human ID SBT contract here.
For the list of circuit IDs, see here.
Last updated