Full-stack bank custody platform with crypto wallets alongside fiat accounts, powered by Dfns
Build a banking platform where crypto wallets sit alongside traditional fiat accounts — same look, same feel. Dfns powers the crypto side invisibly; customers never know it exists.
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:
Copy
Ask AI
// 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://bank-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);};
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.
To enable bank employee access, create a role called employee in Auth0 → User Management → Roles and assign it to your compliance officer user.
3
Configure environment variables
Copy
Ask AI
# Backendcp backend/.env.example backend/.env# Edit backend/.env with your Dfns and Auth0 credentials# Frontendcp 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 bank 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)
4
Start the backend
Copy
Ask AI
cd backendpython -m venv venvsource venv/bin/activatepip install -r requirements.txtpython app.py # Starts on http://localhost:5001
Fiat accounts are auto-created for each customer on first login.
5
Start the frontend
Copy
Ask AI
cd frontendnpm installnpm run dev # Starts on http://localhost:3000
The demo tells the story of a family banking with SecureBank: two parents (Alice and Bob) and their kid (Charlie). A bank compliance officer (Claire) oversees transfers.
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.
Global threshold (APPROVAL_THRESHOLD env var) — applies to wallet owners
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.
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 “bank-as-custodian” model where the bank 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 the bank define custom rules (per-user limits, role-based access). A future blueprint could explore Dfns policies for on-chain governance where bank employees 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.
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 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 bank employees to be registered as Dfns users rather than using a single service account.