Mobile Crypto Wallet Development
Mobile wallet is not just "an app showing balance". It's a key management system, interface to blockchain networks, and security-critical product where error in key derivation or storage can cost user all their funds. Development requires clear understanding of cryptography (BIP-32, BIP-39, BIP-44), specifics of mobile Secure Enclave/Keystore, and WalletConnect/EIP protocols.
Complete picture from key hierarchy to signing flow, with emphasis on security-critical details.
Key Management: HD Wallet Architecture
BIP-39: Mnemonic Phrase
Starting point — 128-256 bits of randomness converted to 12-24 words from BIP-39 dictionary (2048 words). Words are human-readable key backup. Critical: entropy must be generated with cryptographically secure CSPRNG (iOS: SecRandomCopyBytes, Android: SecureRandom).
// iOS: generate 128 bits entropy
var entropy = [UInt8](repeating: 0, count: 16)
let result = SecRandomCopyBytes(kSecRandomDefault, entropy.count, &entropy)
guard result == errSecSuccess else { throw WalletError.entropyGenerationFailed }
// Convert to mnemonic via library (WalletKit, BitcoinKit, TrustWalletCore)
let mnemonic = try Mnemonic.generate(from: entropy)
// ["abandon", "ability", "able", "about", "above", ...]
BIP-32/BIP-44: Hierarchical Key Derivation
From seed (obtained from mnemonic via PBKDF2), key tree is built. BIP-44 defines standard path: m / purpose' / coin_type' / account' / change / address_index.
Example paths:
- Ethereum:
m/44'/60'/0'/0/0— first ETH address - Bitcoin:
m/44'/0'/0'/0/0— first BTC address - Solana:
m/44'/501'/0'/0'
import { HDKey } from '@scure/bip32';
import { mnemonicToSeedSync } from '@scure/bip39';
const seed = mnemonicToSeedSync(mnemonic);
const root = HDKey.fromMasterSeed(seed);
// Derive first ETH account
const ethKey = root.derive("m/44'/60'/0'/0/0");
const privateKey = ethKey.privateKey!; // Uint8Array
const address = computeAddress(privateKey); // ethers.js or viem
Important: ' means hardened derivation — child keys cannot be computed from parent public key. Purpose, coin_type, account levels — always hardened. Change and index levels — not hardened (allows generating public keys without private key for watch-only function).
Secure Storage: Keys in Device Memory
Private keys and seed never stored in plaintext. Strategies by security level:
iOS Keychain + Secure Enclave. iOS Keychain is encrypted system storage. For maximum security: key created directly in Secure Enclave (separate processor, key never leaves chip). Signing operations happen inside Enclave — app gets only signature result, not private key.
Android Keystore. Analog of Keychain, keys in Hardware-backed keystore (TEE). Supports EC keys, including secp256k1 (Android 9+). KeyStore.getInstance("AndroidKeyStore").
Additional encryption layer. On top of OS keychain: seed encrypted via AES-256-GCM with key protected in Keychain. Double encryption adds protection if Keychain API compromised.
Transaction Signing Flow
Transaction Building
For EVM networks: nonce, to, value, data, gasLimit, maxFeePerGas, maxPriorityFeePerGas, chainId (EIP-1559 format). Building happens in app, signing — in secure context:
// viem: build and sign transaction
import { createWalletClient, http, parseEther } from 'viem';
import { mainnet } from 'viem/chains';
const transaction = {
to: recipientAddress,
value: parseEther('0.1'),
chainId: 1,
};
// Estimate gas
const gasEstimate = await publicClient.estimateGas(transaction);
// Get current fee data
const feeData = await publicClient.estimateFeesPerGas();
const signedTx = await walletClient.signTransaction({
...transaction,
gas: gasEstimate,
maxFeePerGas: feeData.maxFeePerGas,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
});
EIP-712 for Structured Data Signing
Most DeFi interactions — not just ETH transfers. approve, permit, signTypedData — all EIP-712 typed signatures. Wallet must:
- Parse EIP-712 structure from dApp request
- Display human-readable data to user (not raw hex)
- After confirmation — sign and return signature
Decoding and displaying EIP-712 — non-trivial task. ethers.js v6 and viem have built-in support. For correct UI display need recursively unfold typed structures.
WalletConnect v2 Integration
WalletConnect v2 — communication protocol between dApp and wallet via encrypted relay. Wallet scans QR-code or gets deep link → establishes encrypted channel → receives JSON-RPC requests from dApp.
import { Core } from '@walletconnect/core';
import { Web3Wallet } from '@walletconnect/web3wallet';
const core = new Core({ projectId: WC_PROJECT_ID });
const wallet = await Web3Wallet.init({ core, metadata: { name: 'MyWallet', ... } });
// Handle session proposal (user scans QR)
wallet.on('session_proposal', async (proposal) => {
const { id, params } = proposal;
// Show user what dApp requests
const approved = await showApprovalUI(params.proposer.metadata, params.requiredNamespaces);
if (approved) {
await wallet.approveSession({
id,
namespaces: {
eip155: {
accounts: [`eip155:1:${userAddress}`, `eip155:137:${userAddress}`],
methods: ['eth_sendTransaction', 'personal_sign', 'eth_signTypedData_v4'],
events: ['accountsChanged', 'chainChanged'],
},
},
});
}
});
// Handle signature requests
wallet.on('session_request', async (request) => {
const { topic, params } = request;
const { method, params: callParams } = params.request;
if (method === 'eth_sendTransaction') {
const tx = callParams[0];
const approved = await showTransactionUI(tx);
if (approved) {
const signedTx = await signTransaction(tx);
const txHash = await broadcastTransaction(signedTx);
await wallet.respondSessionRequest({ topic, response: { id: request.id, result: txHash } });
}
}
});
WalletConnect v2 supports multi-chain: one session can manage addresses on Ethereum, Polygon, Arbitrum simultaneously.
Multi-chain Architecture
Chain Registry and RPC Management
Modern wallet supports 10-50+ networks. Pattern: chain registry with config for each:
interface ChainConfig {
chainId: number;
name: string;
rpcUrls: string[]; // multiple for failover
fallbackRpcUrls: string[]; // public RPC as fallback
nativeCurrency: { symbol: string; decimals: number };
blockExplorer: string;
isTestnet: boolean;
}
// RPC with automatic failover
class RobustProvider {
private providers: JsonRpcProvider[];
private currentIndex = 0;
async call(method: string, params: any[]) {
for (let i = 0; i < this.providers.length; i++) {
try {
return await this.providers[this.currentIndex].send(method, params);
} catch (err) {
this.currentIndex = (this.currentIndex + 1) % this.providers.length;
}
}
throw new Error('All RPC endpoints failed');
}
}
Token Discovery and Balance Fetching
Moralis API, Alchemy Token API, Ankr Advanced API — specialized endpoints for getting all tokens and NFTs of address without iterating all possible ERC-20 contracts. Much faster than separate balanceOf calls.
For production wallet: combination Alchemy/Moralis for main networks + own balance cache in SQLite (React Native with expo-sqlite or MMKV).
Security at App Level
Jailbreak/Root Detection
On compromised device (jailbreak/root), Keychain/Keystore don't guarantee protection — other apps with root access can read protected data. Detecting jailbreak:
iOS: check /Applications/Cydia.app, /private/var/lib/apt, ability to write /private/test-jailbreak. Android: check su binaries, Magisk/SuperSU apps, presence of test build keys.
Reaction: block some functions (seed backup), warn user, optionally — complete ban (debatable: some wallets allow with warning).
Screen Recording Protection
screens with seed phrase and private key must be unavailable for screenshots and recording:
// iOS: screenshot protection for specific view
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Add secure text field as overlay — system protection mechanism
let field = UITextField()
field.isSecureTextEntry = true
self.view.addSubview(field)
field.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
field.sendActions(for: .editingDidBegin)
}
Android: WindowManager.LayoutParams.FLAG_SECURE for activities with sensitive data.
Clipboard Security
Copying seed or private key to clipboard — risk: other apps can read it. Warn user, automatically clear clipboard after 60 seconds:
await Clipboard.setStringAsync(seedPhrase);
setTimeout(async () => {
await Clipboard.setStringAsync(''); // clear after 60 seconds
}, 60_000);
Development Stack
React Native — cross-platform iOS + Android from one codebase. Expo simplifies native modules. But for crypto operations: native modules critical (React Native JavaScript bridge too slow for crypto, especially BIP-32 derivation).
TrustWallet Core — native library (C++), supports 60+ blockchains: HD Wallet, signing, address derivation. Bindings for Swift, Kotlin, TypeScript. Eliminates need to implement BIP-32/BIP-44 for each chain.
| Component | Technology | Complexity |
|---|---|---|
| Key management | TrustWallet Core + Keychain/Keystore | High |
| EVM signing | viem / ethers.js v6 | Medium |
| WalletConnect v2 | @walletconnect/web3wallet | Medium |
| Multi-chain support | Chain registry + RPC failover | Medium |
| Token discovery | Alchemy/Moralis API | Low |
| Secure storage | iOS Keychain / Android Keystore | High |
| Biometric auth | expo-local-authentication | Low |
| NFT display | Alchemy NFT API + expo-image | Medium |
Timeline. MVP with basic functions (create/import wallet, ETH/ERC-20 send/receive, WalletConnect) — 3-4 months. Full-featured wallet with multi-chain, NFT, DeFi integration, security hardening — 6-9 months.
Mandatory stages: security review before App Store/Google Play publication + penetration testing on real devices. Apple and Google check wallets stricter than regular apps — requirements for privacy policy, key management documentation, compliance with local financial regulations.
Cost calculated after detailed specification of target networks and functional scope.







