# How to Add a Crypto Wallet to Your App (Copy-Paste Prompts Included)

- Tool: App Features
- Last updated: July 2026

## TL;DR

A crypto wallet needs six components: a QR receive address, a Web3 RPC client (web3dart or ethers.dart), flutter_secure_storage for the private key, a transaction builder that signs client-side, a transaction history fetched from Alchemy or Moralis, and a biometric gate before every send. With FlutterFlow and custom code blocks you can ship a read-only wallet display in 1–2 days; a full send/receive wallet with proper key management is a 3–6 week custom build. Running costs are $0–120/mo depending on RPC call volume.

## What a Crypto Wallet Feature Actually Is

A crypto wallet is not a place that stores coins — it stores the cryptographic keys that prove ownership of coins recorded on a public blockchain. Your app's wallet feature has two jobs: display what the user owns (balance, transaction history, prices in USD) and authorize transfers by signing transactions with the private key. The signing happens entirely on the device — the private key never travels to your server or any third party. This is what makes crypto wallet development fundamentally different from building a payment form: the security model, key management, and recovery flows require careful decisions that AI tools can scaffold but rarely get right without specific guidance.

## Anatomy of the Feature

Six components. The key management layer and biometric gate are where security fails — and where AI tools take the most shortcuts. Read each component before prompting.

- **Wallet address display and QR** (ui): The qr_flutter Flutter package renders the receive address as a QR code. The address is displayed as copyable text below. The uni_links package handles wallet:// deep links if other apps need to send to your address.
- **Web3 RPC client** (backend): The web3dart Flutter package queries an Ethereum-compatible JSON-RPC endpoint (Alchemy or Infura) to fetch the wallet balance via eth_getBalance and to broadcast signed transactions via eth_sendRawTransaction. All RPC calls are outbound HTTP from the device.
- **Key management layer** (backend): flutter_secure_storage stores the encrypted private key or seed phrase in the device's secure enclave (iOS Keychain, Android Keystore). The key is never transmitted to any server. The local_auth package enforces Face ID or fingerprint authentication before the key is read from storage.
- **Transaction builder** (backend): Constructs and signs raw transactions entirely client-side using web3dart. Gas estimation uses an eth_estimateGas RPC call with the transaction parameters. The gas price uses eth_gasPrice or eth_maxFeePerGas for EIP-1559 chains. The signed transaction hex is then broadcast via eth_sendRawTransaction.
- **Transaction history** (data): Alchemy's Enhanced APIs or the Moralis Transactions API fetches historical transactions by wallet address. Results are cached locally in SQLite via the sqflite Flutter package for offline display, keyed by transaction hash with block_number, from, to, amount_wei, status, and timestamp.
- **Biometric gate** (ui): The local_auth package calls authenticateWithBiometrics() before any operation that reads the private key — initiating a send, signing a message, or displaying the seed phrase. If biometrics are unavailable, fall back to device PIN via LocalAuthentication.authenticate() with deviceCredential fallback.

## Data model

Wallet metadata lives in Supabase — the public address and label only, never the private key. The private key and transaction cache stay on-device. Run this in the Supabase SQL editor:

```sql
create table public.wallets (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users(id) on delete cascade not null,
  address text not null,
  label text not null default 'My Wallet',
  chain text not null default 'ethereum',
  created_at timestamptz not null default now()
);

alter table public.wallets enable row level security;

create policy "Users can view own wallets"
  on public.wallets for select
  using (auth.uid() = user_id);

create policy "Users can insert own wallets"
  on public.wallets for insert
  with check (auth.uid() = user_id);

create policy "Users can update own wallets"
  on public.wallets for update
  using (auth.uid() = user_id);

create policy "Users can delete own wallets"
  on public.wallets for delete
  using (auth.uid() = user_id);

create index wallets_user_id_idx
  on public.wallets (user_id);
```

Private keys are NEVER stored in Supabase or any server — only the public wallet address and a user-facing label. Transaction history is cached in the device's SQLite database (sqflite) with hash, from_address, to_address, amount_wei, status (pending/confirmed/failed), block_number, and timestamp columns.

## Build paths

### Flutterflow — fit 3/10, 1–2 days

Best available AI-tool path for mobile wallet UI — FlutterFlow handles the layout, balance display, and history list while custom code blocks inject the Web3 logic that the visual builder cannot express.

1. In FlutterFlow, create the wallet home page with balance display widget, transaction history ListView, and a send/receive bottom navigation; use page state variables to hold the balance and address
2. Add a Custom Function that uses web3dart to call eth_getBalance with the wallet address and returns the formatted ETH and USD equivalent
3. Add a Custom Widget for the QR code display using the qr_flutter package, passing the wallet address as a parameter
4. In the Action editor, wire a Supabase REST call to store the wallet address and label in the wallets table on first launch
5. Add a Custom Action for the send flow: validate address with EIP-55 checksum, call local_auth for biometric gate, fetch current gas estimate, build and sign the transaction using web3dart, broadcast via eth_sendRawTransaction
6. In Settings → Permissions (iOS), add NSFaceIDUsageDescription; in Android manifest, add USE_BIOMETRIC and USE_FINGERPRINT permissions — missing these causes App Store and Play Store rejection

Limitations:

- web3dart and flutter_secure_storage require custom code blocks — no visual Web3 widget exists in FlutterFlow's marketplace
- FlutterFlow Pro or Teams plan required for custom code export; custom code blocks do not run in the in-browser preview
- Complex key management and transaction signing is error-prone in the visual builder; seed phrase backup flow is particularly difficult to implement correctly without full code export
- Full send/receive with proper security (secure storage, biometrics, seed phrase backup) is at the edge of what FlutterFlow can deliver without significant custom code

### Lovable — fit 1/10, 2–4 hours (read-only display only)

Lovable is suitable ONLY for a read-only wallet balance and history display — fetching data from Alchemy via an Edge Function proxy. Do not build send functionality or key management in a browser-based tool.

1. Create a Lovable project and connect Supabase Cloud
2. Prompt for an Edge Function that accepts a wallet address and calls the Alchemy API to fetch ETH balance and recent transactions — this avoids CORS issues with direct browser calls
3. Build a read-only wallet dashboard page showing balance in ETH and USD, and a transaction history table from the Edge Function response
4. Store only the public wallet address in Supabase; display it with a copy button

Starter prompt:

```
Build a read-only crypto portfolio tracker. Add a Supabase Edge Function called get-wallet that accepts a public address and calls the Alchemy API (ALCHEMY_API_KEY in Lovable Cloud Secrets): fetch ETH balance via eth_getBalance (convert from wei, 18 decimal places) and USD price from CoinGecko (COINGECKO_API_KEY in Secrets). Return { address, balanceEth, balanceUsd, transactions } where transactions come from alchemy_getAssetTransfers. Frontend: address input with EIP-55 checksum validation before calling the Edge Function; ETH balance in large type with USD below it; transactions table showing hash (first 8 chars), from/to (truncated), value in ETH, status (pending or confirmed), and relative timestamp. Copy-address button with toast. Refresh balance on page focus. Show loading shimmer during fetch and error state on Edge Function failure. Store only the public address in Supabase — never a private key.
```

Limitations:

- Browser localStorage is not a secure location for private keys — never implement send/sign functionality in a web-only tool
- No native biometric support in a web browser — Web Authentication API (WebAuthn) is not equivalent to device biometrics for key protection
- No offline capability — balance and history disappear if the user has no connection
- Not a real wallet — suitable only as a portfolio tracker that reads public blockchain data; unsuitable for any transaction functionality

### Custom — fit 5/10, 3–6 weeks

The right path for a production crypto wallet. Full control over BIP39/BIP44 key derivation, multi-chain support, hardware wallet integration, MPC key splitting for institutional custody, and regulatory compliance flows.

1. BIP39 mnemonic generation and BIP44 HD key derivation using the bip39 and ed25519_hd_key Dart packages for proper multi-account wallet support
2. Multi-chain architecture: separate RPC endpoints and gas token handling for Ethereum, Polygon, Base, Optimism, and BSC
3. MPC (multi-party computation) key splitting for enterprise custody where no single device holds the complete private key
4. KYC/AML integration (Sumsub or Jumio) for fiat on-ramp regulatory requirements

Limitations:

- Minimum 3–6 weeks for a production-grade implementation; security audit strongly recommended before launch and adds 2–4 weeks
- Deep expertise in both mobile security and Web3 cryptography is required — this is not a standard app build

## Gotchas

- **Private key stored in SharedPreferences — plaintext on Android** — Flutter's SharedPreferences writes to a plain XML file that is readable without root access on Android debug builds and potentially accessible on rooted devices. AI tools default to SharedPreferences for any persistent data because it requires no additional packages. On iOS, NSUserDefaults has similar plaintext storage. A user's entire wallet is compromised if the device is rooted or if the app backup is not encrypted. Fix: Explicitly require flutter_secure_storage in your prompt — it uses Android Keystore and iOS Secure Enclave, which store keys in hardware-backed secure memory. Verify the key is protected: on Android, use the adb shell and confirm the key does not appear in the shared_prefs directory. Never use SharedPreferences, Hive without encryption, or sqflite for private key storage.
- **Gas fee estimate is stale at transaction confirmation time** — AI-generated wallet code typically fetches the gas estimate when the send form opens — sometimes minutes before the user taps Confirm. On busy networks like Ethereum mainnet, gas prices can move 2–5x in seconds. A transaction submitted with a stale gas price is either rejected (too low) or overpays significantly (if the estimate was too high). Fix: Re-fetch eth_estimateGas and eth_gasPrice (or eth_maxFeePerGas for EIP-1559) immediately before calling signTransaction() — not when the send form renders. Display a 'Gas refreshed' timestamp next to the estimate and show a warning icon if it is more than 30 seconds old. Disable the Confirm button while the refresh is in progress.
- **RPC endpoint hardcoded to Ethereum mainnet during development** — AI generates RPC URLs for Ethereum mainnet by default. During development, any accidental test send uses real ETH. A developer testing a send flow with 0.001 ETH can lose $3–5 per test transaction. Even a single accidental mainnet send during development causes real financial loss. Fix: Scaffold a NETWORK environment variable from day one. Default to Sepolia testnet (https://eth-sepolia.g.alchemy.com) in development. Display a red 'TESTNET' banner in your wallet UI when not on mainnet. Require an explicit MAINNET=true flag in the production build configuration before switching the RPC endpoint.
- **Transaction history shows empty on first load — API 429 silently swallowed** — Alchemy and Moralis impose rate limits per API key. When the app launches, all active users may simultaneously request transaction history, hitting the rate limit. FlutterFlow custom code blocks that do not handle HTTP 429 responses show an empty list with no error message — users think their history is gone. Fix: Add retry logic with exponential backoff in your Custom Action: on 429, wait 1 second, retry; on second 429, wait 3 seconds, then show an error state. Display a shimmer loading skeleton while history loads. Cache the last successful response in sqflite so users see recent history instantly on subsequent opens, even before the API responds.
- **Seed phrase displayed permanently on screen without obscuring** — When AI generates the seed phrase backup screen, it typically renders all 12 or 24 words in a visible grid that stays on screen indefinitely. This is a critical security failure: anyone who glances at the screen — or captures a screenshot — has full wallet access forever. Fix: Require a tap-to-reveal pattern: show the seed phrase blurred by default with a 'Tap to reveal — be sure no one is watching' prompt. Implement screen capture prevention using flutter_windowmanager's FLAG_SECURE on Android and ignoresScreenCapture on iOS. Auto-hide the words after 60 seconds of display. Log seed phrase reveal events (without the words themselves) for security auditing.

## Best practices

- Store private keys exclusively in flutter_secure_storage — never SharedPreferences, Hive without encryption, or any cloud storage including Supabase
- Require biometric authentication immediately before the key read in the same code path, not once on app launch
- Default to testnet (Sepolia for Ethereum) in development and display a prominent banner — one accidental mainnet send costs real money
- Re-fetch gas price immediately before signing a transaction, not when the send form opens; gas moves fast on congested networks
- Validate destination addresses with EIP-55 checksum validation before allowing any amount entry — wrong-address sends are irreversible
- Cache transaction history in sqflite and token prices in memory (1-minute TTL) to reduce RPC call volume and keep the UI responsive offline
- Implement screen capture prevention (FLAG_SECURE / ignoresScreenCapture) on any screen that shows the seed phrase or private key
- Show a 'Pending' status immediately after broadcasting a transaction — poll eth_getTransactionReceipt every 5 seconds and update status when block_number is set

## Frequently asked questions

### Is it safe to store private keys in a mobile app?

Yes, if you use the device's secure enclave — iOS Keychain or Android Keystore — via flutter_secure_storage. These hardware-backed stores are resistant to extraction even on rooted devices. What is not safe is storing keys in SharedPreferences, plain files, or any cloud database. The key must never leave the device; your server should only ever see the public wallet address.

### What blockchain should I build on — Ethereum, Polygon, or Base?

For most consumer apps: Base (an Ethereum Layer 2 built by Coinbase). Gas fees on Base are under $0.01 per transaction, compared to $1–20 on Ethereum mainnet. Base is EVM-compatible, so all Ethereum tooling (web3dart, Alchemy, Metamask) works without changes. Polygon is a solid alternative. Build on Ethereum mainnet only if your use case specifically requires it — gas costs will frustrate your users.

### Can users recover their wallet if they lose their phone?

Only if you implement a seed phrase backup flow. A BIP39 12-word or 24-word mnemonic can regenerate the private key on any device. Your app must walk users through writing it down before their first transaction, store only the mnemonic in flutter_secure_storage (never cloud sync), and provide an import flow on reinstall. Without this, a lost phone means lost funds — permanently and irreversibly.

### How do I show token prices in USD?

Fetch the token's price in USD from the CoinGecko API using the token's contract address or CoinGecko coin ID. Cache the price in memory with a 1-minute TTL — calling CoinGecko on every balance display will exhaust the free tier quickly. Multiply the raw balance (converted from wei using web3dart's EtherAmount) by the USD price to get the fiat value.

### Do I need a license to build a crypto wallet?

For a non-custodial wallet (where users hold their own keys), most jurisdictions do not currently require a license — your app is a key management tool, not a financial intermediary. If you add fiat on/off ramps, custody services, or exchange functionality, money transmission or VASP regulations apply in most countries. Consult a lawyer familiar with crypto regulation in your target markets before launch.

### What's the difference between custodial and non-custodial wallets?

A custodial wallet means your company holds the private keys on behalf of users — like an exchange. You are legally responsible for the funds and face significant regulatory requirements. A non-custodial wallet (what this guide covers) means the user's device holds the private key; you never have access to their funds. Non-custodial is simpler to build legally, but puts full responsibility for key backup on the user.

### How do gas fees work and can I pay them for users?

Gas fees are paid to the blockchain network validators for processing transactions — they go to miners/validators, not to you. They fluctuate with network congestion. You can sponsor gas fees for users via account abstraction (EIP-4337) using a paymaster service like Pimlico or Alchemy's Gas Manager — your app's account covers the gas and charges users separately or absorbs the cost. This significantly improves UX but adds architectural complexity.

### Can I add NFT display to the wallet?

Yes — the Alchemy NFT API (getNFTsForOwner) and Moralis NFT API return all NFTs owned by an address including metadata, image URLs, and collection names. Display them in a grid with lazy-loading images. NFT display is read-only and free at small scale; it adds no signing or transaction complexity. Prompt for it as a separate screen tab within the same wallet project.

---

Source: https://www.rapidevelopers.com/app-features/crypto-wallet
© RapidDev — https://www.rapidevelopers.com/app-features/crypto-wallet
