Documentation Index Fetch the complete documentation index at: https://core.anylayer.org/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The ZKScore Identity SBT (Soulbound Token) is the core smart contract that manages ZKS identities on the blockchain. It implements the ERC-721 standard with soulbound token mechanics, ensuring that identities cannot be transferred once activated, making them truly soulbound to their owners.
The Identity SBT contract is deployed on multiple networks. Always check the latest deployment addresses in the Deployment section before interacting with the contract.
Contract Architecture
Core Components
The Identity SBT contract consists of several key components:
ERC-721 Base : Standard NFT functionality for metadata and ownership
Soulbound Mechanics : Prevents transfer after activation
Access Control : Role-based permissions for minting and management
Metadata Management : On-chain and off-chain metadata storage
Event System : Comprehensive event logging for indexing
Key Features
Soulbound Tokens : Once activated, tokens cannot be transferred
Metadata Flexibility : Support for both on-chain and off-chain metadata
Role-Based Access : Granular permissions for different operations
Gas Optimization : Efficient storage and operation patterns
Upgrade Safety : Immutable core logic with configurable parameters
Contract Addresses
Mainnet Deployments
Ethereum Mainnet
Polygon
Arbitrum
// Identity SBT Contract
address : 0x1234567890123456789012345678901234567890
// Proxy Contract (if using upgradeable pattern)
address : 0x2345678901234567890123456789012345678901
Testnet Deployments
Goerli Testnet
Mumbai Testnet
// Identity SBT Contract
address : 0x7890123456789012345678901234567890123456
// Proxy Contract
address : 0x8901234567890123456789012345678901234567
Contract Interface
Core Functions
// ERC-721 Standard Functions
function balanceOf ( address owner ) external view returns ( uint256 );
function ownerOf ( uint256 tokenId ) external view returns ( address );
function safeTransferFrom ( address from , address to , uint256 tokenId ) external ;
function transferFrom ( address from , address to , uint256 tokenId ) external ;
function approve ( address to , uint256 tokenId ) external ;
function getApproved ( uint256 tokenId ) external view returns ( address );
function setApprovalForAll ( address operator , bool approved ) external ;
function isApprovedForAll ( address owner , address operator ) external view returns ( bool );
// Soulbound Token Functions
function mint ( address to , string memory name , string memory metadataURI ) external returns ( uint256 );
function activate ( uint256 tokenId ) external ;
function isActivated ( uint256 tokenId ) external view returns ( bool );
function isSoulbound ( uint256 tokenId ) external view returns ( bool );
// Metadata Functions
function tokenURI ( uint256 tokenId ) external view returns ( string memory );
function setTokenURI ( uint256 tokenId , string memory newURI ) external ;
function name () external view returns ( string memory );
function symbol () external view returns ( string memory );
// Access Control Functions
function grantRole ( bytes32 role , address account ) external ;
function revokeRole ( bytes32 role , address account ) external ;
function hasRole ( bytes32 role , address account ) external view returns ( bool );
Events
// Standard ERC-721 Events
event Transfer ( address indexed from , address indexed to , uint256 indexed tokenId );
event Approval ( address indexed owner , address indexed approved , uint256 indexed tokenId );
event ApprovalForAll ( address indexed owner , address indexed operator , bool approved );
// Soulbound Token Events
event IdentityMinted ( address indexed to , uint256 indexed tokenId , string name );
event IdentityActivated ( uint256 indexed tokenId , address indexed owner );
event MetadataUpdated ( uint256 indexed tokenId , string newURI );
event SoulboundStatusChanged ( uint256 indexed tokenId , bool isSoulbound );
Soulbound Token Mechanics
What Makes It Soulbound
The Identity SBT implements soulbound token mechanics through several mechanisms:
Transfer Prevention : Once activated, the transferFrom and safeTransferFrom functions revert
Approval Blocking : Approval functions are disabled for activated tokens
Immutable Ownership : Token ownership cannot be changed after activation
Metadata Locking : Metadata becomes immutable after activation
Activation Process
// Before activation - token can be transferred
function activate ( uint256 tokenId ) external {
require ( ownerOf (tokenId) == msg.sender , "Not token owner" );
require ( ! isActivated (tokenId), "Already activated" );
_activate (tokenId);
emit IdentityActivated (tokenId, msg.sender );
}
// After activation - transfers are blocked
function _beforeTokenTransfer (
address from ,
address to ,
uint256 tokenId
) internal override {
require ( ! isActivated (tokenId), "Token is soulbound" );
super . _beforeTokenTransfer (from, to, tokenId);
}
{
"name" : "alice.zks" ,
"description" : "ZKScore Identity for alice.zks" ,
"image" : "https://api.onzks.com/identities/alice.zks/avatar" ,
"attributes" : [
{
"trait_type" : "Identity Type" ,
"value" : "ZKS ID"
},
{
"trait_type" : "Activation Status" ,
"value" : "Activated"
},
{
"trait_type" : "Soulbound" ,
"value" : true
},
{
"trait_type" : "Created At" ,
"value" : "2024-01-20T15:45:00Z"
}
]
}
The contract supports off-chain metadata through IPFS or centralized storage:
{
"name" : "alice.zks" ,
"description" : "Web3 developer and DeFi enthusiast" ,
"image" : "https://api.onzks.com/identities/alice.zks/avatar" ,
"external_url" : "https://onzks.com/identity/alice.zks" ,
"attributes" : [
{
"trait_type" : "Bio" ,
"value" : "Web3 developer and DeFi enthusiast"
},
{
"trait_type" : "Social" ,
"value" : {
"twitter" : "@alice" ,
"github" : "alice-dev"
}
},
{
"trait_type" : "ZKScore" ,
"value" : 1250
}
]
}
Access Control
Roles
The contract implements role-based access control with the following roles:
// Role definitions
bytes32 public constant MINTER_ROLE = keccak256 ( "MINTER_ROLE" );
bytes32 public constant ADMIN_ROLE = keccak256 ( "ADMIN_ROLE" );
bytes32 public constant METADATA_ROLE = keccak256 ( "METADATA_ROLE" );
// Role assignments
function grantRole ( bytes32 role , address account ) external onlyRole ( getRoleAdmin ( role ));
function revokeRole ( bytes32 role , address account ) external onlyRole ( getRoleAdmin ( role ));
function hasRole ( bytes32 role , address account ) external view returns ( bool );
Permission Matrix
Operation MINTER_ROLE ADMIN_ROLE METADATA_ROLE Token Owner Mint Identity ✅ ✅ ❌ ❌ Activate Identity ❌ ❌ ❌ ✅ Update Metadata ❌ ✅ ✅ ❌ Grant Roles ❌ ✅ ❌ ❌ Transfer (before activation) ❌ ❌ ❌ ✅ Transfer (after activation) ❌ ❌ ❌ ❌
Gas Optimization
Storage Patterns
The contract uses efficient storage patterns to minimize gas costs:
// Packed storage for frequently accessed data
struct IdentityData {
address owner; // 20 bytes
bool isActivated; // 1 byte
bool isSoulbound; // 1 byte
uint32 createdAt; // 4 bytes
uint32 activatedAt; // 4 bytes
// Total: 30 bytes (fits in one storage slot)
}
// Efficient metadata storage
mapping ( uint256 => string ) private _tokenURIs;
mapping ( uint256 => IdentityData) private _identities;
Gas Estimates
Operation Gas Cost Description Mint Identity ~150,000 Create new identity with metadata Activate Identity ~50,000 Make token soulbound Transfer (before activation) ~80,000 Standard ERC-721 transfer Transfer (after activation) ❌ Reverts - token is soulbound Update Metadata ~30,000 Update token URI Query Operations ~2,000-5,000 View functions
Contract Verification
Etherscan Verification
The contract is verified on Etherscan for transparency and security:
# Contract verification
https://etherscan.io/address/0x1234567890123456789012345678901234567890#code
# ABI available for integration
https://api.etherscan.io/api?module =contract & action = getabi & address = 0x1234567890123456789012345678901234567890
Source Code
The contract source code is available on GitHub:
# Repository
https://github.com/zkscore/contracts
# Identity SBT Contract
https://github.com/zkscore/contracts/blob/main/contracts/IdentitySBT.sol
# Tests
https://github.com/zkscore/contracts/blob/main/test/IdentitySBT.test.js
Integration Examples
Basic Contract Interaction
import { ethers } from 'ethers' ;
// Contract ABI (simplified)
const IDENTITY_SBT_ABI = [
"function mint(address to, string memory name, string memory metadataURI) external returns (uint256)" ,
"function activate(uint256 tokenId) external" ,
"function isActivated(uint256 tokenId) external view returns (bool)" ,
"function tokenURI(uint256 tokenId) external view returns (string memory)" ,
"event IdentityMinted(address indexed to, uint256 indexed tokenId, string name)" ,
"event IdentityActivated(uint256 indexed tokenId, address indexed owner)"
];
// Initialize contract
const provider = new ethers . providers . JsonRpcProvider ( 'https://mainnet.infura.io/v3/YOUR_KEY' );
const contract = new ethers . Contract (
'0x1234567890123456789012345678901234567890' ,
IDENTITY_SBT_ABI ,
provider
);
// Mint new identity
async function mintIdentity ( to , name , metadataURI ) {
const signer = provider . getSigner ();
const contractWithSigner = contract . connect ( signer );
const tx = await contractWithSigner . mint ( to , name , metadataURI );
const receipt = await tx . wait ();
// Get token ID from event
const event = receipt . events . find ( e => e . event === 'IdentityMinted' );
const tokenId = event . args . tokenId ;
return tokenId ;
}
// Activate identity
async function activateIdentity ( tokenId ) {
const signer = provider . getSigner ();
const contractWithSigner = contract . connect ( signer );
const tx = await contractWithSigner . activate ( tokenId );
await tx . wait ();
console . log ( 'Identity activated and made soulbound' );
}
Event Listening
// Listen for identity events
contract . on ( 'IdentityMinted' , ( to , tokenId , name , event ) => {
console . log ( `New identity minted: ${ name } (ID: ${ tokenId } ) to ${ to } ` );
});
contract . on ( 'IdentityActivated' , ( tokenId , owner , event ) => {
console . log ( `Identity ${ tokenId } activated by ${ owner } ` );
});
// Filter events by specific criteria
const filter = contract . filters . IdentityMinted ( null , null , 'alice.zks' );
contract . on ( filter , ( to , tokenId , name , event ) => {
console . log ( `Alice's identity minted with ID: ${ tokenId } ` );
});
Security Considerations
Audit Results
The Identity SBT contract has undergone comprehensive security audits:
Audit Firm : ConsenSys Diligence
Audit Date : January 2024
Severity : No critical or high-severity issues found
Report : Available here
Security Features
Reentrancy Protection : All external calls are protected
Access Control : Role-based permissions prevent unauthorized access
Input Validation : All inputs are validated before processing
Gas Limit Protection : Functions have reasonable gas limits
Upgrade Safety : Core logic is immutable, only parameters are configurable
Known Limitations
Metadata Immutability : Once set, metadata cannot be changed
Transfer Irreversibility : Once activated, tokens cannot be transferred
Gas Costs : Complex operations may have higher gas costs
Network Dependency : Contract behavior depends on network state
Best Practices
For Developers
Always Check Activation Status : Verify if a token is activated before attempting transfers
Handle Events Properly : Listen for events to track state changes
Validate Inputs : Ensure all inputs are valid before calling contract functions
Use Proper Error Handling : Implement comprehensive error handling for all operations
For Users
Understand Soulbound Nature : Once activated, tokens cannot be transferred
Verify Metadata : Check token metadata before activation
Secure Private Keys : Keep private keys secure as tokens cannot be recovered
Test on Testnet : Always test operations on testnet before mainnet