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

# Swap confidential tokens

> Atomic ERC-20 ↔ ERC-7984 swaps on Ethereum using DFNS wallets and Zama FHEVM. Three flows covering synchronous, async KMS-decrypted, and two-party encrypted exchange.

Three atomic-swap flows between public ERC-20 and Zama FHEVM confidential tokens (ERC-7984), all signed by DFNS-managed wallets. On the confidential leg, amounts are fully encrypted on-chain using Fully Homomorphic Encryption (FHE): only the balance holder can decrypt their own balance, and only the authorised counterparty sees what it needs to settle.

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

## Three swap flows

| # | Flow                | Mechanics                                                                                                                                                                                                                         |
| - | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | ERC-20 → ERC-7984   | Synchronous, single transaction. The swap contract pulls plainEUR from the sender, encrypts the amount in-contract, and credits confSGD to the sender.                                                                            |
| 2 | ERC-7984 → ERC-20   | Asynchronous, two transactions. The sender escrows an encrypted confSGD amount; the contract marks the handle for KMS decryption; a second transaction submits the cleartext + decryption proof to release the matching plainEUR. |
| 3 | ERC-7984 ↔ ERC-7984 | Atomic two-party. The sender escrows encrypted confSGD and pins an encrypted confEUR price. The receiver fills in one transaction, paying confEUR and receiving confSGD. Neither amount is ever visible on-chain.                 |

Rate is 1:1 across all three flows.

## Contracts

| Contract                | Description                                                                  |
| ----------------------- | ---------------------------------------------------------------------------- |
| `PlainToken.sol`        | Standard ERC-20 (OpenZeppelin) with owner-controlled minting                 |
| `ConfidentialToken.sol` | ERC-7984 via OpenZeppelin confidential-contracts; all balances FHE-encrypted |
| `ConfidentialSwap.sol`  | The three-flow swap contract; holds plainEUR and confToken reserves          |

The swap contract follows a reserves pattern: it holds a pre-funded reserve of each token type to settle incoming swaps without needing to mint on-demand. Reserves are minted by the `bank` role at setup time.

## 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:Read` on all three
* Three DFNS wallets on **Ethereum Sepolia**:
  * `bank`: deploys contracts and mints reserves
  * `sender`: initiates swaps
  * `receiver`: fills Flow 3 orders
* Testnet ETH on all three wallets ([Sepolia faucet](https://sepoliafaucet.com/))
* 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-swap
    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 contracts, mints tokens, funds reserves                           |
    | `SENDER_WALLET_ID`   | Initiates all three swap flows                                            |
    | `RECEIVER_WALLET_ID` | Fills the Flow 3 two-party order                                          |
  </Step>

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

## Running the demo

### Deploy

```bash theme={null}
npm run deploy:plain          # Deploy plainEUR (ERC-20)
npm run deploy:confTokens     # Deploy confSGD and confEUR (ERC-7984)
npm run deploy:swap           # Deploy ConfidentialSwap
```

Contract addresses are written to `deployment.json` and read automatically by subsequent scripts.

### Mint and fund reserves

```bash theme={null}
# Mint tokens to the three wallets
npm run mint:plain -- sender 2000
npm run mint:confToken -- confSGD sender 2000
npm run mint:confToken -- confEUR receiver 1000

# Fund the swap contract's reserves
npm run mint:plain -- swap 5000
npm run mint:confToken -- confSGD swap 5000
```

### Set approvals

```bash theme={null}
npm run approve -- sender plainToken   # ERC-20 approve for Flow 1
npm run approve -- sender confSGD      # ERC-7984 operator for Flow 2 and 3
npm run approve -- receiver confEUR    # ERC-7984 operator for Flow 3
```

### Run the swaps

**Flow 1: ERC-20 to confidential token**

```bash theme={null}
npm run swap:erc20-to-confToken -- 100
# sender pays 100 plainEUR, receives 100 confSGD
```

**Flow 2: confidential token to ERC-20 (async)**

```bash theme={null}
npm run swap:confToken-to-erc20 -- 50
# sender pays 50 confSGD, receives 50 plainEUR
# script polls until the KMS produces a decryption proof, then finalises
```

**Flow 3: confidential token to confidential token**

```bash theme={null}
npm run swap:confToken-to-confToken -- 100 80
# sender escrows 100 confSGD, names 80 confEUR as the price
# receiver fills atomically, both transfers occur in one transaction
```

### Check balances

```bash theme={null}
npm run reveal -- sender confSGD    # sender's encrypted confSGD balance
npm run reveal:all                  # all balances for sender and receiver in a table
```

## FHE notes

**Access grants and transaction boundaries.** The Zama FHEVM tracks which addresses may use an encrypted handle. Flow 2 and Flow 3 each span multiple transactions. The contracts use `FHE.allowTransient()` and `FHE.allowThis()` to pass handles between calls. If you add extra steps between transactions (querying balance handles in between, for example), access grants may no longer be valid.

**Input binding.** When encrypting an amount off-chain with the Zama relayer SDK, the ciphertext is bound to a specific `(contractAddress, callerAddress)` pair. A ciphertext encrypted for the swap contract cannot be replayed against the token contract directly.

**Flow 2 KMS latency.** After the first transaction, the script polls until the Zama KMS produces a cleartext + signature. This typically takes 10–30 seconds on Sepolia. The script retries automatically.

**1:1 rate and 6 decimals.** All tokens use 6 decimals. The swap contract does no scaling. Mixing tokens with different decimal counts requires adjusting the reserve maths.

## Related

<CardGroup cols={2}>
  <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="Cross-border payments on Solana" icon="arrow-right-arrow-left" href="/solutions/cross-border-payments-solana">
    Atomic stablecoin swaps with DFNS KMS
  </Card>

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