Use Case Overview
KYC attestations enable protocols to verify user compliance without handling sensitive personal data. Specialized KYC providers perform identity verification and issue cryptographic attestations that protocols can verify instantly. Ideal For:- Fintech applications requiring regulatory compliance
- Token launches with jurisdiction restrictions
- DeFi protocols offering regulated products
- Platforms requiring identity verification
Implementation
Select Schema
Use the standard KYC schema or create a custom variant:Copy
// Standard KYC schema
const schemaUID = 'kyc-basic-v1';
// Definition: verified:bool,level:string,jurisdiction:string,timestamp:uint64
// Enhanced KYC schema for higher requirements
const enhancedSchemaUID = 'kyc-enhanced-v1';
// Definition: verified:bool,level:string,jurisdiction:string,riskScore:uint8,checkTypes:string,timestamp:uint64
// Example: Create a custom KYC schema
const customSchemaResult = await sdk.createSchema({
schemaName: `kyc-custom-${Date.now()}`,
schemaContent: 'verified:bool,level:string,jurisdiction:string,riskScore:uint8,pepCheck:bool,sanctionsCheck:bool,timestamp:uint64',
revocable: true
});
if (customSchemaResult.error) {
throw new Error(`Schema creation failed: ${customSchemaResult.error}`);
}
console.log('Custom KYC schema created:', customSchemaResult.data.schemaUID);
KYC Provider Integration
Implement the KYC provider workflow:Copy
import { StellarAttestSDK, SolanaAttestSDK } from '@attestprotocol/sdk';
class KYCProvider {
private sdk: StellarAttestSDK | SolanaAttestSDK;
constructor(sdk: StellarAttestSDK | SolanaAttestSDK) {
this.sdk = sdk;
}
Copy
async verifyAndAttest(userData: {
walletAddress: string;
documents: any[];
jurisdiction: string;
}) {
// Generate unique session ID for tracking
const sessionId = `kyc-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
try {
// Step 1: Run identity verification checks
const verificationResult = await this.performIdentityChecks(userData);
if (!verificationResult.passed) {
throw new Error(`Identity verification failed: ${verificationResult.reason}`);
}
// Step 2: Create on-chain attestation
const attestation = await this.issueKYCAttestation(userData, verificationResult, sessionId);
// Step 3: Store compliance records off-chain
await this.storeComplianceRecords({
sessionId,
attestationUID: attestation,
verificationData: verificationResult,
documents: userData.documents.map(d => d.hash),
timestamp: Date.now()
});
return {
success: true,
sessionId,
attestationUID: attestation,
level: verificationResult.level
};
} catch (error) {
await this.logError(sessionId, error);
throw error;
}
}
Copy
private async performIdentityChecks(userData: any) {
// Integrate with third-party KYC services:
// - Document verification APIs (e.g., Jumio, Onfido)
// - Biometric verification services
// - AML/PEP screening databases
// - Address verification services
// Example implementation placeholder
const documentCheck = await this.verifyDocuments(userData.documents);
const amlCheck = await this.performAMLScreening(userData);
const addressCheck = await this.verifyAddress(userData);
const allChecksPassed = documentCheck.valid && amlCheck.clear && addressCheck.verified;
return {
passed: allChecksPassed,
level: allChecksPassed ? 'enhanced' : 'basic',
score: this.calculateRiskScore({ documentCheck, amlCheck, addressCheck }),
checks: ['document', 'biometric', 'aml', 'address']
};
}
Copy
private async issueKYCAttestation(
userData: any,
verificationResult: any,
sessionId: string
): Promise<string> {
// Create structured attestation value
const attestationValue = [
`verified:true`,
`level:${verificationResult.level}`,
`jurisdiction:${userData.jurisdiction}`,
`timestamp:${Math.floor(Date.now() / 1000)}`
].join(',');
const attestation = await this.sdk.attest({
schemaUID: 'kyc-basic-v1',
subject: userData.walletAddress,
value: attestationValue,
reference: sessionId
});
if (attestation.error) {
throw new Error(`Attestation creation failed: ${attestation.error}`);
}
return attestation.data;
}
}
Protocol Integration
Verify KYC status in your dApp:Copy
class DeFiProtocol {
private sdk: StellarAttestSDK | SolanaAttestSDK;
constructor(sdk: StellarAttestSDK | SolanaAttestSDK) {
this.sdk = sdk;
}
Copy
async checkUserCompliance(userAddress: string): Promise<{
verified: boolean;
level?: string;
jurisdiction?: string;
expiresAt?: number;
}> {
try {
// Fetch KYC attestation from blockchain
const attestation = await this.sdk.fetchAttestation({
schemaUID: 'kyc-basic-v1',
subject: userAddress
});
// Handle fetch errors gracefully
if (attestation.error) {
console.warn(`Error fetching attestation: ${attestation.error}`);
return { verified: false };
}
// Check if attestation exists and is not revoked
if (!attestation.data || attestation.data.revoked) {
return { verified: false };
}
// Parse and validate attestation data
return this.validateAttestationData(attestation.data);
} catch (error) {
console.error('Compliance check failed:', error);
return { verified: false };
}
}
Copy
private validateAttestationData(attestationData: any) {
const data = this.parseKYCData(attestationData.value);
// Check attestation age (365 days maximum)
const attestationAge = (Date.now() / 1000) - data.timestamp;
const maxAge = 365 * 24 * 60 * 60; // 365 days in seconds
if (attestationAge > maxAge) {
console.log('KYC attestation has expired');
return { verified: false };
}
// Return validated compliance data
return {
verified: data.verified === 'true',
level: data.level,
jurisdiction: data.jurisdiction,
expiresAt: data.timestamp + maxAge
};
}
Copy
async requireKYC(userAddress: string, requiredLevel: string = 'basic') {
const compliance = await this.checkUserCompliance(userAddress);
// Check basic verification requirement
if (!compliance.verified) {
throw new Error('KYC verification required');
}
// Check level requirement for enhanced features
if (requiredLevel === 'enhanced' && compliance.level === 'basic') {
throw new Error('Enhanced KYC verification required');
}
return true;
}
Copy
private parseKYCData(value: string) {
const parts = value.split(',');
const data: any = {};
// Parse key:value pairs from attestation
parts.forEach(part => {
const [key, val] = part.split(':');
// Convert string booleans to actual booleans
data[key] = val === 'true' ? true : val === 'false' ? false : val;
});
return data;
}
}
Smart Contract Integration
For on-chain verification:Copy
// Stellar/Soroban example
use soroban_sdk::{contractimpl, Address, Env, String};
pub struct KYCGatedContract;
#[contractimpl]
impl KYCGatedContract {
pub fn restricted_function(env: Env, user: Address) -> Result<(), Error> {
// Verify KYC attestation exists
let attestation = env.invoke_contract(
&attestation_contract,
&Symbol::new(&env, "fetch_attestation"),
(&schema_uid, &user)
);
if !attestation.verified {
return Err(Error::KYCRequired);
}
// Execute restricted functionality
Ok(())
}
}
Advanced Patterns
Jurisdiction-Specific Compliance
Handle different regulatory requirements:Copy
async function checkJurisdictionCompliance(
userAddress: string,
requiredJurisdictions: string[]
): Promise<boolean> {
const attestation = await sdk.fetchAttestation({
schemaUID: 'kyc-basic-v1',
subject: userAddress
});
if (!attestation.data || attestation.data.revoked) {
return false;
}
const data = parseKYCData(attestation.data.value);
return requiredJurisdictions.includes(data.jurisdiction);
}
// Usage example
const allowedJurisdictions = ['US', 'UK', 'EU'];
const isCompliant = await checkJurisdictionCompliance(userAddress, allowedJurisdictions);
if (!isCompliant) {
console.log('User jurisdiction not supported for this product');
// Redirect to supported products or re-verification
await redirectToSupportedProducts(userAddress);
}
Tiered Access Control
Implement different access levels based on KYC:Copy
class TieredAccessControl {
async getUserAccessLevel(userAddress: string): Promise<{
tier: 'none' | 'basic' | 'enhanced' | 'institutional';
limits: any;
}> {
// Fetch enhanced KYC attestation
const kyc = await this.sdk.fetchAttestation({
schemaUID: 'kyc-enhanced-v1',
subject: userAddress
});
// Return no access if no valid KYC found
if (!kyc.data || kyc.data.revoked) {
return this.getNoAccessLimits();
}
const data = parseKYCData(kyc.data.value);
return this.mapKYCLevelToAccessTier(data.level);
}
Copy
private mapKYCLevelToAccessTier(kycLevel: string) {
switch(kycLevel) {
case 'basic':
return {
tier: 'basic' as const,
limits: {
dailyLimit: 1000, // $1,000 daily limit
singleTransactionLimit: 500, // $500 per transaction
allowedProducts: ['spot', 'savings']
}
};
case 'enhanced':
return {
tier: 'enhanced' as const,
limits: {
dailyLimit: 50000, // $50,000 daily limit
singleTransactionLimit: 10000, // $10,000 per transaction
allowedProducts: ['spot', 'savings', 'margin', 'derivatives']
}
};
case 'institutional':
return {
tier: 'institutional' as const,
limits: {
dailyLimit: -1, // Unlimited
singleTransactionLimit: -1, // Unlimited
allowedProducts: ['all']
}
};
default:
return this.getNoAccessLimits();
}
}
Copy
private getNoAccessLimits() {
return {
tier: 'none' as const,
limits: {
dailyLimit: 0,
singleTransactionLimit: 0,
allowedProducts: []
}
};
}
}
Privacy-Preserving KYC
Implement zero-knowledge KYC verification:Copy
// Schema for privacy-preserving KYC
const privacyKYCSchema = {
name: 'kyc-privacy-v1',
definition: 'hasValidKYC:bool,meetsAgeRequirement:bool,passedAML:bool,jurisdictionAllowed:bool'
};
// Provider issues privacy-preserving attestation
async function issuePrivacyKYC(userAddress: string, fullKYCData: any) {
// Perform full KYC verification
const kycResult = await performFullKYC(fullKYCData);
// Issue attestation with only boolean results
const attestation = await sdk.attest({
schemaUID: 'kyc-privacy-v1',
subject: userAddress,
value: `hasValidKYC:true,meetsAgeRequirement:${kycResult.age >= 18},passedAML:${kycResult.amlClear},jurisdictionAllowed:${kycResult.jurisdictionOK}`,
reference: `privacy-kyc-${Date.now()}`
});
return attestation;
}
Best Practices
Data Minimization
Only store essential data on-chain:Copy
// Good: Minimal on-chain data
const attestationValue = 'verified:true,level:enhanced,timestamp:1704067200';
// Avoid: Excessive on-chain data
const badValue = 'verified:true,firstName:John,lastName:Doe,ssn:123456789,address:...';
Attestation Lifecycle
Implement proper lifecycle management:Copy
class KYCLifecycleManager {
// Issue KYC with explicit expiration date
async issueKYCWithExpiry(userAddress: string, data: any) {
const expiryDays = 365; // 1 year validity
const expiryTimestamp = Date.now() + (expiryDays * 24 * 60 * 60 * 1000);
// Create structured attestation value with expiry
const attestationValue = [
`verified:true`,
`level:${data.level}`,
`jurisdiction:${data.jurisdiction}`,
`timestamp:${Date.now()}`,
`expiry:${expiryTimestamp}`
].join(',');
return await sdk.attest({
schemaUID: 'kyc-basic-v1',
subject: userAddress,
value: attestationValue,
reference: `kyc-${Date.now()}`
});
}
Copy
// Check expiration and trigger renewal when needed
async checkAndRenewKYC(userAddress: string) {
const current = await sdk.fetchAttestation({
schemaUID: 'kyc-basic-v1',
subject: userAddress
});
if (!current.data) {
console.log('No KYC attestation found for user');
return;
}
const data = parseKYCData(current.data.value);
const daysUntilExpiry = (data.expiry - Date.now()) / (24 * 60 * 60 * 1000);
// Trigger renewal 30 days before expiry
if (daysUntilExpiry < 30) {
console.log(`KYC expires in ${Math.floor(daysUntilExpiry)} days`);
await this.initiateReverification(userAddress);
}
}
Copy
private async initiateReverification(userAddress: string) {
// Send notification to user about upcoming expiry
await this.notifyUserOfExpiry(userAddress);
// Create re-verification request
await this.createReverificationRequest({
userAddress,
reason: 'renewal',
requestedAt: Date.now()
});
}
}
Compliance Records
Maintain proper audit trails:Copy
interface ComplianceRecord {
attestationId: string;
userAddress: string;
verificationMethod: string[];
documentHashes: string[];
checkResults: {
identity: boolean;
aml: boolean;
pep: boolean;
sanctions: boolean;
};
timestamp: number;
expiryDate: number;
}
// Store off-chain with reference on-chain
async function storeComplianceRecord(record: ComplianceRecord) {
// Store in secure, compliant database
await complianceDB.store(record);
// Only attestation reference goes on-chain
return record.attestationId;
}
Testing Strategy
Test Implementation
Copy
import { expect } from 'chai';
describe('KYC Integration', () => {
let provider: KYCProvider;
let protocol: DeFiProtocol;
let testUser: string;
beforeEach(async () => {
provider = new KYCProvider(sdk);
protocol = new DeFiProtocol(sdk);
testUser = 'test_user_address';
});
it('should issue KYC attestation for verified user', async () => {
const attestation = await provider.verifyAndAttest({
walletAddress: testUser,
documents: [{ type: 'passport', hash: '0x123...' }],
jurisdiction: 'US'
});
expect(attestation.data).to.exist;
expect(attestation.data.subject).to.equal(testUser);
});
it('should verify KYC compliance in protocol', async () => {
// First issue attestation
await provider.verifyAndAttest({
walletAddress: testUser,
documents: [{ type: 'passport', hash: '0x123...' }],
jurisdiction: 'US'
});
// Then verify in protocol
const compliance = await protocol.checkUserCompliance(testUser);
expect(compliance.verified).to.be.true;
expect(compliance.level).to.equal('enhanced');
});
it('should reject expired KYC', async () => {
// Mock expired attestation
const expiredAttestation = {
value: `verified:true,level:basic,timestamp:${Date.now() - 400 * 24 * 60 * 60 * 1000}` // 400 days old
};
// Should return not verified
const compliance = await protocol.checkUserCompliance(testUser);
expect(compliance.verified).to.be.false;
});
});
Common Issues
Issue: Attestation Not Found
Copy
// Problem: User has no KYC attestation
const attestation = await sdk.fetchAttestation({
schemaUID: 'kyc-basic-v1',
subject: userAddress
});
if (!attestation.data) {
// Solution: Redirect to KYC provider
redirectToKYCProvider(userAddress);
}
Issue: Expired Attestation
Copy
// Check and handle expiration
function isAttestationValid(attestationData: any): boolean {
const data = parseKYCData(attestationData.value);
const age = Date.now() - data.timestamp;
const maxAge = 365 * 24 * 60 * 60 * 1000; // 1 year
return age <= maxAge && !attestationData.revoked;
}
Issue: Wrong Jurisdiction
Copy
// Handle jurisdiction mismatches
async function handleJurisdictionMismatch(userAddress: string, required: string[]) {
const current = await sdk.fetchAttestation({
schemaUID: 'kyc-basic-v1',
subject: userAddress
});
if (current.data) {
const data = parseKYCData(current.data.value);
console.log(`User verified in ${data.jurisdiction}, but need ${required.join(' or ')}`);
// Suggest alternative products or guide to re-verification
return suggestAlternatives(data.jurisdiction);
}
}
Next Steps
- DAO Voting Integration - Implement governance with attestations
- Custom Schema Design - Create specialized KYC schemas
- Privacy Options - Implement zero-knowledge KYC
- Compliance Guide - Regulatory considerations