Skip to content

Latest commit

Β 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Satlas β€” know what your Bitcoin reveals before you spend it

Rust WebAssembly React TypeScript
Bitcoin BIP-329 Tests Backend License

A client-side Bitcoin privacy analyzer. See your wallet as individual coins, name where they came from, and check a payment before you send it β€” in plain English.

Quick start Β· Features Β· How it works Β· Trust model Β· Development


🧭 The problem

Bitcoin has no single balance. Your wallet holds separate coins, each with its own history. When you pay someone, the wallet picks coins automatically β€” and if it picks two that came from different parts of your life, everyone watching the blockchain learns they belong to the same person. Forever.

flowchart LR
    S["🟦 Salary coin<br/>0.20 BTC"]:::salary --> T{{"One payment<br/>of 0.23 BTC"}}:::tx
    D["🟩 Donation coin<br/>0.05 BTC"]:::donation --> T
    T --> R["Recipient"]:::out
    T --> C["Change back to you"]:::out
    T -. "now publicly linked" .-> L["😬 Salary ⟷ Donation<br/>same owner"]:::leak

    classDef salary fill:#1e3a5f,stroke:#3987e5,color:#fff
    classDef donation fill:#123f31,stroke:#199e70,color:#fff
    classDef tx fill:#27272a,stroke:#f59e0b,color:#fff
    classDef out fill:#18181b,stroke:#52525b,color:#e4e4e7
    classDef leak fill:#450a0a,stroke:#ef4444,color:#fecaca
Loading

Most wallets hide this behind automatic coin selection. Satlas shows it to you before it happens β€” without holding your keys, replacing your wallet, or sending your data to a server.

πŸ”‘
Watch-only
No private keys, no signing, no broadcasting
πŸ–₯️
No backend
All analysis runs in your browser via WASM
πŸ”Œ
Wallet-agnostic
Works beside Sparrow, BlueWallet, Electrum…
🧾
Explainable
Every conclusion says why, and how sure it is

πŸš€ Quick start

git clone https://github.com/CapThunder19/Satlas.git
cd Satlas/web
pnpm install
pnpm wasm      # build the Rust engine to WebAssembly
pnpm dev       # β†’ http://localhost:5173
  1. Click Explore with a demo wallet (or paste your own xpub / zpub / descriptor)
  2. Open Check a payment and type 0.23
  3. Watch Satlas warn you that it would link Salary and Donation β€” then click Show me for a safer way

✨ Features

πŸͺ™ Your coins

Every unspent coin with its value, age, origin and group. Click + name this coin to label it; change inherits the label of the coins that funded it when that's unambiguous.

πŸ” Check a payment

Type an amount. Satlas simulates the coins your wallet would pick and tells you what that reveals β€” with a Show me button when a safer selection exists.

Your coins Check a payment

πŸ—ΊοΈ Map

A force-directed map: bubbles are coins sized by value, shaded areas are groups an outsider can already link, and a pulsing amber line shows the new link your planned payment would create.

🏠 Beginner-first

Three-step onboarding, per-wallet "where's my xpub?" help, a glossary, jargon-free copy, and "what you can do" advice on every warning.

Map Landing page

Verdicts

Verdict Meaning
🟒 Safe to send Reveals nothing an observer couldn't already infer
🟑 Mostly fine Some history exposed, but no labelled coins newly linked
πŸ”΄ Careful Connects coins that were previously separate

Certainty on every conclusion

Satlas never dresses a heuristic up as a fact.

Badge Meaning Example
Fact On the blockchain, or a label you added "This address received 2 payments"
Likely A standard chain-analysis rule of thumb "This output looks like change"
Unclear Not enough information "This coin has no label"

Six coin-selection strategies, compared side by side

Strategy What it models
Biggest coins first The default in most wallets
Oldest coins first FIFO
Smallest coins first Consolidation
Avoid change Exact-match subset, no change output
Best for privacy Stay inside one already-linked group
I'll pick the coins Manual coin control

More

  • 🏷️ BIP-329 labels β€” export to / import from Sparrow and other compatible wallets
  • 🌐 Real-wallet scanning β€” parallel gap-limit scan, retries, rate-limit backoff, automatic failover
  • πŸ’Ύ Remember this wallet β€” cached in IndexedDB, reopens instantly, refreshes in the background
  • πŸ› οΈ Self-hosted backend β€” point Satlas at your own Esplora / mempool instance
  • πŸ§ͺ Demo wallet β€” synthetic history for trying it with zero setup

βš™οΈ How it works

Architecture

flowchart LR
    U(["πŸ‘€ xpub /<br/>descriptor"]):::you --> D

    subgraph B["πŸ–₯️ Your browser"]
        D["β‘  Derive addresses<br/><sub>πŸ¦€ Rust Β· WASM</sub>"]:::rust --> S["β‘‘ Scan history<br/><sub>TypeScript</sub>"]:::ts
        S --> A["β‘’ Coins + heuristics<br/><sub>πŸ¦€ Rust Β· WASM</sub>"]:::rust
        A --> V["β‘£ Coins Β· Map<br/><sub>React</sub>"]:::ts
        V --> P["β‘€ Simulate payment<br/><sub>πŸ¦€ Rust Β· WASM</sub>"]:::rust
        V <--> I[("IndexedDB<br/><sub>labels Β· cache</sub>")]:::store
    end

    S <-->|"HTTPS Β· addresses only"| E[("Esplora API<br/><sub>mempool.space<br/>blockstream.info<br/>or your own</sub>")]:::ext

    classDef you fill:#1c1917,stroke:#f59e0b,color:#fde68a
    classDef rust fill:#3b1d12,stroke:#d95926,color:#fff
    classDef ts fill:#132a45,stroke:#3987e5,color:#fff
    classDef store fill:#18181b,stroke:#71717a,color:#e4e4e7
    classDef ext fill:#1c1917,stroke:#f59e0b,color:#fde68a
Loading

🟧 Rust compiled to WebAssembly · 🟦 TypeScript / React · everything inside the box runs locally

What happens when you check a payment

sequenceDiagram
    autonumber
    actor You
    participant UI as React UI
    participant Core as satlas-core (WASM)

    You->>UI: Amount 0.23 BTC
    UI->>Core: simulateSpend(wallet, amount, strategy)
    Core->>Core: Select coins (biggest first)
    Core->>Core: Which groups & labels do the inputs span?
    Core->>Core: Is the change output identifiable?
    Core-->>UI: πŸ”΄ Careful β€” links Salary ⟷ Donation
    UI->>Core: simulateAll(every strategy)
    Core-->>UI: "Best for privacy" β†’ 🟒 Safe to send
    UI-->>You: Verdict Β· why Β· what you can do Β· Show me
Loading

The heuristics

mindmap
  root((satlas-core<br/>heuristics))
    Common-input ownership
      Coins spent together share an owner
      Union-find over full history
      Names the tx that merged groups
    Address reuse
      Same address received twice
      Always a Fact
    Change detection
      Round payment amount
      Script-type mismatch
      Change to a reused address
      Unnecessary input
    Label mixing
      Inputs carry different labels
      From previously separate groups
Loading
Scanning a real wallet β€” the details
flowchart LR
    A["Derive receive #0…n<br/>and change #0…n"] --> B{"Address has<br/>history?"}
    B -- yes --> C["Fetch all pages<br/>until count matches"] --> D["Extend window:<br/>last used + gap"]
    B -- no --> E{"20 unused<br/>in a row?"}
    D --> A
    E -- no --> A
    E -- yes --> F(["Done βœ…"])
    C -. "429 / timeout" .-> G["Back off & retry<br/>or fail over to<br/>next provider"] -.-> C
Loading
  • Receive and change chains are scanned in parallel, each with its own worker pool
  • Unused addresses cost one cheap stats request
  • History pagination is checked against the provider's own transaction count, so nothing is silently dropped
  • Retry-After is honoured; each request times out after 15 s so a dead host fails over fast
  • Results are cached per descriptor, so reopening shows coins instantly while a refresh runs

πŸ” Trust model

flowchart LR
    subgraph Never["❌ Satlas never"]
        direction TB
        N1["sees your seed or private keys"]
        N2["signs or broadcasts"]
        N3["runs a server or account"]
    end
    subgraph Does["⚠️ By default"]
        direction TB
        D1["asks a public block explorer<br/>about your derived addresses"]
    end
    subgraph Fix["βœ… To remove that"]
        direction TB
        F1["Settings βš™ β†’ your own<br/>Esplora / mempool server"]
    end
    Does --> Fix
Loading

The block explorer's operator can see which addresses you asked about β€” the same trade-off every watch-only wallet with a public backend makes. Point Satlas at your own server and no third party sees them. Labels and cached scans stay in your browser; Forget wipes both. Full details: docs/trust-model.md.


πŸ—‚οΈ Project layout

satlas/
β”œβ”€β”€ crates/
β”‚   β”œβ”€β”€ satlas-core/        πŸ¦€ Pure Rust engine β€” no network, no WASM, reusable as a library
β”‚   β”‚   └── src/
β”‚   β”‚       β”œβ”€β”€ descriptor.rs    xpub / zpub / descriptor parsing, address derivation
β”‚   β”‚       β”œβ”€β”€ wallet.rs        transactions β†’ UTXOs, balance, tx classification
β”‚   β”‚       β”œβ”€β”€ heuristics/      clustering Β· reuse Β· change detection Β· labels
β”‚   β”‚       β”œβ”€β”€ simulate.rs      coin selection + linkage analysis
β”‚   β”‚       └── bip329.rs        label import / export
β”‚   └── satlas-wasm/        wasm-bindgen bindings
β”œβ”€β”€ web/
β”‚   └── src/
β”‚       β”œβ”€β”€ api/            Esplora client + gap-limit scanner
β”‚       β”œβ”€β”€ engine/         typed facade over the WASM engine
β”‚       β”œβ”€β”€ state/          zustand stores (wallet Β· labels Β· settings Β· cache)
β”‚       β”œβ”€β”€ components/     React UI (CoinGraph, Simulator, …)
β”‚       └── demo/           synthetic demo wallet
└── docs/                   trust model, screenshots, banner

πŸ› οΈ Development

Prerequisites

Tool Why Install
Rust stable + wasm32-unknown-unknown the engine rustup.rs (pinned by rust-toolchain.toml)
wasm-pack Rust β†’ npm package cargo install wasm-pack
clang compiles secp256k1 for WASM Windows: winget install LLVM.LLVM + add C:\Program Files\LLVM\bin to PATH
Node 22+ and pnpm the UI npm i -g pnpm

Commands

Command Where What
cargo test repo root engine tests
pnpm wasm web/ release build of the engine β†’ web/src/wasm/pkg
pnpm wasm:dev web/ faster, unoptimised engine build
pnpm dev web/ dev server on http://localhost:5173
pnpm test web/ UI + WASM-boundary tests
pnpm typecheck web/ TypeScript strict check
pnpm build web/ production build β†’ web/dist

Tip

Re-run pnpm wasm whenever you change Rust code.

Live network test against a real mainnet wallet
SATLAS_NETWORK_TESTS=1 pnpm test
SATLAS_NETWORK_TESTS=1 SATLAS_ESPLORA=https://blockstream.info/api pnpm test

Runs import β†’ scan β†’ analyse end to end on the public BIP-84 test-vector wallet.

Troubleshooting
Error Fix
failed to find tool "clang" Install LLVM and put C:\Program Files\LLVM\bin on PATH
Bulk memory operations require bulk memory Already handled by the wasm-opt flags in crates/satlas-wasm/Cargo.toml
pnpm won't run esbuild's install script pnpm approve-builds esbuild
npm.ps1 cannot be loaded (PowerShell) Set-ExecutionPolicy -Scope CurrentUser RemoteSigned

⚠️ Limitations

Note

Satlas describes what a typical chain-analysis observer would infer. It is not a privacy guarantee, and CoinJoin / PayJoin transactions can defeat these heuristics.

  • Observer-side change detection covers the common one payment + change shape; batched spends aren't analysed
  • Fee estimates assume p2sh inputs are wrapped segwit
  • The Avoid change search considers at most 16 coins
  • A first scan of a large wallet over a public API can take tens of seconds

πŸ—ΊοΈ Roadmap

  • Descriptor & xpub import, UTXO discovery
  • Clustering, reuse and change heuristics with certainty levels
  • Pre-spend simulator with six strategies
  • BIP-329 export & import
  • Relationship map
  • Self-hosted endpoint, caching, failover
  • Publish satlas-core as a standalone crate
  • Incremental refresh (only re-check addresses with new activity)
  • Change analysis for batched spends
  • Import a PSBT to check the exact transaction your wallet built

Satlas β€” because "which coin should I spend?" is really "what will spending this reveal?"

MIT License Β· built with πŸ¦€ Rust, βš›οΈ React and β‚Ώ Bitcoin

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages