Skip to main content

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

  1. PIN Protection: User enters PIN to unlock app
  2. Key Derivation: PIN → AES key via PBKDF2
  3. AES Encryption: Sensitive data encrypted with AES-256-CBC
  4. 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',
  },
}

Performance Optimizations

React Native Performance

  • 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

Development Tools

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:
  1. Discuss first: Open an issue or discussion
  2. Document decisions: Update this guide
  3. Maintain patterns: Follow existing conventions
  4. Test thoroughly: Add tests for new patterns
  5. Consider impact: Breaking changes affect all users

Further Reading

Questions?

For architecture questions: