> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dfns.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Issue confidential tokens

> Deploy, mint, transfer, and reveal an ERC-7984 confidential token on Ethereum using DFNS wallets. Balances stay FHE-encrypted on-chain; only the holder can read them.

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.

<Card title="Get the code" icon="github" href="https://github.com/dfns/dfns-solutions/tree/m/confidential-token">
  dfns/dfns-solutions: confidential-token
</Card>

## 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

```mermaid theme={null}
sequenceDiagram
    participant Bank
    participant DFNS
    participant Relayer as Zama Relayer
    participant Token as ConfidentialToken
    participant KMS as Zama KMS

    Bank->>DFNS: broadcastTransaction (deploy)
    DFNS->>Token: Deploy ConfidentialToken

    Bank->>Relayer: createEncryptedInput(amount)
    Relayer-->>Bank: handle + inputProof
    Bank->>DFNS: broadcastTransaction (mint)
    DFNS->>Token: mint(handle, inputProof)

    note over Token,KMS: Balance stored as encrypted euint64 handle

    Bank->>DFNS: generateSignature (EIP-712 user-decrypt)
    DFNS-->>Bank: signature
    Bank->>Relayer: userDecrypt(handle, signature)
    KMS-->>Relayer: cleartext (re-encrypted to client keypair)
    Relayer-->>Bank: decrypted balance
```

## Reference implementation

| Component  | Technology                                                                                                                                   |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Contract   | ERC-7984 via OpenZeppelin `confidential-contracts`; `ZamaEthereumConfig` pins Sepolia addresses                                              |
| Encryption | Zama relayer SDK (`@zama-fhe/relayer-sdk`)                                                                                                   |
| Signing    | DFNS KMS via `@dfns/sdk`: `broadcastTransaction` for on-chain txs, `generateSignature` with `kind: 'Eip712'` for user-decrypt authorizations |
| Scripts    | TypeScript via `tsx`                                                                                                                         |
| Network    | Ethereum Sepolia (Solidity 0.8.27, EVM target `cancun`)                                                                                      |

## What you'll need

* A [DFNS account](https://app.dfns.io)
* A [service account](/guides/developers/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](https://sepoliafaucet.com/)). The bank wallet needs more, since it deploys the contract and signs the mint.
* Node.js v22+

## Setup

<Steps>
  <Step title="Clone and install">
    ```bash theme={null}
    git clone https://github.com/dfns/dfns-solutions.git
    cd dfns-solutions/confidential-token
    npm install
    ```
  </Step>

  <Step title="Configure environment variables">
    ```bash theme={null}
    cp .env.example .env
    ```

    ```bash .env theme={null}
    # DFNS API credentials: find these in app.dfns.io under Settings > Developers > Service Accounts
    DFNS_API_URL=https://api.dfns.io
    DFNS_ORG_ID=or-xxx-xxx
    DFNS_AUTH_TOKEN=eyJ...
    DFNS_CRED_ID=cred-xxx
    DFNS_PRIVATE_KEY="-----BEGIN EC PRIVATE KEY-----\n...\n-----END EC PRIVATE KEY-----"

    # Wallet IDs: create three EVM wallets on Sepolia in app.dfns.io
    BANK_WALLET_ID=wa-xxx-xxx
    SENDER_WALLET_ID=wa-yyy-yyy
    RECEIVER_WALLET_ID=wa-zzz-zzz

    # RPC URL (optional, defaults to a public Sepolia node)
    # BLOCKCHAIN_RPC_URL=
    ```

    | Variable             | Description                                                               |
    | -------------------- | ------------------------------------------------------------------------- |
    | `DFNS_API_URL`       | DFNS API base URL                                                         |
    | `DFNS_ORG_ID`        | Your organization ID. See [how to find it](/guides/find-organization-id). |
    | `DFNS_AUTH_TOKEN`    | Service account auth token                                                |
    | `DFNS_CRED_ID`       | Service account credential ID                                             |
    | `DFNS_PRIVATE_KEY`   | Service account private key (PEM format)                                  |
    | `BANK_WALLET_ID`     | Deploys the contract and mints tokens                                     |
    | `SENDER_WALLET_ID`   | Receives the initial mint; initiates transfers and reveals                |
    | `RECEIVER_WALLET_ID` | Receives transfers; can also initiate reveals                             |
  </Step>

  <Step title="Compile contracts">
    ```bash theme={null}
    npm run compile
    ```
  </Step>
</Steps>

## Running the demo

### Deploy

```bash theme={null}
npm run deploy
```

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

### Mint

```bash theme={null}
npm run 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

```bash theme={null}
npm run 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:

```bash theme={null}
npm run reveal-balance               # reveals receiver's balance (default)
npm run reveal-balance -- sender     # reveals sender's balance
```

**Public reveal**: the holder marks their balance handle publicly decryptable on-chain; anyone can then fetch the cleartext from the relayer:

```bash theme={null}
npm run reveal-public                # public-discloses receiver's balance (default)
npm run reveal-public -- sender      # public-discloses sender's balance
```

## 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](https://docs.zama.ai/protocol/solidity-guides/v0.10/docs/sdk-guides/user-decryption.html). DFNS signs it via `wallets.generateSignature` with `kind: 'Eip712'` instead of a local signer.

```ts theme={null}
const res = await dfnsApi.wallets.generateSignature({
  walletId: holderWalletId,
  body: {
    kind: 'Eip712',
    types: typedData.types,
    domain: typedData.domain,
    message: typedData.message,
  },
})
const signature = res.signature.encoded
```

## 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.

## Related

<CardGroup cols={2}>
  <Card title="Swap confidential tokens" icon="arrow-right-arrow-left" href="/solutions/confidential-swap">
    Atomic ERC-20 ↔ ERC-7984 swaps including async KMS decryption and two-party encrypted exchange
  </Card>

  <Card title="Issue tokenized bonds" icon="landmark" href="/solutions/issue-tokenized-bonds">
    ERC-20 bond lifecycle with DFNS signing
  </Card>

  <Card title="Build programmable approval policies" icon="code" href="/solutions/build-programmable-approval-policies">
    Service accounts that decode ABI call data
  </Card>

  <Card title="Wallets API" icon="wallet" href="/api-reference/wallets">
    `broadcastTransaction` and `generateSignature` reference
  </Card>
</CardGroup>
