SPB Git

spb/os-vault Public

Self-custody, multi-chain crypto wallet for macOS. One recovery phrase, six chain families, zero API keys — nothing leaves your Mac.

Swift 96% Shell 3.4% Makefile 0.6%
ZIP
NameLast commitUpdated
assets OS Vault 1.0.0 — self-custody multi-chain wallet for macOS 4 days ago
docs OS Vault 1.0.0 — self-custody multi-chain wallet for macOS 4 days ago
scripts OS Vault 1.0.0 — self-custody multi-chain wallet for macOS 4 days ago
Sources OS Vault 1.0.0 — self-custody multi-chain wallet for macOS 4 days ago
Support OS Vault 1.0.0 — self-custody multi-chain wallet for macOS 4 days ago
Tests OS Vault 1.0.0 — self-custody multi-chain wallet for macOS 4 days ago
vendor OS Vault 1.0.0 — self-custody multi-chain wallet for macOS 4 days ago
.gitignore OS Vault 1.0.0 — self-custody multi-chain wallet for macOS 4 days ago
CLAUDE.md OS Vault 1.0.0 — self-custody multi-chain wallet for macOS 4 days ago
Makefile OS Vault 1.0.0 — self-custody multi-chain wallet for macOS 4 days ago
Package.resolved OS Vault 1.0.0 — self-custody multi-chain wallet for macOS 4 days ago
Package.swift OS Vault 1.0.0 — self-custody multi-chain wallet for macOS 4 days ago
README.md README: credit author in header 4 days ago
README.md
OS Vault icon

# OS Vault

Self-custody, multi-chain crypto wallet for macOS. One recovery phrase. Six chain families. Zero API keys. Nothing ever leaves your Mac.

by Simon-Pierre Boucher · contact@spboucher.ai

Platform Swift SwiftUI Tests Release Notarized Keyless Self--custody

Bitcoin Ethereum Solana Tron XRPL TON


# Why OS Vault

Most wallets make you choose: convenience (custodial, tracked, keyed APIs) or sovereignty (CLI tools, manual everything). OS Vault refuses the trade:

  • 🔐 Your keys, your Mac, your encryption. The BIP-39 mnemonic is sealed with OS Vault's own vault format — PBKDF2-HMAC-SHA512 (600k rounds) → AES-256-GCM — in a local file. No macOS Keychain, no iCloud, no telemetry.
  • 🌐 One phrase, every chain. The same 12 words derive Bitcoin (BIP-84), 11 EVM chains, Solana, Tron, XRPL and TON — cross-validated against independent crypto stacks in the test suite.
  • 🗝️ Zero mandatory API keys. Every endpoint is public and keyless, with automatic failover. The only egress is blockchain RPC (+ optional CoinGecko prices, one toggle to kill).
  • ✍️ Sign-and-forget. The private key exists only for the milliseconds a transaction is being signed — every send re-derives it from your password and discards it.

# Chains

Family Assets Fees handled Testnet default
EVM × 11 — Ethereum, Base, Arbitrum, OP, Polygon, BNB, Avalanche, Gnosis, Linea, Scroll (+ Base Sepolia) USDC, USDC.e, USDT, DAI, USDS, EURC + native coin EIP-1559, BSC zero-base-fee, OP-stack/Scroll L1 data fee via oracle, Arbitrum inclusive estimates, Linea pinned base Base Sepolia
Bitcoin BTC (native SegWit bc1q…) sat/vB presets live from mempool.space, RBF on Signet
Solana SOL + USDC (SPL) ATA rent surfaced when the recipient has no token account Devnet
Tron TRX + USDT (TRC-20) Energy burn estimated pre-send (~13–27 TRX), fee_limit capped Nile
XRP Ledger XRP + RLUSD Reserves shown as locked; one-tap RLUSD trustline; recipient trustline checked Testnet
TON TON + USDT (jetton) Jetton-wallet indirection handled; ~0.07 TON attached, excess refunded Testnet

Every stablecoin contract address and decimal count was verified live on-chain before registration — including the traps: BNB-peg USDT/USDC are 18 decimals, bridged USDC.e is indistinguishable from native USDC by symbol() alone, DAI is 18 while USDC is 6. See docs/STABLECOINS.md.

# Security model

password ──▶ PBKDF2-HMAC-SHA512 (600k) ──▶ AES-256-GCM ──▶ vault.json (0600)

                          wrong password / tampering ─┘ indistinguishable (GCM auth)

unlock  ──▶ public addresses only stay in memory
send    ──▶ password → derive key → sign → discard          (every single time)
  • Forced written-backup verification (3 random words) before the wallet exists
  • Confirm screen with recipient, amount, network, worst-case fees — always
  • EIP-55 checksums; per-chain address validation; TESTNET badges everywhere
  • App Sandbox + Hardened Runtime; signed, notarized and stapled by Apple
  • The watch-only Bitcoin wallet holds public descriptors only; a throwaway in-memory signer signs PSBTs

# Infrastructure — no API keys

Concern Source (keyless) Fallback
EVM RPC PublicNode (11/11 verified) official chain RPCs, health-scored failover
Bitcoin mempool.space Esplora blockstream.info
Solana PublicNode api.mainnet-beta.solana.com
Tron TronGrid (anonymous, backoff)
XRPL xrplcluster.com s1.ripple.com
TON toncenter v2/v3 (1 req/s, throttled client-side)
Prices CoinGecko batched (USD/CAD/EUR), stale-while-revalidate, off switch DefiLlama

# Build

Pure SwiftPM — no .xcodeproj, no CocoaPods, no manual steps:

scripts/vendor-walletcore.sh   # one-time: vendors Trust wallet-core for macOS (~270 MB, not in git)
swift build                    # compile
swift test                     # 32 tests: BIP-39/BIP-84 vectors, vault crypto, decimals, validators
make dev                       # debug bundle, ad-hoc signed, launched
make release                   # Developer ID + notarize + staple + DMG with volume icon

The wallet-core vendoring is this repo's party trick: upstream ships no macOS SwiftPM support, so the script repackages the official CocoaPods tarball as a local binaryTarget — including a surgical ld -r pass that demotes the duplicate Rust runtime symbol it shares with the Bitcoin Dev Kit. Details in docs/RESEARCH-MULTICHAIN.md.

# Architecture

Sources/OSVaultKit
├── Services
│   ├── VaultCrypto      ← the encryption mechanism (no Keychain)
│   ├── KeyManager       ← BIP-39, HD derivation, vault lifecycle
│   ├── RPCService       ← EVM JSON-RPC with endpoint failover
│   ├── TransactionService ← EIP-1559 + 5 other real fee models
│   ├── BitcoinService   ← bdk-swift, watch-only + transient signer
│   ├── SolanaService    ← solana-swift (vendored), SPL + ATA
│   ├── TronService      ← wallet-core signing + TronGrid REST
│   ├── XRPLService      ← wallet-core signing + xrplcluster JSON-RPC
│   ├── TONService       ← wallet-core signing + toncenter v2/v3
│   └── PriceService     ← CoinGecko keyless, cached, optional
├── Models               ← Network (chain registry), Token (verified matrix)…
└── Views                ← SwiftUI: onboarding, home, per-chain panels

# Requirements

  • macOS 15.5+ (Apple silicon or Intel)
  • That's it. No accounts, no keys, no configuration.

# Testnet quickstart

  1. Create a wallet (write the 12 words down — the app makes you prove it).
  2. Fund: Circle faucet (USDC on Base Sepolia + Solana devnet), a Base Sepolia ETH faucet, mempool.space signet faucet, nileex.io (TRX + test USDT), XRPL faucet, @testgiver_ton_bot.
  3. Send. Watch it confirm on the linked explorer.

Built with Swift, paranoia, and a refusal to type API keys. © 2026 Simon-Pierre Boucher · contact@spboucher.ai