Skip to main content
Verify user identity and compliance status using AttestProtocol’s KYC schemas. This guide covers integration patterns for fintech dApps, token launches, and regulated DeFi protocols.

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:
// 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:
import { StellarAttestSDK, SolanaAttestSDK } from '@attestprotocol/sdk';

class KYCProvider {
  private sdk: StellarAttestSDK | SolanaAttestSDK;
  
  constructor(sdk: StellarAttestSDK | SolanaAttestSDK) {
    this.sdk = sdk;
  }
Main Verification Method
  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;
    }
  }
Identity Verification Logic
  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']
    };
  }
Attestation Creation
  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:
class DeFiProtocol {
  private sdk: StellarAttestSDK | SolanaAttestSDK;
  
  constructor(sdk: StellarAttestSDK | SolanaAttestSDK) {
    this.sdk = sdk;
  }
Compliance Verification
  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 };
    }
  }
Data Validation and Age Check
  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
    };
  }
Access Control Enforcement
  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;
  }
Data Parsing Utility
  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:
// 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:
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:
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);
  }
Access Tier Mapping
  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();
    }
  }
Default Limits Helper
  private getNoAccessLimits() {
    return {
      tier: 'none' as const,
      limits: {
        dailyLimit: 0,
        singleTransactionLimit: 0,
        allowedProducts: []
      }
    };
  }
}

Privacy-Preserving KYC

Implement zero-knowledge KYC verification:
// 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:
// 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:
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()}`
    });
  }
Renewal Management
  // 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);
    }
  }
Re-verification Process
  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:
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

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

// 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

// 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

// 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