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
Copy
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);
Contract Interaction Class
Copy
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);
Error Handling
Comprehensive Error Handling
Copy
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);
}
Event Integration
Event Listener Setup
Copy
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.
});
Real-world Integration Examples
Complete Identity Management System
Copy
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);
}
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