Overview
This guide provides comprehensive examples for integrating with the ZKScore Identity SBT contract. It covers Web3 integration, error handling, best practices, and real-world use cases.Always test your integration on testnet before deploying to mainnet. Use the testnet contract addresses provided in the Contract Overview section.
Web3 Integration
Basic Setup
import { ethers } from 'ethers';
// Contract configuration
const CONTRACT_ADDRESS = '0x1234567890123456789012345678901234567890';
const CONTRACT_ABI = [
// ... ABI definitions
];
// Initialize provider and contract
const provider = new ethers.providers.JsonRpcProvider('https://mainnet.infura.io/v3/YOUR_KEY');
const contract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, provider);
// Initialize with signer for transactions
const signer = provider.getSigner();
const contractWithSigner = contract.connect(signer);
from web3 import Web3
# Contract configuration
CONTRACT_ADDRESS = '0x1234567890123456789012345678901234567890'
CONTRACT_ABI = [
# ... ABI definitions
]
# Initialize provider and contract
w3 = Web3(Web3.HTTPProvider('https://mainnet.infura.io/v3/YOUR_KEY'))
contract = w3.eth.contract(address=CONTRACT_ADDRESS, abi=CONTRACT_ABI)
# Initialize with account for transactions
account = w3.eth.account.from_key('YOUR_PRIVATE_KEY')
Contract Interaction Class
class IdentitySBTIntegration {
constructor(provider, contractAddress, abi) {
this.provider = provider;
this.contract = new ethers.Contract(contractAddress, abi, provider);
this.contractWithSigner = null;
}
// Set signer for transactions
setSigner(signer) {
this.contractWithSigner = this.contract.connect(signer);
}
// Mint new identity
async mintIdentity(to, name, metadataURI) {
if (!this.contractWithSigner) {
throw new Error('Signer not set');
}
try {
const tx = await this.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 {
success: true,
tokenId: tokenId.toString(),
transactionHash: receipt.transactionHash,
blockNumber: receipt.blockNumber
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
// Activate identity
async activateIdentity(tokenId) {
if (!this.contractWithSigner) {
throw new Error('Signer not set');
}
try {
const tx = await this.contractWithSigner.activate(tokenId);
const receipt = await tx.wait();
return {
success: true,
transactionHash: receipt.transactionHash,
blockNumber: receipt.blockNumber
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
// Get identity information
async getIdentityInfo(tokenId) {
try {
const [owner, isActivated, tokenURI] = await Promise.all([
this.contract.ownerOf(tokenId),
this.contract.isActivated(tokenId),
this.contract.tokenURI(tokenId)
]);
return {
tokenId: tokenId.toString(),
owner,
isActivated,
tokenURI
};
} catch (error) {
return {
error: error.message
};
}
}
// Get user's identities
async getUserIdentities(address) {
try {
const balance = await this.contract.balanceOf(address);
const identities = [];
for (let i = 0; i < balance.toNumber(); i++) {
const tokenId = await this.contract.tokenOfOwnerByIndex(address, i);
const info = await this.getIdentityInfo(tokenId);
identities.push(info);
}
return identities;
} catch (error) {
return {
error: error.message
};
}
}
}
// Usage
const integration = new IdentitySBTIntegration(
provider,
'0x1234567890123456789012345678901234567890',
IDENTITY_SBT_ABI
);
integration.setSigner(signer);
class IdentitySBTIntegration:
def __init__(self, w3, contract_address, abi):
self.w3 = w3
self.contract = w3.eth.contract(address=contract_address, abi=abi)
self.account = None
# Set account for transactions
def set_account(self, private_key):
self.account = self.w3.eth.account.from_key(private_key)
# Mint new identity
def mint_identity(self, to, name, metadata_uri):
if not self.account:
raise ValueError('Account not set')
try:
tx = self.contract.functions.mint(to, name, metadata_uri).buildTransaction({
'from': self.account.address,
'gas': 200000,
'gasPrice': self.w3.eth.gas_price,
'nonce': self.w3.eth.get_transaction_count(self.account.address)
})
signed_tx = self.w3.eth.account.sign_transaction(tx, self.account.key)
tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction)
receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
# Get token ID from event
event = self.contract.events.IdentityMinted().processReceipt(receipt)[0]
token_id = event['args']['tokenId']
return {
'success': True,
'tokenId': str(token_id),
'transactionHash': receipt.transactionHash.hex(),
'blockNumber': receipt.blockNumber
}
except Exception as error:
return {
'success': False,
'error': str(error)
}
# Activate identity
def activate_identity(self, token_id):
if not self.account:
raise ValueError('Account not set')
try:
tx = self.contract.functions.activate(token_id).buildTransaction({
'from': self.account.address,
'gas': 100000,
'gasPrice': self.w3.eth.gas_price,
'nonce': self.w3.eth.get_transaction_count(self.account.address)
})
signed_tx = self.w3.eth.account.sign_transaction(tx, self.account.key)
tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction)
receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
return {
'success': True,
'transactionHash': receipt.transactionHash.hex(),
'blockNumber': receipt.blockNumber
}
except Exception as error:
return {
'success': False,
'error': str(error)
}
# Get identity information
def get_identity_info(self, token_id):
try:
owner = self.contract.functions.ownerOf(token_id).call()
is_activated = self.contract.functions.isActivated(token_id).call()
token_uri = self.contract.functions.tokenURI(token_id).call()
return {
'tokenId': str(token_id),
'owner': owner,
'isActivated': is_activated,
'tokenURI': token_uri
}
except Exception as error:
return {
'error': str(error)
}
# Get user's identities
def get_user_identities(self, address):
try:
balance = self.contract.functions.balanceOf(address).call()
identities = []
for i in range(balance):
token_id = self.contract.functions.tokenOfOwnerByIndex(address, i).call()
info = self.get_identity_info(token_id)
identities.append(info)
return identities
except Exception as error:
return {
'error': str(error)
}
# Usage
integration = IdentitySBTIntegration(
w3,
'0x1234567890123456789012345678901234567890',
IDENTITY_SBT_ABI
)
integration.set_account('YOUR_PRIVATE_KEY')
Error Handling
Comprehensive Error Handling
class IdentitySBTErrorHandler {
static handleError(error) {
const errorMessage = error.message || error.toString();
// Common error patterns
if (errorMessage.includes('AccessControl')) {
return {
type: 'PERMISSION_ERROR',
message: 'Insufficient permissions to perform this action',
code: 'ACCESS_DENIED'
};
}
if (errorMessage.includes('Token is soulbound')) {
return {
type: 'SOULBOUND_ERROR',
message: 'Token is soulbound and cannot be transferred',
code: 'TOKEN_SOULBOUND'
};
}
if (errorMessage.includes('Token does not exist')) {
return {
type: 'NOT_FOUND_ERROR',
message: 'Token does not exist',
code: 'TOKEN_NOT_FOUND'
};
}
if (errorMessage.includes('Already activated')) {
return {
type: 'ALREADY_ACTIVATED_ERROR',
message: 'Token is already activated',
code: 'ALREADY_ACTIVATED'
};
}
if (errorMessage.includes('Not token owner')) {
return {
type: 'OWNERSHIP_ERROR',
message: 'Caller is not the owner of the token',
code: 'NOT_OWNER'
};
}
if (errorMessage.includes('name already exists')) {
return {
type: 'DUPLICATE_ERROR',
message: 'Name is already taken',
code: 'NAME_EXISTS'
};
}
if (errorMessage.includes('insufficient funds')) {
return {
type: 'INSUFFICIENT_FUNDS_ERROR',
message: 'Insufficient funds for transaction',
code: 'INSUFFICIENT_FUNDS'
};
}
if (errorMessage.includes('gas limit exceeded')) {
return {
type: 'GAS_ERROR',
message: 'Transaction gas limit exceeded',
code: 'GAS_LIMIT_EXCEEDED'
};
}
// Default error
return {
type: 'UNKNOWN_ERROR',
message: errorMessage,
code: 'UNKNOWN'
};
}
static async safeExecute(operation, retries = 3) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
return await operation();
} catch (error) {
const errorInfo = this.handleError(error);
if (attempt === retries) {
throw errorInfo;
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}
}
// Usage
try {
const result = await IdentitySBTErrorHandler.safeExecute(async () => {
return await contract.mint(to, name, metadataURI);
});
console.log('Success:', result);
} catch (error) {
console.error('Error:', error.message);
console.error('Code:', error.code);
}
class IdentitySBTErrorHandler:
@staticmethod
def handle_error(error):
error_message = str(error)
# Common error patterns
if 'AccessControl' in error_message:
return {
'type': 'PERMISSION_ERROR',
'message': 'Insufficient permissions to perform this action',
'code': 'ACCESS_DENIED'
}
if 'Token is soulbound' in error_message:
return {
'type': 'SOULBOUND_ERROR',
'message': 'Token is soulbound and cannot be transferred',
'code': 'TOKEN_SOULBOUND'
}
if 'Token does not exist' in error_message:
return {
'type': 'NOT_FOUND_ERROR',
'message': 'Token does not exist',
'code': 'TOKEN_NOT_FOUND'
}
if 'Already activated' in error_message:
return {
'type': 'ALREADY_ACTIVATED_ERROR',
'message': 'Token is already activated',
'code': 'ALREADY_ACTIVATED'
}
if 'Not token owner' in error_message:
return {
'type': 'OWNERSHIP_ERROR',
'message': 'Caller is not the owner of the token',
'code': 'NOT_OWNER'
}
if 'name already exists' in error_message:
return {
'type': 'DUPLICATE_ERROR',
'message': 'Name is already taken',
'code': 'NAME_EXISTS'
}
if 'insufficient funds' in error_message:
return {
'type': 'INSUFFICIENT_FUNDS_ERROR',
'message': 'Insufficient funds for transaction',
'code': 'INSUFFICIENT_FUNDS'
}
if 'gas limit exceeded' in error_message:
return {
'type': 'GAS_ERROR',
'message': 'Transaction gas limit exceeded',
'code': 'GAS_LIMIT_EXCEEDED'
}
# Default error
return {
'type': 'UNKNOWN_ERROR',
'message': error_message,
'code': 'UNKNOWN'
}
@staticmethod
async def safe_execute(operation, retries=3):
for attempt in range(1, retries + 1):
try:
return await operation()
except Exception as error:
error_info = IdentitySBTErrorHandler.handle_error(error)
if attempt == retries:
raise error_info
# Wait before retry
await asyncio.sleep(1 * attempt)
# Usage
try:
result = await IdentitySBTErrorHandler.safe_execute(
lambda: contract.functions.mint(to, name, metadata_uri).transact()
)
print('Success:', result)
except Exception as error:
print('Error:', error['message'])
print('Code:', error['code'])
Event Integration
Event Listener Setup
class IdentityEventManager {
constructor(contract) {
this.contract = contract;
this.listeners = new Map();
this.isListening = false;
}
startListening() {
if (this.isListening) return;
this.contract.on('IdentityMinted', (to, tokenId, name, event) => {
this.handleEvent('IdentityMinted', {
to,
tokenId: tokenId.toString(),
name,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash
});
});
this.contract.on('IdentityActivated', (tokenId, owner, event) => {
this.handleEvent('IdentityActivated', {
tokenId: tokenId.toString(),
owner,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash
});
});
this.contract.on('Transfer', (from, to, tokenId, event) => {
this.handleEvent('Transfer', {
from,
to,
tokenId: tokenId.toString(),
blockNumber: event.blockNumber,
transactionHash: event.transactionHash
});
});
this.isListening = true;
}
stopListening() {
this.contract.removeAllListeners();
this.isListening = false;
}
handleEvent(eventType, data) {
console.log(`Event: ${eventType}`, data);
if (this.listeners.has(eventType)) {
this.listeners.get(eventType).forEach(callback => {
try {
callback(data);
} catch (error) {
console.error(`Error in event listener for ${eventType}:`, error);
}
});
}
}
on(eventType, callback) {
if (!this.listeners.has(eventType)) {
this.listeners.set(eventType, []);
}
this.listeners.get(eventType).push(callback);
}
off(eventType, callback) {
if (this.listeners.has(eventType)) {
const callbacks = this.listeners.get(eventType);
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
}
}
}
// Usage
const eventManager = new IdentityEventManager(contract);
eventManager.startListening();
eventManager.on('IdentityMinted', (data) => {
console.log('New identity minted:', data);
// Update UI, send notifications, etc.
});
eventManager.on('IdentityActivated', (data) => {
console.log('Identity activated:', data);
// Update UI, send notifications, etc.
});
class IdentityEventManager:
def __init__(self, contract):
self.contract = contract
self.listeners = {}
self.is_listening = False
def start_listening(self):
if self.is_listening:
return
self.contract.events.IdentityMinted().on('data', self.handle_identity_minted)
self.contract.events.IdentityActivated().on('data', self.handle_identity_activated)
self.contract.events.Transfer().on('data', self.handle_transfer)
self.is_listening = True
def stop_listening(self):
self.contract.events.IdentityMinted().uninstall_filter()
self.contract.events.IdentityActivated().uninstall_filter()
self.contract.events.Transfer().uninstall_filter()
self.is_listening = False
def handle_identity_minted(self, event):
data = {
'to': event['args']['to'],
'tokenId': str(event['args']['tokenId']),
'name': event['args']['name'],
'blockNumber': event['blockNumber'],
'transactionHash': event['transactionHash'].hex()
}
self.handle_event('IdentityMinted', data)
def handle_identity_activated(self, event):
data = {
'tokenId': str(event['args']['tokenId']),
'owner': event['args']['owner'],
'blockNumber': event['blockNumber'],
'transactionHash': event['transactionHash'].hex()
}
self.handle_event('IdentityActivated', data)
def handle_transfer(self, event):
data = {
'from': event['args']['from'],
'to': event['args']['to'],
'tokenId': str(event['args']['tokenId']),
'blockNumber': event['blockNumber'],
'transactionHash': event['transactionHash'].hex()
}
self.handle_event('Transfer', data)
def handle_event(self, event_type, data):
print(f"Event: {event_type}", data)
if event_type in self.listeners:
for callback in self.listeners[event_type]:
try:
callback(data)
except Exception as error:
print(f"Error in event listener for {event_type}: {error}")
def on(self, event_type, callback):
if event_type not in self.listeners:
self.listeners[event_type] = []
self.listeners[event_type].append(callback)
def off(self, event_type, callback):
if event_type in self.listeners:
if callback in self.listeners[event_type]:
self.listeners[event_type].remove(callback)
# Usage
event_manager = IdentityEventManager(contract)
event_manager.start_listening()
def on_identity_minted(data):
print('New identity minted:', data)
# Update UI, send notifications, etc.
def on_identity_activated(data):
print('Identity activated:', data)
# Update UI, send notifications, etc.
event_manager.on('IdentityMinted', on_identity_minted)
event_manager.on('IdentityActivated', on_identity_activated)
Real-world Integration Examples
Complete Identity Management System
class IdentityManagementSystem {
constructor(provider, contractAddress, abi) {
this.provider = provider;
this.contract = new ethers.Contract(contractAddress, abi, provider);
this.contractWithSigner = null;
this.eventManager = new IdentityEventManager(this.contract);
}
setSigner(signer) {
this.contractWithSigner = this.contract.connect(signer);
}
// Complete identity creation flow
async createIdentity(name, metadataURI, autoActivate = false) {
try {
// Step 1: Mint identity
const mintResult = await this.mintIdentity(name, metadataURI);
if (!mintResult.success) {
return mintResult;
}
const tokenId = mintResult.tokenId;
// Step 2: Auto-activate if requested
if (autoActivate) {
const activateResult = await this.activateIdentity(tokenId);
if (!activateResult.success) {
return {
success: false,
error: `Identity minted but activation failed: ${activateResult.error}`,
tokenId
};
}
}
return {
success: true,
tokenId,
isActivated: autoActivate,
transactionHash: mintResult.transactionHash
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
// Get comprehensive identity data
async getIdentityData(tokenId) {
try {
const [owner, isActivated, tokenURI, balance] = await Promise.all([
this.contract.ownerOf(tokenId),
this.contract.isActivated(tokenId),
this.contract.tokenURI(tokenId),
this.contract.balanceOf(await this.contract.ownerOf(tokenId))
]);
return {
tokenId: tokenId.toString(),
owner,
isActivated,
tokenURI,
ownerBalance: balance.toString(),
isSoulbound: isActivated
};
} catch (error) {
return {
error: error.message
};
}
}
// Batch operations
async batchMintIdentities(identities) {
const results = [];
for (const identity of identities) {
try {
const result = await this.mintIdentity(identity.name, identity.metadataURI);
results.push({
name: identity.name,
success: result.success,
tokenId: result.tokenId,
error: result.error
});
} catch (error) {
results.push({
name: identity.name,
success: false,
error: error.message
});
}
}
return results;
}
// Start event monitoring
startEventMonitoring() {
this.eventManager.startListening();
}
// Stop event monitoring
stopEventMonitoring() {
this.eventManager.stopListening();
}
}
// Usage
const identitySystem = new IdentityManagementSystem(
provider,
'0x1234567890123456789012345678901234567890',
IDENTITY_SBT_ABI
);
identitySystem.setSigner(signer);
identitySystem.startEventMonitoring();
// Create identity
const result = await identitySystem.createIdentity(
'alice.zks',
'https://api.onzks.com/metadata/alice.zks',
true // Auto-activate
);
if (result.success) {
console.log('Identity created:', result.tokenId);
} else {
console.error('Failed to create identity:', result.error);
}
class IdentityManagementSystem:
def __init__(self, w3, contract_address, abi):
self.w3 = w3
self.contract = w3.eth.contract(address=contract_address, abi=abi)
self.account = None
self.event_manager = IdentityEventManager(self.contract)
def set_account(self, private_key):
self.account = self.w3.eth.account.from_key(private_key)
# Complete identity creation flow
def create_identity(self, name, metadata_uri, auto_activate=False):
try:
# Step 1: Mint identity
mint_result = self.mint_identity(name, metadata_uri)
if not mint_result['success']:
return mint_result
token_id = mint_result['tokenId']
# Step 2: Auto-activate if requested
if auto_activate:
activate_result = self.activate_identity(token_id)
if not activate_result['success']:
return {
'success': False,
'error': f"Identity minted but activation failed: {activate_result['error']}",
'tokenId': token_id
}
return {
'success': True,
'tokenId': token_id,
'isActivated': auto_activate,
'transactionHash': mint_result['transactionHash']
}
except Exception as error:
return {
'success': False,
'error': str(error)
}
# Get comprehensive identity data
def get_identity_data(self, token_id):
try:
owner = self.contract.functions.ownerOf(token_id).call()
is_activated = self.contract.functions.isActivated(token_id).call()
token_uri = self.contract.functions.tokenURI(token_id).call()
balance = self.contract.functions.balanceOf(owner).call()
return {
'tokenId': str(token_id),
'owner': owner,
'isActivated': is_activated,
'tokenURI': token_uri,
'ownerBalance': str(balance),
'isSoulbound': is_activated
}
except Exception as error:
return {
'error': str(error)
}
# Batch operations
def batch_mint_identities(self, identities):
results = []
for identity in identities:
try:
result = self.mint_identity(identity['name'], identity['metadataURI'])
results.append({
'name': identity['name'],
'success': result['success'],
'tokenId': result['tokenId'],
'error': result['error']
})
except Exception as error:
results.append({
'name': identity['name'],
'success': False,
'error': str(error)
})
return results
# Start event monitoring
def start_event_monitoring(self):
self.event_manager.start_listening()
# Stop event monitoring
def stop_event_monitoring(self):
self.event_manager.stop_listening()
# Usage
identity_system = IdentityManagementSystem(
w3,
'0x1234567890123456789012345678901234567890',
IDENTITY_SBT_ABI
)
identity_system.set_account('YOUR_PRIVATE_KEY')
identity_system.start_event_monitoring()
# Create identity
result = identity_system.create_identity(
'alice.zks',
'https://api.onzks.com/metadata/alice.zks',
True # Auto-activate
)
if result['success']:
print('Identity created:', result['tokenId'])
else:
print('Failed to create identity:', result['error'])
Best Practices
Security Considerations
- Private Key Management: Never hardcode private keys in your application
- Input Validation: Always validate inputs before sending transactions
- Gas Estimation: Estimate gas before sending transactions
- Error Handling: Implement comprehensive error handling
- Event Monitoring: Monitor events for state changes
Performance Optimization
- Batch Operations: Group multiple operations when possible
- Caching: Cache frequently accessed data
- Event Filtering: Use event filters to reduce data processing
- Gas Optimization: Use appropriate gas limits
- Connection Pooling: Reuse connections when possible
Testing
- Unit Tests: Test individual functions
- Integration Tests: Test complete workflows
- Error Testing: Test error conditions
- Performance Testing: Test under load
- Security Testing: Test for vulnerabilities
Related Documentation
- Contract Overview - Contract architecture and features
- Functions Reference - Complete function documentation
- Events Reference - Event documentation
- Security Guide - Security considerations
- Deployment Guide - Deployment instructions