Confidential token distribution, powered by Fully Homomorphic Encryption.
Recipient addresses are public. Amounts are not.
VeilDrop lets you distribute tokens to any number of recipients with fully encrypted allocations. No one — not block explorers, not other recipients, not even VeilDrop itself — can see how much each address received. Only the recipient, using their wallet, can decrypt their own allocation.
This makes VeilDrop suitable for:
- Payroll — pay contributors without leaking salary data on-chain
- Grants — distribute grant amounts privately
- Investor allocations — fund rounds without revealing per-investor token amounts
- Airdrops — drop tokens to a community without public enumeration of amounts
Confidentiality is enforced at the protocol level using Zama's Fully Homomorphic Encryption (FHE), via the ERC-7984 confidential token standard. VeilDrop integrates with the TokenOps SDK and ships its own ERC-7984 demo token, vCTT (VeilToken).
Testnet only — VeilDrop currently runs on Ethereum Sepolia testnet.
| Token | Address | Description |
|---|---|---|
| vCTT (VeilToken) | 0x1c20CeC1...670A42 |
VeilDrop's own ERC-7984 demo token, deployed and owned by this repo |
| CTTT | TokenOps default | TokenOps' canonical confidential test token, useful for testers already on the platform |
VeilDrop supports three on-chain distribution strategies, both powered by @tokenops/sdk:
Admin sends tokens to all recipients in a single transaction. Recipients receive tokens automatically — no action required on their end. The admin pays gas for the full distribution.
Admin funds a pool and signs a per-recipient claim authorization off-chain. Recipients receive a stateless claim link containing their encrypted payload, and claim on their own schedule. No backend required — the full claim data is encoded in the URL.
Admin funds a vesting contract with linear unlocking schedules. Recipient claims unlocked tokens gradually over time. Only the recipient can decrypt their own vesting progress and claimable amounts.
- Node ≥ 22 (required by
@zama-fhe/sdk— see.nvmrc) - pnpm
- A browser wallet (MetaMask or compatible) connected to Sepolia
# Use the correct Node version
nvm use
# Install dependencies
pnpm install
# Start the dev server
pnpm devOpen http://localhost:3000, connect your wallet on Sepolia, and mint testnet tokens from the /faucet before creating a distribution.
# .env
DATABASE_URL="postgresql://user:password@host:5432/dbname"DATABASE_URL is required, VeilDrop uses Postgres (via Prisma) for distribution history, recipient claim state, drafts, and the address book. Any Postgres works, including a local instance or a free tier on Neon or Supabase.
# .env.local, optional
NEXT_PUBLIC_SEPOLIA_RPC_URL=https://your-rpc-endpointnpx prisma migrate devApplies the schema in prisma/schema.prisma and generates the Prisma client.
| Route | Description |
|---|---|
/ |
Landing page |
/distribute |
Create a new Disperse, Airdrop, or Vesting distribution |
/dashboard |
All distributions created by the connected wallet |
/dashboard/[id] |
Detail view for a specific distribution |
/received |
Recipient dashboard — view your private allocations, decrypt sealed values, check claim status |
/claim/[id] |
Recipient claim portal — decrypt and claim an Airdrop allocation |
/vesting/[id] |
Vesting portal — decrypt, claim unlocked tokens, and check your vesting schedule timeline |
/faucet |
Mint testnet tokens (vCTT or CTTT) |
/docs |
In-app documentation |
Encryption and decryption go through @zama-fhe/react-sdk's ZamaProvider. The provider only mounts after a wallet is connected on Sepolia. Components that call Zama hooks must gate on useIsZamaReady() (see app/providers.tsx) — not just isConnected — to avoid mounting before the provider is ready during the connection handshake.
lib/encryptor-adapter.ts bridges a shape mismatch between @tokenops/sdk and @zama-fhe/sdk at their currently published versions:
@tokenops/sdk'sEncryptorexpects{ handles: Uint8Array[], inputProof: Uint8Array }@zama-fhe/[email protected]'s relayer returns{ encryptedValues: Hex[], inputProof: Hex }
This adapter is required, not optional.
Wallet-mode Disperse requires two separate approvals:
- Each subwallet must approve the Disperse singleton via
register()/useApproveTokenOnWallets - The admin's own balance must separately approve the singleton as operator via
useConfidentialSetOperator
Skipping the second step reverts on-chain with ERC7984UnauthorizedSpender.
| Layer | Technology |
|---|---|
| Framework | Next.js 14 (App Router) |
| Language | TypeScript |
| Styling | Tailwind CSS |
| Blockchain | wagmi · viem |
| Data fetching | TanStack Query |
| FHE | @zama-fhe/sdk · @zama-fhe/react-sdk |
| Token ops | @tokenops/sdk |
| Animations | Framer Motion |
The contracts/ workspace contains the VeilToken ERC-7984 source. See contracts/contracts/VeilToken.sol.
Sepolia deployment: 0x1c20CeC11BbfDB19f88450569Ed7a98A7a670A42
Built on Zama FHE · TokenOps SDK