Skip to main content

KYC Integration Guide

This guide walks you through integrating Know Your Customer (KYC) providers with the Mantle RWA SDK to verify investor eligibility and maintain regulatory compliance.

Overview​

The Mantle RWA SDK supports multiple KYC providers through a unified interface, allowing you to:

  • Verify investor identities
  • Determine accreditation status
  • Maintain compliance with securities regulations
  • Sync verification results to on-chain registry

Supported Providers​

Persona​

  • Best for: US-focused projects requiring accredited investor verification
  • Features: Identity verification, accreditation checks, ongoing monitoring
  • Pricing: Pay-per-verification model
  • Documentation: Persona Developer Docs

Synaps​

  • Best for: European projects requiring GDPR compliance
  • Features: Identity verification, AML screening, document verification
  • Pricing: Subscription-based with volume discounts
  • Documentation: Synaps API Docs

Jumio​

  • Best for: Global projects requiring comprehensive identity verification
  • Features: Document verification, biometric matching, fraud detection
  • Pricing: Enterprise pricing with custom rates
  • Documentation: Jumio Developer Portal

Integration Steps​

Step 1: Choose Your Provider​

Select a KYC provider based on your requirements:

import { RWAClient } from '@mantle-rwa/sdk';

const client = new RWAClient({
network: 'mantle-testnet',
privateKey: process.env.PRIVATE_KEY,
});

// Set your preferred KYC provider
client.kyc.setProvider('persona'); // or 'synaps' or 'jumio'

Step 2: Configure Provider Credentials​

Set up your provider API credentials:

# .env
PERSONA_API_KEY=your_persona_api_key
PERSONA_TEMPLATE_ID=your_template_id
PERSONA_ENVIRONMENT=sandbox # or production

SYNAPS_API_KEY=your_synaps_api_key
SYNAPS_CLIENT_ID=your_client_id

JUMIO_API_TOKEN=your_jumio_token
JUMIO_API_SECRET=your_jumio_secret
JUMIO_DATACENTER=US # or EU, SG

Step 3: Implement Verification Flow​

Basic Verification​

async function verifyInvestor(investorAddress: string) {
try {
// Initiate KYC verification
const session = await client.kyc.verifyInvestor(investorAddress);

console.log('Verification session created:', session.sessionId);
console.log('Verification URL:', session.verificationUrl);

// Return URL to investor for completion
return session.verificationUrl;
} catch (error) {
console.error('KYC verification failed:', error);
throw error;
}
}

Check Verification Status​

async function checkVerificationStatus(sessionId: string) {
const status = await client.kyc.checkStatus(sessionId);

switch (status.status) {
case 'pending':
console.log('Verification in progress...');
break;
case 'completed':
console.log('Verification completed successfully');
const result = await client.kyc.getVerificationResult(sessionId);
return result;
case 'failed':
console.log('Verification failed:', status.reason);
break;
}
}

Update On-Chain Registry​

async function syncToRegistry(
registryAddress: string,
investorAddress: string,
verificationResult: VerificationResult
) {
try {
const txResult = await client.kyc.updateRegistry(
registryAddress,
investorAddress,
verificationResult
);

console.log('Registry updated:', txResult.hash);
return txResult;
} catch (error) {
console.error('Registry update failed:', error);
throw error;
}
}

Provider-Specific Configuration​

Persona Integration​

// Configure Persona-specific options
client.kyc.setProvider('persona', {
apiKey: process.env.PERSONA_API_KEY,
templateId: process.env.PERSONA_TEMPLATE_ID,
environment: process.env.PERSONA_ENVIRONMENT,

// Verification requirements
requirements: {
identity: true,
accreditation: true,
address: true,
tax: false,
},

// Webhook configuration
webhookUrl: 'https://your-app.com/webhooks/persona',
});

// Custom verification flow
async function personaVerification(investorAddress: string) {
const session = await client.kyc.verifyInvestor(investorAddress, {
// Persona-specific options
referenceId: `investor_${investorAddress}`,
fields: {
nameFirst: 'John',
nameLast: 'Doe',
emailAddress: 'john.doe@example.com',
},
});

return session;
}

Synaps Integration​

// Configure Synaps-specific options
client.kyc.setProvider('synaps', {
apiKey: process.env.SYNAPS_API_KEY,
clientId: process.env.SYNAPS_CLIENT_ID,

// Verification steps
steps: [
'identity',
'document',
'liveness',
'aml',
],

// GDPR compliance
gdprCompliant: true,
dataRetentionDays: 2555, // 7 years
});

// Custom verification flow
async function synapsVerification(investorAddress: string) {
const session = await client.kyc.verifyInvestor(investorAddress, {
// Synaps-specific options
alias: investorAddress,
locale: 'en',
tier: 3, // Full verification
});

return session;
}

Jumio Integration​

// Configure Jumio-specific options
client.kyc.setProvider('jumio', {
apiToken: process.env.JUMIO_API_TOKEN,
apiSecret: process.env.JUMIO_API_SECRET,
datacenter: process.env.JUMIO_DATACENTER,

// Verification workflow
workflow: 'identity_verification',

// Document types
acceptedDocuments: [
'PASSPORT',
'DRIVING_LICENSE',
'ID_CARD',
],
});

// Custom verification flow
async function jumioVerification(investorAddress: string) {
const session = await client.kyc.verifyInvestor(investorAddress, {
// Jumio-specific options
userReference: investorAddress,
workflowId: 'your_workflow_id',
locale: 'en',
});

return session;
}

Webhook Handling​

Set up webhooks to receive real-time verification updates:

Express.js Webhook Handler​

import express from 'express';
import { RWAClient } from '@mantle-rwa/sdk';

const app = express();
app.use(express.json());

const client = new RWAClient({
network: 'mantle-testnet',
privateKey: process.env.PRIVATE_KEY,
});

// Persona webhook
app.post('/webhooks/persona', async (req, res) => {
const { data } = req.body;

if (data.attributes.status === 'completed') {
const verificationResult = await client.kyc.getVerificationResult(
data.attributes['reference-id']
);

// Update on-chain registry
await client.kyc.updateRegistry(
process.env.KYC_REGISTRY_ADDRESS!,
data.attributes['reference-id'],
verificationResult
);
}

res.status(200).send('OK');
});

// Synaps webhook
app.post('/webhooks/synaps', async (req, res) => {
const { session_id, status } = req.body;

if (status === 'FINISHED') {
const verificationResult = await client.kyc.getVerificationResult(session_id);

// Update on-chain registry
await client.kyc.updateRegistry(
process.env.KYC_REGISTRY_ADDRESS!,
session_id,
verificationResult
);
}

res.status(200).send('OK');
});

app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});

Frontend Integration​

React Component Integration​

import { KYCFlow } from '@mantle-rwa/react';
import { useState } from 'react';

function InvestorOnboarding() {
const [kycStatus, setKycStatus] = useState<'pending' | 'completed' | 'failed'>('pending');

return (
<div className="onboarding">
<h2>Investor Verification</h2>

<KYCFlow
provider="persona"
onComplete={(kycData) => {
console.log('KYC completed:', kycData);
setKycStatus('completed');
}}
onError={(error) => {
console.error('KYC failed:', error);
setKycStatus('failed');
}}
requiredFields={['identity', 'accreditation']}
theme="light"
registryAddress={process.env.REACT_APP_KYC_REGISTRY_ADDRESS}
autoUpdateRegistry={true}
/>

{kycStatus === 'completed' && (
<div className="success">
✅ Verification completed! You can now purchase tokens.
</div>
)}
</div>
);
}

Custom Verification UI​

import { useRWAClient } from '@mantle-rwa/react';
import { useState } from 'react';

function CustomKYCFlow() {
const client = useRWAClient();
const [verificationUrl, setVerificationUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(false);

const startVerification = async () => {
setLoading(true);
try {
const session = await client.kyc.verifyInvestor(userAddress);
setVerificationUrl(session.verificationUrl);
} catch (error) {
console.error('Failed to start verification:', error);
} finally {
setLoading(false);
}
};

return (
<div className="kyc-flow">
{!verificationUrl ? (
<button onClick={startVerification} disabled={loading}>
{loading ? 'Starting verification...' : 'Start KYC Verification'}
</button>
) : (
<iframe
src={verificationUrl}
width="100%"
height="600px"
frameBorder="0"
/>
)}
</div>
);
}

Batch Operations​

For managing multiple investors efficiently:

async function batchVerifyInvestors(investors: Array<{
address: string;
email: string;
name: string;
}>) {
const verificationSessions = [];

// Start verification for all investors
for (const investor of investors) {
const session = await client.kyc.verifyInvestor(investor.address, {
emailAddress: investor.email,
nameFirst: investor.name.split(' ')[0],
nameLast: investor.name.split(' ')[1],
});

verificationSessions.push({
address: investor.address,
sessionId: session.sessionId,
});
}

return verificationSessions;
}

async function batchUpdateRegistry(
registryAddress: string,
verificationResults: Array<{
address: string;
result: VerificationResult;
}>
) {
// Batch update registry for gas efficiency
const txResult = await client.kyc.batchUpdateRegistry(
registryAddress,
verificationResults
);

console.log('Batch registry update:', txResult.hash);
return txResult;
}

Testing and Development​

Mock Provider for Testing​

import { MockKYCProvider } from '@mantle-rwa/sdk/testing';

// Use mock provider in development
if (process.env.NODE_ENV === 'development') {
client.kyc.setProvider(new MockKYCProvider({
// Always approve verifications
autoApprove: true,

// Simulate verification delay
verificationDelay: 2000,

// Default verification result
defaultResult: {
verified: true,
tier: 'Accredited',
identityHash: '0x1234...',
expiryDate: new Date('2025-12-31'),
},
}));
}

Testing Verification Flow​

import { describe, it, expect } from 'vitest';

describe('KYC Integration', () => {
it('should verify investor successfully', async () => {
const session = await client.kyc.verifyInvestor('0x1234...');
expect(session.sessionId).toBeDefined();
expect(session.verificationUrl).toContain('https://');
});

it('should update registry after verification', async () => {
const verificationResult = {
verified: true,
tier: 'Accredited' as const,
identityHash: '0x1234...',
expiryDate: new Date('2025-12-31'),
};

const txResult = await client.kyc.updateRegistry(
registryAddress,
investorAddress,
verificationResult
);

expect(txResult.status).toBe('success');
});
});

Security Best Practices​

API Key Management​

  • Store API keys in environment variables
  • Use different keys for development and production
  • Rotate keys regularly
  • Monitor API key usage

Data Privacy​

  • Never store raw identity documents
  • Use identity hashes for on-chain storage
  • Implement data retention policies
  • Comply with GDPR/CCPA requirements

Webhook Security​

  • Verify webhook signatures
  • Use HTTPS endpoints only
  • Implement rate limiting
  • Log all webhook events

Troubleshooting​

Common Issues​

1. Verification Fails

Error: Verification failed - insufficient documentation

Solution: Check provider requirements and ensure all required documents are provided.

2. Registry Update Fails

Error: Transaction reverted - investor not verified

Solution: Ensure verification is completed before updating registry.

3. Webhook Not Received

Error: Webhook timeout

Solution: Check webhook URL accessibility and implement proper error handling.

Provider-Specific Issues​

Persona

  • Check template configuration
  • Verify API key permissions
  • Review webhook endpoint setup

Synaps

  • Ensure client ID matches API key
  • Check step configuration
  • Verify GDPR compliance settings

Jumio

  • Confirm datacenter selection
  • Check workflow configuration
  • Verify document type acceptance

Production Checklist​

Before going live:

  • Production API keys configured
  • Webhook endpoints secured with HTTPS
  • Data retention policies implemented
  • Compliance requirements verified
  • Error handling and logging in place
  • Rate limiting configured
  • Monitoring and alerting set up
  • Privacy policy updated
  • Terms of service include KYC requirements

With proper KYC integration, your RWA platform will maintain regulatory compliance while providing a smooth investor onboarding experience.