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

# Accept cryptocurrencies

> Build a full-stack custody platform that combines crypto wallets and fiat accounts on Dfns, including merchant payments and treasury management.

Add crypto to a fintech, neobank, wealth platform, or any institution's internal flows. Crypto wallets sit alongside existing fiat accounts, same look, same feel. Dfns powers the crypto side invisibly; end customers never know it exists.

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

## Overview

The reference implementation models a retail financial platform (using a bank as the working example, but the same pattern fits any fintech or institution):

* **Customers** see their fiat accounts (EUR checking, savings) and crypto wallets in one dashboard
* **The platform operator** holds a Dfns service account. Customers never interact with Dfns directly
* **Transfers** below a threshold execute immediately; larger transfers require operator approval
* **Family delegation** lets customers share wallet access with relatives, with per-person transfer limits

```mermaid theme={null}
flowchart LR
    FE["<b>Next.js frontend</b><br/>React, Auth0"]
    BE["<b>Flask backend</b><br/>Python REST API<br/>• Auth0 JWT verify<br/>• SQLite<br/>• Dfns Python SDK"]
    API["<b>Dfns API</b><br/>• Wallets<br/>• Transactions"]
    FE --> BE --> API
```

The backend is a pure REST API with JWT auth. Any client (web, mobile, CLI) can consume it.

| Layer          | Technology                                 |
| -------------- | ------------------------------------------ |
| Frontend       | Next.js 15 + TypeScript                    |
| Backend        | Flask (Python)                             |
| Database       | SQLite                                     |
| Auth           | Auth0 (RBAC for employee role)             |
| Crypto custody | Dfns Python SDK (service account)          |
| Blockchain     | Ethereum Sepolia (via Dfns, no direct RPC) |

## Prerequisites

* Python 3.10+
* Node.js 18+
* A [Dfns account](https://app.dfns.io) with a service account
* An [Auth0](https://auth0.com/) tenant

## Configuration

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

  <Step title="Set up Auth0">
    Create an Auth0 application (Regular Web Application) and an API:

    | Setting               | Value                                 |
    | --------------------- | ------------------------------------- |
    | Allowed Callback URLs | `http://localhost:3000/auth/callback` |
    | Allowed Logout URLs   | `http://localhost:3000`               |
    | API Audience          | `https://bank-custody-api`            |

    Authorize your application to access the API (Application → APIs tab → toggle on). On the API settings, enable **RBAC** and **Allow Offline Access**.

    Create a Post-Login Action to include user info in the access token:

    ```js theme={null}
    // Auth0 Action: "Add claims to token"
    // IMPORTANT: The namespace MUST NOT be your Auth0 domain. Auth0 silently
    // strips custom claims namespaced under its own domain.
    exports.onExecutePostLogin = async (event, api) => {
      const namespace = "https://custody.example.com";
      api.accessToken.setCustomClaim(`${namespace}/roles`, event.authorization?.roles || []);
      api.accessToken.setCustomClaim(`${namespace}/email`, event.user.email);
      api.accessToken.setCustomClaim(`${namespace}/name`, event.user.name);
    };
    ```

    <Warning>
      The claims namespace **cannot** be your Auth0 tenant domain (e.g. `https://your-tenant.auth0.com`). Auth0 silently strips claims under its own domain. Use any other HTTPS URI.
    </Warning>

    To enable operator access (compliance, treasury, ops), create a role called `employee` in Auth0 → User Management → Roles and assign it to the relevant users.
  </Step>

  <Step title="Configure environment variables">
    ```bash theme={null}
    # Backend
    cp backend/.env.example backend/.env
    # Edit backend/.env with your Dfns and Auth0 credentials

    # Frontend
    cp frontend/.env.example frontend/.env
    # Edit frontend/.env with your Auth0 credentials
    ```

    **Backend variables:**

    | Variable             | Description                                                          |
    | -------------------- | -------------------------------------------------------------------- |
    | `DFNS_API_URL`       | Dfns API base URL (default: `https://api.dfns.io`)                   |
    | `DFNS_AUTH_TOKEN`    | Service account auth token                                           |
    | `DFNS_CRED_ID`       | Service account credential ID                                        |
    | `DFNS_PRIVATE_KEY`   | Service account private key (PEM format)                             |
    | `AUTH0_DOMAIN`       | Your Auth0 tenant domain (e.g. `your-tenant.auth0.com`)              |
    | `AUTH0_AUDIENCE`     | Auth0 API audience identifier                                        |
    | `APPROVAL_THRESHOLD` | Transfer amount in ETH requiring operator approval (default: `1000`) |

    **Frontend variables:**

    | Variable              | Description                                        |
    | --------------------- | -------------------------------------------------- |
    | `AUTH0_SECRET`        | A random string for session encryption             |
    | `APP_BASE_URL`        | Frontend URL (default: `http://localhost:3000`)    |
    | `AUTH0_DOMAIN`        | Your Auth0 tenant domain                           |
    | `AUTH0_CLIENT_ID`     | Auth0 application client ID                        |
    | `AUTH0_CLIENT_SECRET` | Auth0 application client secret                    |
    | `AUTH0_AUDIENCE`      | Auth0 API audience identifier                      |
    | `NEXT_PUBLIC_API_URL` | Backend API URL (default: `http://localhost:5001`) |
  </Step>

  <Step title="Start the backend">
    ```bash theme={null}
    cd backend
    python -m venv venv
    source venv/bin/activate
    pip install -r requirements.txt
    python app.py    # Starts on http://localhost:5001
    ```

    Fiat accounts are auto-created for each customer on first login.
  </Step>

  <Step title="Start the frontend">
    ```bash theme={null}
    cd frontend
    npm install
    npm run dev      # Starts on http://localhost:3000
    ```
  </Step>
</Steps>

## Demo walkthrough

The demo follows a family using a financial platform called SecureFinance: two parents (Alice and Bob) and their kid (Charlie). An operator compliance officer (Claire) oversees transfers.

### Part 1: Platform setup

1. Create an Auth0 user for the compliance officer (e.g. `compliance@securefinance.com`)
2. Assign the `employee` role to this user in Auth0 → User Management → Roles
3. Set `APPROVAL_THRESHOLD=0.05` in `backend/.env` so you can test the approval flow with small amounts

### Part 2: Create the family

Create three Auth0 users. They register automatically in the app on first login:

| User             | Email                 | Role     |
| ---------------- | --------------------- | -------- |
| Parent 1 (Alice) | `alice@example.com`   | Customer |
| Parent 2 (Bob)   | `bob@example.com`     | Customer |
| Kid (Charlie)    | `charlie@example.com` | Customer |

### Part 3: Alice sets up wallets

Log in as **Alice**:

1. **Create wallets**: click "+ New crypto account" twice: "Alice's Wallet" and "Charlie's Wallet" (both Ethereum Sepolia)
2. **Fund both wallets** using a [Sepolia faucet](https://www.alchemy.com/faucets/ethereum-sepolia)
3. **Share access** via the **Family** page:
   * Share "Alice's Wallet" with Bob → **View + Transfer**
   * Share "Charlie's Wallet" with Bob → **View + Transfer**
   * Share "Charlie's Wallet" with Charlie → **View + Transfer**, approval threshold: `0.01` ETH

### Part 4: Bob sets up his wallet

Log in as **Bob**:

1. **Create a wallet**: "Bob's Wallet" (Ethereum Sepolia)
2. Verify he can see Alice's and Charlie's wallets (marked "Shared")
3. Share "Bob's Wallet" with Alice → **View + Transfer**

### Part 5: Charlie sends funds

Log in as **Charlie**:

1. See "Charlie's Wallet" in the dashboard (marked "Shared")
2. **Send 0.001 ETH**: goes through immediately (under his 0.01 ETH limit)
3. **Send 0.02 ETH**: queued for operator approval (over his personal limit)

### Part 6: Operator reviews

Log in as the **compliance officer**:

1. Go to the **Admin** section → **Pending approvals**
2. See Charlie's pending transfer
3. **Approve** or **Reject** the transfer
4. Approved transfers execute on-chain via Dfns

## How It Works

### Authentication Flow

The frontend uses Auth0 for authentication. On login, Auth0 issues a JWT access token containing custom claims (email, name, roles). The backend verifies this token against Auth0's JWKS endpoint and auto-creates a local user record on first login.

```text theme={null}
Browser → Auth0 → JWT access token → Backend (verify + extract claims)
```

### Wallet Operations

All wallet operations go through the backend's Dfns service account:

| Operation     | Backend action         | Dfns SDK call                 |
| ------------- | ---------------------- | ----------------------------- |
| Create wallet | `POST /api/wallets`    | `wallets.create_wallet()`     |
| View balance  | `GET /api/wallets/:id` | `wallets.get_wallet_assets()` |
| Transfer      | `POST /api/transfers`  | `wallets.transfer_asset()`    |

The backend converts ETH amounts to wei before sending to Dfns.

### Approval Logic

Transfers are checked against two thresholds:

1. **Global threshold** (`APPROVAL_THRESHOLD` env var): applies to wallet owners
2. **Per-delegation limit** (`transfer_limit` on delegations): applies to delegated users, overrides the global threshold

If the transfer amount is at or above the applicable threshold, it's queued in SQLite with status `pending`. A bank employee can then approve (which broadcasts via Dfns) or reject it.

### Family Delegation

Wallet owners can grant two levels of access to family members:

| Permission   | What the grantee can do                                  |
| ------------ | -------------------------------------------------------- |
| **View**     | See the wallet balance and transaction history           |
| **Transfer** | View + initiate transfers (subject to per-person limits) |

Delegations are stored in SQLite and enforced in the backend. The wallet owner can revoke access at any time.

## API Reference

### Customer endpoints (require Auth0 JWT)

| Method   | Path                   | Description                                            |
| -------- | ---------------------- | ------------------------------------------------------ |
| `GET`    | `/api/wallets`         | List own wallets, delegated wallets, and fiat accounts |
| `POST`   | `/api/wallets`         | Create a new crypto wallet                             |
| `GET`    | `/api/wallets/:id`     | Wallet detail with balance and transfer history        |
| `GET`    | `/api/transfers`       | Transfer history                                       |
| `POST`   | `/api/transfers`       | Initiate a transfer (may require approval)             |
| `GET`    | `/api/delegations`     | List delegations granted and received                  |
| `POST`   | `/api/delegations`     | Grant family access (with optional `transfer_limit`)   |
| `DELETE` | `/api/delegations/:id` | Revoke access                                          |

### Employee endpoints (require Auth0 JWT + employee role)

| Method | Path                               | Description               |
| ------ | ---------------------------------- | ------------------------- |
| `GET`  | `/api/admin/customers`             | List all customers        |
| `GET`  | `/api/admin/customers/:id/wallets` | List a customer's wallets |
| `GET`  | `/api/admin/transfers`             | Pending transfers         |
| `POST` | `/api/admin/transfers/:id/approve` | Approve and broadcast     |
| `POST` | `/api/admin/transfers/:id/reject`  | Reject transfer           |

## Design Decisions

**Dfns is the source of truth** for wallet balances, addresses, and transaction status. SQLite stores what Dfns doesn't know: user-wallet ownership, friendly names, delegations, and the approval queue.

**Service account pattern**: the backend holds a single Dfns service account key. Customers never interact with Dfns directly. This is the "platform-as-custodian" model where the operator (bank, fintech, or institution) has full control over wallet operations.

**App-level approvals**: transfer approval is handled in the application layer (SQLite queue + employee dashboard), not via Dfns policies. This keeps the architecture simple and lets you define custom rules (per-user limits, role-based access). A future blueprint could explore Dfns policies for on-chain governance where operators are registered as Dfns users.

**Mobile-ready API**: pure REST, JWT Bearer auth, JSON responses. No server-side sessions or cookies required on the API side.

## Adapting for Production

**Different networks**: change the `network` parameter when creating wallets (e.g., `Ethereum`, `Polygon`, `Arbitrum`). The Dfns SDK handles chain-specific details.

**Real fiat integration**: replace the demo fiat accounts with a real banking or account-info API (e.g., Plaid, Open Banking) to show actual account balances.

**Multi-level approvals**: extend the approval logic to support multiple approvers, approval chains, or time-delayed execution for high-value transfers.

**Dfns policies**: for stricter governance, Dfns policies can enforce signing rules (e.g., approval quorums, velocity limits) independent of the application layer. This requires operators to be registered as Dfns users rather than using a single service account.
