Skip to main content
Traditional KYC and compliance verification requires every platform to collect, store, and protect sensitive personal data — creating liability, compliance risk, and repeated friction for users. AIR Kit lets you issue a KYC credential once, and any partner platform can verify it via zero-knowledge proof without ever accessing raw PII.

What You Can Build

  • KYC credentials — Issue a verified identity attestation the moment a user passes KYC; partner platforms accept it without re-running the check
  • Income / accreditation proofs — Issue an “Accredited Investor” credential backed by income verification, provable via ZK proof
  • Compliance gating — Gate DeFi pools, financial products, or high-value transactions behind credential checks
  • Age verification — Prove a user is 18+ or 21+ without revealing their birthdate to the verifier
  • Cross-border compliance — One credential, accepted at every partner service in the ecosystem

Architecture

KYC Attestation

{
  "title": "KYC Attestation",
  "description": "Identity verification attestation — no raw PII included",
  "properties": {
    "kycLevel": {
      "type": "string",
      "enum": ["basic", "enhanced", "institutional"],
      "description": "Level of KYC verification completed"
    },
    "kycProvider": {
      "type": "string",
      "description": "Name of the KYC provider (e.g. 'Jumio', 'Onfido')"
    },
    "verifiedAt": {
      "type": "string",
      "format": "date-time"
    },
    "countryCode": {
      "type": "string",
      "description": "ISO 3166-1 alpha-2 country code"
    },
    "isOver18": {
      "type": "boolean"
    },
    "isOver21": {
      "type": "boolean"
    }
  },
  "required": ["kycLevel", "kycProvider", "verifiedAt", "isOver18"]
}
Never include raw PII — full name, passport number, date of birth, address — in credentialSubject. Store only attestations and derived facts. ZK proofs let verifiers confirm isOver18 === true without seeing any underlying data.

Implementation

Step 1 — Issue KYC credential from your KYC webhook

// kyc-webhook.js  — called when your KYC provider sends a completion event
const { getPartnerJwt } = require('./lib/jwt');

async function issueKycCredential({ userEmail, kycResult }) {
  if (kycResult.status !== 'APPROVED') return; // only issue on success

  const token = await getPartnerJwt(userEmail);

  const res = await fetch('https://api.sandbox.mocachain.org/v1/credentials/issue-on-behalf', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-partner-auth': token,
    },
    body: JSON.stringify({
      issuerDid: process.env.ISSUER_DID,
      credentialId: process.env.KYC_CREDENTIAL_ID,
      credentialSubject: {
        kycLevel: kycResult.level,             // "basic" | "enhanced" | "institutional"
        kycProvider: 'YourKYCProvider',
        verifiedAt: new Date().toISOString(),
        countryCode: kycResult.countryCode,    // e.g. "US", "GB"
        isOver18: kycResult.age >= 18,
        isOver21: kycResult.age >= 21,
      },
      onDuplicate: 'revoke', // re-issue if KYC level upgrades
    }),
  });

  if (!res.ok) throw new Error(`KYC credential issuance failed: ${res.status}`);
  return res.json();
}

// Express webhook endpoint
app.post('/webhooks/kyc', async (req, res) => {
  const { userEmail, kycResult } = req.body;
  await issueKycCredential({ userEmail, kycResult });
  res.json({ ok: true });
});

Step 2 — Gate financial products with credential verification

// fintech-gate.js  (frontend)
import { AirService } from '@mocanetwork/airkit';

import { AirService, BUILD_ENV } from "@mocanetwork/airkit";

const airService = new AirService({ partnerId: process.env.PARTNER_ID });
await airService.init({ buildEnv: BUILD_ENV.SANDBOX });

async function requireKyc() {
  const result = await airService.verifyCredential({
    programId: process.env.KYC_VERIFY_PROGRAM_ID,
    // Verifier program rule: kycLevel is "enhanced" or "institutional"
  });

  if (result.status !== 'COMPLIANT') {
    throw new Error('KYC_REQUIRED');
  }
  return result;
}

// Gate a high-value transaction
try {
  await requireKyc();
  proceedWithTransaction();
} catch (err) {
  if (err.message === 'KYC_REQUIRED') showKycOnboarding();
}

Step 3 — Age verification gate (no birthdate exposed)

// age-gate.js  (frontend)
async function requireAgeVerification() {
  const result = await airService.verifyCredential({
    programId: process.env.AGE_18_VERIFY_PROGRAM_ID,
    // Program checks: isOver18 === true  — verifier never sees the actual age/DOB
  });
  return result.status === 'COMPLIANT';
}

Privacy Guarantee

The ZK proof flow means the verifier receives only a boolean result. No raw KYC data, no PII, no liability for the verifier to store sensitive documents.
What the Verifier SeesWhat Stays Private
COMPLIANT / NON_COMPLIANTFull name, passport / ID number
Credential expiry dateDate of birth
KYC level (basic / enhanced)Residential address
Issuer DIDIncome figures, net worth
isOver18: trueActual age

Examples

Next Steps