Skip to main content
A confidential fungible token where all balances are encrypted on-chain using Fully Homomorphic Encryption (FHE). Nobody can read a balance without the holder’s explicit authorization: not validators, not other users, not the contract owner. DFNS wallets sign every transaction and every EIP-712 user-decryption authorization, keeping keys off the application entirely.

Get the code

dfns/dfns-solutions: confidential-token

What this demonstrates

  • Confidential balances: balances live on-chain as encrypted euint64 values (Zama FHEVM). The EVM only sees ciphertext handles; amounts are never revealed on-chain.
  • Confidential transfers: amounts are encrypted client-side by the Zama relayer SDK before submission. The on-chain transaction carries only the ciphertext and an input proof.
  • Holder-only reveal: the holder signs an EIP-712 user-decryption request with their DFNS wallet. The Zama KMS decrypts the balance and re-encrypts it to a one-time NaCl keypair before returning it to the client.
  • Public reveal: the holder marks their balance handle publicly decryptable on-chain. Any client can then fetch the cleartext from the relayer.

How it works

Reference implementation

What you’ll need

  • A DFNS account
  • A service account with these permissions:
    • Wallets:BroadcastTransaction on the bank, sender, and receiver wallets
    • Wallets:GenerateSignature on the sender and receiver wallets (for reveal flows)
    • Wallets:Read on all three
  • Three DFNS wallets on Ethereum Sepolia:
    • bank: deploys the contract and mints tokens
    • sender: receives the mint and initiates transfers
    • receiver: receives transfers
  • Testnet ETH on all three wallets (Sepolia faucet). The bank wallet needs more, since it deploys the contract and signs the mint.
  • Node.js v22+

Setup

1

Clone and install

2

Configure environment variables

.env
3

Compile contracts

Running the demo

Deploy

Deploys ConfidentialToken from the bank wallet and writes the token address to deployment.json.

Mint

The bank wallet mints 1,000 cUSD (encrypted) to the sender. The amount is encrypted client-side by the relayer SDK and bound to (tokenAddress, bankAddress) before submission. The on-chain call carries only the ciphertext handle and an input proof.

Transfer

The sender confidentially transfers 200 cUSD to the receiver. The amount is re-encrypted, this time bound to (tokenAddress, senderAddress), and the contract updates both encrypted balances without ever learning the amounts.

Reveal balances

Holder-only reveal: the holder signs an EIP-712 authorization with their DFNS wallet; the Zama KMS decrypts and returns the balance only to them:
Public reveal: the holder marks their balance handle publicly decryptable on-chain; anyone can then fetch the cleartext from the relayer:

How DFNS fits in

DFNS is involved at two distinct points in the flow. Broadcasting transactions. Every on-chain call (deploy, mint, transfer, and the requestDiscloseEncryptedAmount call in the public reveal) is broadcast via wallets.broadcastTransaction. The application never holds a private key; it constructs the calldata and hands it to DFNS. Signing EIP-712 user-decrypt requests. The holder-only reveal requires an EIP-712 signature authorizing the Zama relayer to decrypt a specific encrypted handle. This is a standard Zama user-decryption request. DFNS signs it via wallets.generateSignature with kind: 'Eip712' instead of a local signer.

FHE notes

Input binding. When the relayer SDK encrypts an amount, the ciphertext is bound to a (contractAddress, callerAddress) pair. The FHEVM input verifier checks this binding on-chain: a ciphertext encrypted for the bank cannot be submitted by the sender, and a ciphertext encrypted for the token contract cannot be replayed against a different contract. Handle-based storage. The contract stores 32-byte handles, not ciphertexts. The actual ciphertexts live off-chain in the FHEVM coprocessor. The EVM runs homomorphic add and sub on handles, keeping gas costs low. ACL. The on-chain Access Control List (ACL) maps handles to addresses permitted to decrypt them. FHE.allow(handle, addr) grants user-decrypt rights; FHE.makePubliclyDecryptable(handle) grants the public role. The KMS refuses to decrypt without a matching ACL entry. Decimals. ERC-7984 uses 6 decimals (like USDC). 1,000 cUSD = 1_000_000_000 raw units.

Swap confidential tokens

Atomic ERC-20 ↔ ERC-7984 swaps including async KMS decryption and two-party encrypted exchange

Issue tokenized bonds

ERC-20 bond lifecycle with DFNS signing

Build programmable approval policies

Service accounts that decode ABI call data

Wallets API

broadcastTransaction and generateSignature reference
Last modified on June 12, 2026