Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
LABS
Guides

Setting Up On-Chain KYC/AML for Compliant Token Sales

This guide provides a technical walkthrough for integrating decentralized identity verification protocols into token sale smart contracts to enforce KYC/AML compliance while preserving user privacy.
Chainscore © 2026
introduction
TECHNICAL GUIDE

Setting Up On-Chain KYC/AML for Compliant Token Sales

This guide explains how to integrate on-chain KYC/AML verification into token sale smart contracts, ensuring regulatory compliance while preserving user privacy.

On-chain KYC/AML refers to embedding identity verification and anti-money laundering checks directly into blockchain transactions. Unlike traditional off-chain processes, this method uses zero-knowledge proofs (ZKPs) or attestation tokens to prove a user's verified status without revealing their personal data. For token sales, this is critical for projects targeting regulated markets or requiring compliance with the Financial Action Task Force (FATF) Travel Rule. The core components are a verification provider (like Veriff or Persona), a registry smart contract storing attestations, and a sale contract that checks this registry before allowing participation.

The typical architecture involves three steps. First, users complete verification with a KYC provider off-chain. Upon success, the provider's backend mints a non-transferable Soulbound Token (SBT) or a signed attestation to the user's wallet address. Second, a registry contract, such as an ERC-721 or ERC-1155 for SBTs, manages these credentials. Third, your token sale contract includes a modifier, like onlyVerifiedUsers, that checks the registry before processing a transaction. This creates a permissioned sale where only wallets with valid credentials can call the buyTokens function, enforced entirely on-chain.

Here is a basic Solidity example of a sale contract that checks an external verification registry. The VerificationRegistry is assumed to be an ERC-721 contract where token ownership proves KYC status.

solidity
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

contract CompliantTokenSale {
    IERC721 public verificationRegistry;
    uint256 public tokenPrice;

    constructor(address _registryAddress, uint256 _price) {
        verificationRegistry = IERC721(_registryAddress);
        tokenPrice = _price;
    }

    modifier onlyVerified(address user) {
        require(verificationRegistry.balanceOf(user) > 0, "KYC verification required");
        _;
    }

    function buyTokens() external payable onlyVerified(msg.sender) {
        require(msg.value >= tokenPrice, "Insufficient payment");
        // Mint or transfer tokens to msg.sender
    }
}

This pattern ensures compliance logic is immutable and transparent, but the verification data itself remains private.

For enhanced privacy, consider using zero-knowledge proofs. Platforms like Sismo or iden3 allow users to generate a ZK proof that they hold a valid credential from a trusted issuer. Your sale contract then verifies this proof on-chain. This approach minimizes on-chain data and improves user experience, as the proof can be reused across different dApps. However, it adds complexity in circuit design and proof verification gas costs. Always audit the credential issuance process, as the smart contract's security depends entirely on the trustworthiness of the attestation source.

Key implementation considerations include managing credential revocation and expiration. Your registry contract should allow the issuer to burn tokens or invalidate attestations if a user's status changes. Furthermore, design your sale to be compatible with multi-chain strategies; a credential issued on Polygon should be verifiable on your Ethereum-based sale. Using standards like EIP-712 for signed typed data or EIP-4973 for account-bound tokens can improve interoperability. Finally, remember that on-chain compliance is a rapidly evolving field—always consult legal experts to ensure your implementation meets specific jurisdictional requirements for your token sale.

prerequisites
PREREQUISITES AND SETUP

Setting Up On-Chain KYC/AML for Compliant Token Sales

Implementing compliant token sales requires integrating identity verification directly into your smart contract workflow. This guide covers the essential setup, from selecting a provider to writing the integration code.

Before writing any code, you must select a KYC/AML provider with on-chain verification capabilities. Leading providers like Coinbase Verifications, Persona, or Veriff offer APIs that return a verifiable credential or proof upon successful user verification. Your primary technical decision is the integration model: a centralized whitelist managed by your backend, or a decentralized model using verifiable credentials (VCs) or zero-knowledge proofs (ZKPs) stored on-chain. For most token sales, a hybrid approach is practical, where the provider's API verifies the user off-chain, and your backend signs a permission that is checked by the smart contract.

Your development environment needs specific tools. You will require a Node.js setup with Hardhat or Foundry for smart contract development and testing. Essential packages include an Ethers.js or Viem for blockchain interaction and the SDK from your chosen KYC provider. For on-chain proof verification, you may need libraries for handling EIP-712 signed typed data or JSON Web Tokens (JWTs). Set up a .env file to securely manage your provider API keys, blockchain RPC URLs, and the private key of the admin wallet that will authorize verified users.

The core of the setup is the verification smart contract. This contract maintains a mapping of approved addresses, often keyed by a hash of the user's submitted data. A basic function, restricted to an admin role, allows addresses to be added to a whitelist. A more advanced, gas-efficient pattern uses Merkle proofs. Here, your backend generates a Merkle tree of all verified addresses and publishes the root to the contract. Users then submit a Merkle proof with their address to claim eligibility. This minimizes on-chain transactions and costs.

Here is a foundational example of a whitelist contract using a simple mapping, written in Solidity for an ERC-20 sale:

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract CompliantSale {
    address public admin;
    mapping(address => bool) public isWhitelisted;

    constructor() {
        admin = msg.sender;
    }

    modifier onlyAdmin() {
        require(msg.sender == admin, "Not authorized");
        _;
    }

    function addToWhitelist(address _user) external onlyAdmin {
        isWhitelisted[_user] = true;
    }

    function purchaseTokens() external {
        require(isWhitelisted[msg.sender], "KYC verification required");
        // ... token sale logic
    }
}

This contract provides the basic access control layer. The addToWhitelist function would be called by your secure backend after confirming a user's KYC status with your provider.

Integrating the backend is the final step. Your server must: 1) Expose an endpoint for users to initiate KYC, 2) Redirect them to the provider's verification flow, 3) Listen for the provider's webhook confirming verification, 4) Upon success, call the smart contract's addToWhitelist function or generate a Merkle proof for the user. Ensure your backend validates the webhook's cryptographic signature to prevent spoofing. For production, implement rate limiting, monitor for failed transactions, and consider using a transaction relayer to pay gas fees on behalf of users for a seamless experience.

Thoroughly test the entire flow on a testnet like Sepolia or Goerli before mainnet deployment. Write tests that simulate the user journey: API call to your backend, mock KYC approval, and a final on-chain transaction. Audit the access control in your smart contract to prevent unauthorized whitelist modifications. Remember, while on-chain checks enforce rules, the legal responsibility for KYC/AML compliance remains with your project. The technical setup is a tool to operationalize your compliance policy, which should be drafted with legal counsel.

key-concepts-text
CORE CONCEPTS: VERIFIABLE CREDENTIALS AND ZK PROOFS

Setting Up On-Chain KYC/AML for Compliant Token Sales

This guide explains how to integrate privacy-preserving identity verification into token sales using verifiable credentials and zero-knowledge proofs, ensuring regulatory compliance without exposing user data.

On-chain KYC/AML (Know Your Customer/Anti-Money Laundering) is a critical requirement for compliant token sales, but traditional methods that store personal data on-chain create significant privacy and security risks. Verifiable Credentials (VCs) and Zero-Knowledge Proofs (ZKPs) offer a solution. A VC is a tamper-evident digital credential, like a passport attestation, issued by a trusted entity. A ZKP allows a user to prove they possess a valid VC—and that it satisfies specific rules like citizenship or accreditation—without revealing the underlying data. This creates a compliant, privacy-first framework for permissioned DeFi and token distributions.

The technical architecture involves three core roles: the Issuer (a regulated KYC provider), the Holder (the user/investor), and the Verifier (the smart contract for your token sale). The issuer signs a credential containing the user's verified attributes. The holder then generates a ZK-SNARK or STARK proof from this credential. This proof cryptographically demonstrates that the credential is valid, was issued by a trusted source, and contains attributes that pass the sale's eligibility rules, all without the verifier ever seeing the raw data. Protocols like Semaphore, Sismo, and zkPass provide frameworks for this flow.

To implement this, you first need to select or partner with a KYC provider that supports VC issuance, such as Circle's Verite or Ontology. Your smart contract, acting as the verifier, must include a function to validate the ZKP. For example, using the Semaphore protocol on Ethereum, you would verify a proof against an on-chain registry of approved issuers. The contract logic would check the proof's validity and then, if successful, mint tokens or add the user's nullifier to a registry to prevent double-spending their KYC status. This gatekeeping happens in a single, gas-efficient transaction.

A critical design consideration is the nullifier. This is a unique, deterministic hash generated from the user's identity and the specific application (your sale). It allows the contract to prevent a user from using the same KYC proof to participate multiple times, enforcing a one-person-one-vote or one-person-one-allocation rule. The nullifier is revealed with the ZKP, but it cannot be traced back to the original identity credential. This mechanism ensures compliance with investment limits without creating a public list of participant identities on the blockchain.

When designing the rules for your sale, you encode the eligibility criteria into the circuit that generates the ZK proof. This could require the user's credential to prove they are accredited, are from a permitted jurisdiction (e.g., not a sanctioned country), and are over 18 years old. The KYC issuer's public key is hardcoded into your verifier contract as a trusted root. This setup shifts the compliance burden to the issuer's off-chain process while giving you and your users cryptographic certainty that every participant has been properly vetted, creating a transparently compliant and private token sale.

ON-CHAIN KYC/AML SOLUTIONS

Comparison of Verification Protocols

Technical and operational differences between leading protocols for embedding compliance into token sales.

Feature / MetricChainlink Functions + KYC ProviderPolygon IDVerite by Circle

Verification Method

Off-chain API call to accredited provider

Zero-Knowledge Proof (ZK) identity

Verifiable Credentials (VC) standard

On-Chain Data Privacy

Gas Cost per Verification

$2-5 (estimated)

$0.5-1.5 (estimated)

$1-3 (estimated)

Settlement Finality

Minutes (depends on provider API)

Seconds (on-chain proof verification)

Seconds (on-chain VC verification)

Required User Action

Submit documents to provider

Generate ZK proof in wallet

Present VC from identity wallet

Revocable Compliance

Via provider dashboard

Via on-chain revocation registry

Via on-chain status list

Primary Use Case

High-assurance, traditional KYC lift

Privacy-preserving proof of eligibility

Portable, standardized DeFi credentials

Developer Integration

Smart contract oracle calls

ZK circuit libraries & SDKs

VC issuance/verification SDKs

step-1-credential-issuance
ON-CHAIN COMPLIANCE

Step 1: Integrate Credential Issuance

This guide explains how to integrate a credential issuance system to verify investor eligibility for compliant token sales, using on-chain attestations.

On-chain credential issuance transforms traditional KYC/AML verification into a reusable, privacy-preserving digital attestation. Instead of storing sensitive personal data on-chain, a trusted issuer (like a licensed KYC provider) creates a signed credential—often a Verifiable Credential (VC) or an Ethereum Attestation Service (EAS) attestation—that proves an investor has passed checks. This credential is linked to the investor's wallet address. The core components are the Issuer (the trusted entity), the Subject (the investor's wallet), and the Verifier (your smart contract). This model separates identity verification from transaction logic, enhancing both compliance and user privacy.

To implement this, you first select a credential standard. The Ethereum Attestation Service (EAS) is a popular choice for its schema-based, gas-efficient on-chain attestations. Alternatively, you can use Verifiable Credentials (VCs) with decentralized identifiers (DIDs) for a more portable, off-chain model. Your integration will involve two main steps: 1) Setting up an off-chain service to request and validate KYC data from users via a provider like Coinbase Verifications or Persona, and 2) Using that service to call the credential issuer's smart contract to mint an attestation for the user's verified wallet address.

Here is a simplified code example using EAS on the Sepolia testnet. First, your backend service would call the EAS contract after successful KYC verification.

javascript
import { EAS, Offchain, SchemaEncoder } from "@ethereum-attestation-service/eas-sdk";
import { ethers } from "ethers";

const EAS_CONTRACT_ADDRESS = "0xC2679fBD37d54388Ce493F1DB75320D236e1815e"; // Sepolia EAS
const SCHEMA_UID = "0xYOUR_SCHEMA_UID_HERE"; // Your defined schema for KYC status

const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const signer = new ethers.Wallet(ISSUER_PRIVATE_KEY, provider);
const eas = new EAS(EAS_CONTRACT_ADDRESS);
eas.connect(signer);

// Create an attestation for a verified user's wallet
const schemaEncoder = new SchemaEncoder("bool isKYCVerified,uint64 expiryTimestamp");
const encodedData = schemaEncoder.encodeData([
  { name: "isKYCVerified", value: true, type: "bool" },
  { name: "expiryTimestamp", value: Math.floor(Date.now() / 1000) + 90*24*60*60, type: "uint64" } // 90-day expiry
]);

const tx = await eas.attest({
  schema: SCHEMA_UID,
  data: {
    recipient: "0xVerifiedInvestorWalletAddress",
    expirationTime: 0n, // Use 0 for schema-defined expiry
    revocable: true,
    data: encodedData,
  },
});

After issuing the credential, your token sale smart contract must verify it before allowing participation. The contract checks for a valid, unrevoked, and unexpired attestation linked to the caller's address. Using EAS, this is a straightforward on-chain lookup. Your mint or purchase function would include a modifier or require statement that queries the EAS contract. This creates a gated transaction where only wallets with a valid KYC attestation can proceed. It's crucial to also handle credential revocation, which allows the issuer to immediately invalidate an attestation if a user's status changes, blocking further transactions.

Key considerations for production include choosing a reputable KYC provider, managing credential expiry and renewal flows, and deciding on attestation revocation logic. Costs involve gas fees for on-chain attestation minting and the service fees from your KYC provider. By implementing this pattern, you create a compliant foundation that can be extended to other credentials, such as proof of accreditation or jurisdictional whitelists, enabling more complex and regulated DeFi primitives.

step-2-smart-contract-gating
CORE LOGIC

Step 2: Design the Gating Smart Contract

This step involves writing the Solidity smart contract that will enforce compliance rules on-chain, acting as the gatekeeper for your token sale.

The gating contract's primary function is to check a user's verification status before allowing them to participate. It does not store KYC data itself but queries an external verification registry—a separate, potentially updatable contract that holds the attestations. This separation of concerns is a critical security and upgradeability pattern. Your main contract will need a state variable for the registry address and a function, typically isVerified(address user), that returns a boolean.

For a compliant sale, you need to define specific rules. Common gating logic includes checking that a user is: KYC verified, not on a sanctions list, and from a permitted jurisdiction. The contract should revert transactions from non-compliant addresses. You may also implement tiered access, where different verification levels (e.g., accredited investor status) unlock different contribution limits. Always use the Checks-Effects-Interactions pattern to prevent reentrancy when transferring funds.

Here is a minimal example of a gating function in a sale contract:

solidity
function contribute() external payable {
    require(verificationRegistry.isVerified(msg.sender), "KYC check failed");
    require(block.timestamp >= saleStartTime, "Sale not started");
    // ... additional checks and contribution logic
}

The verificationRegistry is an interface to the external contract. Using OpenZeppelin's Ownable or AccessControl for admin functions to update the registry address is a best practice for maintainability.

Security is paramount. Your contract must be pausable in case of a critical issue with the verification provider. Consider implementing a timelock for administrative actions like changing the registry. Thoroughly test all edge cases: what happens if the registry reverts? How do you handle a user whose verification expires mid-sale? Use tools like Foundry or Hardhat to simulate these scenarios before deployment.

Finally, the contract design must align with legal requirements. For example, some regulations require the permanent storage of transaction records. You might need to emit specific events (e.g., ContributorVerified) for audit trails. Consult legal counsel to ensure your on-chain logic correctly encodes the off-chain compliance obligations. Once deployed, the contract's immutable rules become the single source of truth for access control.

step-3-zk-proof-integration
PRIVACY-PRESERVING COMPLIANCE

Step 3: Integrate Zero-Knowledge Proof Verification

This step explains how to verify a user's KYC/AML status on-chain without exposing their personal data, using zero-knowledge proofs (ZKPs).

Zero-knowledge proof verification is the core privacy mechanism for compliant token sales. It allows the smart contract to confirm a user has passed KYC/AML checks without learning any details about their identity. This is achieved using a ZK-SNARK or ZK-STARK proof, generated off-chain by a trusted verifier. The proof cryptographically attests that a private input (the user's verified credentials) satisfies a public statement (the compliance rules), revealing only the statement's truth. For token sales, this public statement is simply: "The prover is on the approved list."

The integration requires a verifier smart contract. This contract contains the verification key corresponding to the proving key used by the off-chain service. When a user wants to participate, they submit a transaction calling the verifyProof function, passing the generated ZK proof as a parameter. The contract runs the verification algorithm; if it returns true, the user's address is marked as verified and can mint tokens or participate in the sale. Popular libraries for this include circom and snarkjs for SNARKs, or StarkWare's Cairo for STARKs. The verification logic is often pre-compiled into a Solidity contract using tools like snarkjs's zkey export solidityverifier.

Here is a simplified example of a verifier contract interface and its integration with a sale contract:

solidity
// Import the generated Verifier contract
import "./Verifier.sol";

contract CompliantSale {
    Verifier public verifier;
    mapping(address => bool) public isVerified;

    constructor(address _verifierAddress) {
        verifier = Verifier(_verifierAddress);
    }

    function submitProof(
        uint[2] memory a,
        uint[2][2] memory b,
        uint[2] memory c,
        uint[1] memory input
    ) public {
        require(verifier.verifyProof(a, b, c, input), "Invalid proof");
        isVerified[msg.sender] = true;
    }

    function mintTokens() public {
        require(isVerified[msg.sender], "Not KYC verified");
        // ... minting logic
    }
}

The a, b, c parameters represent the elliptic curve points of the SNARK proof, and the input is the public signal (e.g., a hash of the approved user list).

Key considerations for production include gas cost and trust assumptions. Verifying a SNARK proof on-chain typically costs 200k-500k gas, which is significant but a one-time fee per user. The system's security relies entirely on the trustworthiness of the entity that performed the initial KYC check and generated the proving/verification key pair. Using a trusted setup ceremony (like Perpetual Powers of Tau) for SNARKs mitigates risks, while STARKs do not require this. Furthermore, the link between a user's wallet and their off-chain identity must be securely established during proof generation to prevent proof selling or theft.

To implement this, developers typically set up an off-chain prover service. This service holds the proving key and, after validating a user's credentials against the compliance provider (e.g., Synaps, Fractal), generates the unique ZK proof for that user's wallet address. The user then signs a transaction to submit this proof to the chain. For scalability, consider using batch verification or proof aggregation where a single proof can verify multiple users, as implemented in projects like Semaphore or zkSync's internal systems. Always audit the circuit logic (the rules encoded into the ZKP) and the verifier contract, as bugs here compromise the entire system's compliance and privacy guarantees.

data-handling-jurisdiction
COMPLIANCE

Setting Up On-Chain KYC/AML for Compliant Token Sales

Implementing Know Your Customer (KYC) and Anti-Money Laundering (AML) checks directly on-chain is essential for launching compliant token offerings. This guide covers the core concepts and technical approaches for integrating these regulatory requirements into your smart contracts and dApp flow.

On-chain KYC/AML refers to the process of verifying user identities and screening for illicit activity before granting access to a token sale, all while leveraging blockchain infrastructure. Unlike traditional, centralized databases, this approach can use zero-knowledge proofs (ZKPs) or token-gated access to create a privacy-preserving compliance layer. The goal is to prove a user has been verified by a trusted provider without exposing their sensitive personal data on the public ledger. Protocols like Polygon ID and Veramo provide frameworks for creating and verifying these decentralized identity credentials.

The technical implementation typically involves a multi-step flow. First, users complete verification with a KYC provider (e.g., Sumsub, Jumio) off-chain. Upon success, the provider issues a verifiable credential (VC) or a proof, such as a ZK proof, that attests to the user's verified status. This proof is then submitted to your smart contract. The contract, which holds the token sale logic, will check the validity of this proof—often by verifying a cryptographic signature from the trusted issuer—before allowing the user to mint tokens or participate in the sale. This keeps the sensitive KYC data off-chain while enforcing rules on-chain.

For developers, key contract functions include a modifier to check for a valid verification. A basic Solidity pattern might store a mapping of verified addresses. A more advanced approach uses ERC-721 or ERC-1155 for soulbound tokens (SBTs) as non-transferable proof of KYC completion. Alternatively, you can integrate with oracles like Chainlink to fetch verification status from an API. It's critical to also implement AML screening by checking user addresses against on-chain threat intelligence feeds from providers like TRM Labs or Chainalysis to block wallets associated with sanctions or stolen funds.

Jurisdictional rules add another layer of complexity. Token sales must often restrict participants based on geography, such as excluding residents of the United States or other prohibited jurisdictions. This requires IP address checking or proof-of-residence verification during the KYC flow. Smart contracts can enforce this by requiring a VC that includes a validated country code. However, developers must be aware of the legal limitations; purely on-chain geoblocking can be circumvented by VPNs, so a combination of off-chain attestation and on-chain enforcement is standard practice.

When architecting your system, prioritize user privacy and data minimization. Using ZK proofs allows users to prove they are over 18 and not from a banned country without revealing their exact birthdate or nationality. Always conduct a legal review to ensure your chosen method satisfies the regulations in your target markets. The combination of decentralized identity, verifiable credentials, and programmable compliance within smart contracts creates a robust foundation for launching global, yet compliant, token sales.

ON-CHAIN KYC/AML

Frequently Asked Questions

Common technical questions and solutions for developers implementing compliant token sale infrastructure.

On-chain KYC stores verification status directly on the blockchain, typically as a non-transferable token (like an SBT) or a mapping in a smart contract. This allows other protocols (like a token sale contract) to permissionlessly check a user's status. Off-chain KYC processes data through a traditional, centralized provider and returns a simple pass/fail signal to the dApp backend, which then manages a private allowlist.

Key Technical Differences:

  • On-chain: Verification is transparent, composable, and user-carried. It uses more gas for issuance and checking.
  • Off-chain: Data privacy is preserved, gas costs are lower for the core application, but it creates a centralized dependency and limits interoperability.

Use on-chain verification for decentralized, permissionless systems where proof of compliance needs to be portable across dApps. Use off-chain for maximum data privacy and when integrating with existing enterprise KYC providers.

conclusion-next-steps
IMPLEMENTATION CHECKLIST

Conclusion and Next Steps

You have configured the core components for an on-chain KYC/AML framework. This section outlines final integration steps and resources for maintaining compliance.

To finalize your setup, integrate the verification module with your token sale smart contract. Use a modifier like onlyVerified to gate critical functions such as mint or transfer. For example, a basic ERC-20 with gated minting would check the KYCRegistry contract before proceeding. Always implement a failsafe mechanism, such as a multi-sig controlled emergencyPause, to halt all transactions if a critical vulnerability is discovered in your compliance logic. Thoroughly test the integrated system on a testnet like Sepolia or Polygon Mumbai before mainnet deployment.

Compliance is not a one-time setup. You must establish ongoing monitoring procedures. This includes regularly updating sanction lists by calling updateSanctionList on your oracle or registry contract, and having a clear process for handling user data requests or revocations. Consider implementing automated alerts for when a user's status changes from verified to non-compliant. For high-value transactions, you may add secondary checks using decentralized identity solutions like Verifiable Credentials (VCs) or proof-of-personhood protocols.

The regulatory landscape for digital assets is evolving. Stay informed by monitoring guidance from bodies like the Financial Action Task Force (FATF) and updates from your local financial regulator. Engage with legal counsel specializing in crypto compliance to ensure your implementation meets specific jurisdictional requirements. For further technical depth, review the source code and documentation for leading compliance providers like Chainalysis Oracle or Integral's on-chain KYC solution.

How to Set Up On-Chain KYC/AML for Compliant Token Sales | ChainScore Guides