Documentation Index
Fetch the complete documentation index at: https://mintlify.com/satsigner/satsigner/llms.txt
Use this file to discover all available pages before exploring further.
SatSigner is built as a privacy-first Bitcoin wallet with a focus on UTXO control, multi-signature support, and comprehensive visualization tools. This guide provides an overview of the technical architecture and key design decisions.
Technology Stack
Mobile Framework
- React Native (0.73.6): Cross-platform mobile development
- Expo (~50.0.14): Development platform and build tooling
- Expo Router (~3.4.8): File-based routing for navigation
- TypeScript (5.1.3+): Type-safe JavaScript for reliability
Bitcoin Integration
- BDK (Bitcoin Development Kit): Core Bitcoin functionality via bdk-rn
- bitcoinjs-lib (6.1.7+): Bitcoin transaction building and signing
- Electrum Client: Server communication for blockchain data
- Esplora API: Alternative blockchain backend
State Management
- Zustand (4.5.2+): Lightweight state management with persistence
- Immer (10.1.1+): Immutable state updates
- TanStack Query (5.64.2+): Server state and data fetching
- MMKV (2.12.2+): Fast, encrypted persistent storage
Cryptography & Security
- react-native-aes-crypto: AES-256-CBC encryption
- expo-secure-store: Secure key storage
- @bitcoinerlab/secp256k1: Bitcoin cryptography
- bip32: Hierarchical deterministic wallets
- bip39: Mnemonic phrase generation and validation
Visualization & UI
- React Native Skia: High-performance 2D graphics
- Victory Native: Charts and data visualization
- D3 (7.9.0+): Data-driven visualizations
- d3-sankey: Transaction flow diagrams
- React Native SVG: Scalable vector graphics
Additional Features
- Nostr (@nostr-dev-kit/ndk): Decentralized label synchronization
- Cashu (@cashu/cashu-ts): eCash integration
- Lightning: LND node integration
- NFC Manager: Hardware wallet communication
- i18n-js: Internationalization support
Monorepo Structure
SatSigner uses a monorepo architecture with Yarn Workspaces:
satsigner/
├── apps/
│ ├── docs/ # Mintlify documentation
│ └── mobile/ # React Native mobile app
├── package.json # Root workspace configuration
├── turbo.json # Turborepo build configuration
└── yarn.lock # Locked dependencies
Workspace Benefits
- Shared dependencies: Single
node_modules for the entire project
- Consistent versioning: All packages use the same dependency versions
- Simplified development: Single
yarn install for the entire project
- Turborepo integration: Parallel task execution and caching
Mobile App Architecture
Directory Structure
apps/mobile/
├── api/ # Backend integrations
│ ├── bdk.ts # BDK wallet operations
│ ├── blockchain.ts # Blockchain data fetching
│ ├── electrum.ts # Electrum protocol client
│ ├── esplora.ts # Esplora API client
│ ├── ecash.ts # Cashu eCash integration
│ └── nostr.ts # Nostr protocol integration
├── app/ # Expo Router pages (file-based routing)
│ ├── (authenticated)/ # Protected routes
│ ├── (onboarding)/ # Onboarding flow
│ └── _layout.tsx # Root layout
├── components/ # Reusable React components
├── hooks/ # Custom React hooks
├── store/ # Zustand state stores
│ ├── accounts.ts # Bitcoin accounts state
│ ├── auth.ts # Authentication state
│ ├── blockchain.ts # Network settings
│ ├── wallets.ts # Wallet management
│ └── settings.ts # App settings
├── types/ # TypeScript type definitions
│ ├── models/ # Data models (Account, Transaction, UTXO)
│ └── settings/ # Settings types
├── utils/ # Utility functions
│ ├── bitcoin.ts # Bitcoin-specific utilities
│ ├── bip32.ts # HD wallet utilities
│ ├── bip39.ts # Mnemonic utilities
│ ├── crypto.ts # Encryption/decryption
│ └── parse.ts # Data parsing
├── storage/ # Storage layer
│ ├── mmkv.ts # MMKV storage
│ └── encrypted.ts # Encrypted storage
├── constants/ # App constants and configurations
├── config/ # Configuration files
├── styles/ # Theme and styling
├── tests/ # Test files
│ ├── unit/ # Unit tests
│ └── int/ # Integration tests
├── android/ # Android native code
├── ios/ # iOS native code
└── .storybook/ # Storybook configuration
Core Design Patterns
1. BDK Integration
Bitcoin Development Kit (BDK) powers all Bitcoin operations:
// api/bdk.ts
import { Wallet, Blockchain, Mnemonic, Descriptor } from 'bdk-rn'
// Create wallet from mnemonic
const mnemonic = await new Mnemonic().create()
const descriptor = await getPrivateDescriptorFromMnemonic(
mnemonic,
network,
scriptVersion
)
const wallet = await new Wallet().create(descriptor, network)
// Sync with blockchain
const blockchain = await new Blockchain().create(blockchainConfig)
await wallet.sync(blockchain)
// Get balance
const balance = await wallet.getBalance()
// Build transaction
const txBuilder = await new TxBuilder()
.addRecipient(address, amount)
.feeRate(feeRate)
const psbt = await txBuilder.finish(wallet)
2. State Management with Zustand
Zustand provides simple, scalable state management:
// store/accounts.ts
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { produce } from 'immer'
type AccountsState = {
accounts: Account[]
addAccount: (account: Account) => void
updateAccount: (account: Account) => void
}
export const useAccountsStore = create<AccountsState>()(
persist(
(set) => ({
accounts: [],
addAccount: (account) =>
set(
produce((state) => {
state.accounts.push(account)
})
),
updateAccount: (account) =>
set(
produce((state) => {
const index = state.accounts.findIndex((a) => a.id === account.id)
if (index !== -1) state.accounts[index] = account
})
),
}),
{
name: 'accounts-storage',
storage: createJSONStorage(() => mmkvStorage),
}
)
)
3. Encrypted Storage
Sensitive data is encrypted with AES-256-CBC:
// storage/encrypted.ts
import { encrypt, decrypt } from 'react-native-aes-crypto'
import * as SecureStore from 'expo-secure-store'
// Encryption uses PIN-derived key
const key = await deriveKeyFromPIN(pin)
// Store encrypted data
export async function setItem(key: string, value: string, pin: string) {
const encryptionKey = await deriveKeyFromPIN(pin)
const encrypted = await aesEncrypt(value, encryptionKey)
await SecureStore.setItemAsync(key, encrypted)
}
// Retrieve and decrypt
export async function getItem(key: string, pin: string) {
const encrypted = await SecureStore.getItemAsync(key)
if (!encrypted) return null
const encryptionKey = await deriveKeyFromPIN(pin)
return await aesDecrypt(encrypted, encryptionKey)
}
4. Multi-Signature Wallet Support
Supports M-of-N multisig with sortedmulti descriptors:
// Multisig descriptor format
const descriptor = `wsh(sortedmulti(${threshold},${xpubs.join(',')}))`
// Create multisig wallet
const wallet = await createMultisigWallet({
threshold: 2,
total: 3,
keys: [xpub1, xpub2, xpub3],
network: 'bitcoin',
})
// PSBT workflow
// 1. Coordinator creates PSBT
const psbt = await wallet.buildTransaction(outputs)
// 2. Each cosigner signs
const signedPsbt1 = await cosigner1.signPSBT(psbt)
const signedPsbt2 = await cosigner2.signPSBT(psbt)
// 3. Combine and finalize
const finalPsbt = await combinePSBTs([signedPsbt1, signedPsbt2])
const tx = await finalizePSBT(finalPsbt)
5. File-Based Routing
Expo Router provides intuitive file-based routing:
app/
├── (authenticated)/ # Requires authentication
│ ├── (tabs)/ # Tab navigation
│ │ ├── signer/ # Bitcoin signer
│ │ ├── explorer/ # Blockchain explorer
│ │ └── converter/ # Currency converter
│ └── _layout.tsx
├── (onboarding)/ # Onboarding flow
│ ├── welcome.tsx
│ ├── create-wallet.tsx
│ └── _layout.tsx
├── auth/ # Authentication
│ ├── pin.tsx
│ └── unlock.tsx
└── _layout.tsx # Root layout
Route example:
app/(authenticated)/(tabs)/signer/bitcoin/account/[id]/index.tsx
→ /signer/bitcoin/account/abc123
6. Server State with TanStack Query
Efficient data fetching and caching:
import { useQuery } from '@tanstack/react-query'
// Fetch blockchain data
const { data: balance, isLoading } = useQuery({
queryKey: ['balance', accountId],
queryFn: async () => {
await wallet.sync(blockchain)
return await wallet.getBalance()
},
refetchInterval: 30000, // Refresh every 30s
})
// Fetch mempool data
const { data: feeRates } = useQuery({
queryKey: ['feeRates'],
queryFn: () => fetchFeeRates(),
staleTime: 60000, // Cache for 1 minute
})
Key Features Implementation
UTXO Selection
- Bubble chart visualization: React Native Skia for interactive UTXO bubbles
- Coin control: Manual UTXO selection for privacy and fee optimization
- Labeling: BIP-329 labels for UTXOs, addresses, and transactions
Transaction Building
- Manual fee control: Sat/vB fee rate selection
- RBF support: Replace-by-fee for stuck transactions
- Time locks: nLockTime and nSequence support
- Custom outputs: Multiple recipients, change control
Privacy Features
- No tracking: Zero analytics or telemetry
- Tor support: Private communication with Electrum servers
- Coin control: Prevent address reuse and UTXO linkage
- Seed dropping: Remove mnemonics after key extraction
Lightning & Layer 2
- LND integration: Connect to Lightning nodes via REST API
- eCash (Cashu): Private digital cash with mint management
- Token operations: Send/receive eCash tokens
Nostr Integration
- Label sync: Decentralized label synchronization via Nostr DMs
- Relay management: Configure Nostr relays
- Trusted devices: Manage sync permissions
Security Architecture
Encryption Layers
- PIN Protection: User enters PIN to unlock app
- Key Derivation: PIN → AES key via PBKDF2
- AES Encryption: Sensitive data encrypted with AES-256-CBC
- Secure Storage: Encrypted data stored in Expo SecureStore
Security Best Practices
- No plaintext secrets: All mnemonics and private keys encrypted
- Encrypted backups: Export files are encrypted
- PIN rate limiting: Prevent brute force attacks
- Duress PIN: Alternate PIN for emergency situations
- Biometric support: FaceID/TouchID integration
Testing Strategy
Unit Tests
Test individual functions and utilities:
// tests/unit/utils/bitcoin.test.ts
import { formatSats, parseSats } from '@/utils/bitcoin'
describe('Bitcoin utilities', () => {
test('formatSats formats correctly', () => {
expect(formatSats(100000000)).toBe('1.00000000')
expect(formatSats(1234567)).toBe('0.01234567')
})
test('parseSats parses correctly', () => {
expect(parseSats('1.00000000')).toBe(100000000)
expect(parseSats('0.01234567')).toBe(1234567)
})
})
Integration Tests
Test complex workflows:
// tests/int/wallet.test.ts
import { createWallet, syncWallet } from '@/api/bdk'
describe('Wallet operations', () => {
test('creates and syncs wallet', async () => {
const wallet = await createWallet({
mnemonic: TEST_MNEMONIC,
network: 'testnet',
})
await syncWallet(wallet)
const balance = await wallet.getBalance()
expect(balance).toBeDefined()
expect(balance.total).toBeGreaterThanOrEqual(0)
})
})
Test Configuration
// jest.config.js
module.exports = {
preset: 'jest-expo',
transformIgnorePatterns: [
'node_modules/(?!(react-native|@react-native|expo|bdk-rn|...))',
],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
},
}
- FlashList: High-performance list rendering
- Skia: GPU-accelerated graphics
- Reanimated: 60 FPS animations on UI thread
- Memoization:
useMemo and useCallback for expensive computations
Storage Optimization
- MMKV: Fast synchronous storage (vs AsyncStorage)
- Selective persistence: Only persist necessary state
- Lazy loading: Load data on demand
Bundle Size
- Code splitting: Dynamic imports where possible
- Tree shaking: Remove unused code
- Asset optimization: Compressed images and fonts
Build Configuration
Expo Configuration
// app.config.ts
export default {
name: 'satsigner',
version: '0.2.0',
ios: {
bundleIdentifier: 'com.satsigner.satsigner',
infoPlist: {
NFCReaderUsageDescription: 'NFC for hardware wallets',
},
},
android: {
package: 'com.satsigner.satsigner',
permissions: ['NFC'],
},
plugins: [
'expo-router',
'expo-secure-store',
'expo-camera',
],
}
Build Types
- Development: Debug builds with hot reload
- Preview: Production-like builds for testing
- Production: Optimized builds for app stores
Storybook
Component development in isolation:
# Run Storybook
yarn sb:android
yarn sb:ios
Debugging
- React DevTools: Component inspection
- Flipper: Network, storage, and performance debugging
- Metro bundler: JavaScript debugging
- Native debuggers: Xcode/Android Studio for native code
Code Quality
- TypeScript: Type checking (
yarn type-check)
- ESLint: Linting (
yarn lint)
- Prettier: Formatting (
yarn format)
- Jest: Testing (
yarn test)
Contributing to Architecture
When making architectural changes:
- Discuss first: Open an issue or discussion
- Document decisions: Update this guide
- Maintain patterns: Follow existing conventions
- Test thoroughly: Add tests for new patterns
- Consider impact: Breaking changes affect all users
Further Reading
Questions?
For architecture questions: