A non-custodial, multi-chain browser wallet — one seed, one signing model, across EVM, Solana and Bitcoin.
OpenWallet is a browser-extension wallet built in the open: a BIP-39 seed, SLIP-0010/BIP-32 derivation, an encrypted-at-rest vault, and per-chain signing behind a single ChainAdapter contract, so EVM, Solana and Bitcoin are driven by exactly the same send flow. Keys never leave the extension's background worker — dApps talk to it over EIP-1193/EIP-6963, and price, token and swap data comes from a stateless API that never sees a key. Every primitive either wraps an audited library (@noble/*, @scure/*, viem, bitcoinjs-lib, @solana/web3.js) or, where implemented here (SLIP-0010 derivation), is verified against the official spec test vectors. See docs/architecture.md for the security invariants and how to add a chain.
| Area | Technology |
|---|---|
| Language | TypeScript (Node.js ≥ 20, strict, type-aware ESLint) |
| Extension | WXT + React 19, Tailwind CSS 4, Radix UI |
| Cryptography | @noble/* / @scure/* |
| EVM | viem — signing, ERC-20, fee tiers, ENS |
| Solana | @solana/web3.js — SPL, priority fees |
| Bitcoin | bitcoinjs-lib — native SegWit, PSBT, RBF |
| Swaps | LI.FI (EVM) and Jupiter (Solana) |
| Backend | NestJS 11 with Zod request/response contracts |
| Tooling | pnpm workspaces, Vitest, Prettier, Knip |
flowchart LR
A[BIP-39 mnemonic] --> B[SLIP-0010 / BIP-32 derivation]
B --> C[Encrypted vault]
C --> D{ChainAdapter}
D --> E[EVM]
D --> F[Solana]
D --> G[Bitcoin]
H[dApp] -->|EIP-1193 / EIP-6963| D
D --> I[JSON-RPC nodes]
J[OpenWallet API] -->|prices, tokens, swap quotes| D
- A mnemonic is generated or imported, then encrypted into a vault that only decrypts with the user's password — the plaintext seed never touches disk.
- Accounts are derived per chain from that one seed (secp256k1 for EVM and Bitcoin, ed25519 for Solana), each following its chain's SLIP-44 path convention.
- Every chain package exposes the same
ChainAdapter: derive, validate, quote fees, build, sign, broadcast. The UI and the dApp bridge only ever talk to that interface, so adding a chain doesn't touch the send flow. - Signing happens in the extension's background worker behind an auto-lock; the injected page provider relays requests but never holds key material.
| Package | What it is |
|---|---|
@openwallet/core |
Mnemonic, HD/SLIP-10 derivation, encrypted vault, keyring, ChainAdapter |
@openwallet/chain-evm |
EVM derivation, signing, ERC-20 transfers, fee tiers, ENS, speed-up/cancel |
@openwallet/chain-solana |
Solana derivation, signing, SPL transfers, fee tiers |
@openwallet/chain-bitcoin |
Bitcoin (native SegWit) derivation, PSBT signing, RBF, message signing |
@openwallet/api-contract |
Zod schemas shared by the API and its consumers |
@openwallet/lifi-client |
LI.FI quotes and token lists for EVM swaps |
@openwallet/jupiter-client |
Jupiter quotes and token lists for Solana swaps |
@openwallet/extension |
The browser extension: onboarding, accounts, send, swap, dApp approvals |
@openwallet/api |
NestJS service for prices, tokens and swap routing — stateless, key-free |
Dependency direction is chain-* → core only. core never imports a chain package and chain packages never import each other; ESLint enforces it rather than leaving it to convention.
Prerequisites: Node.js ≥ 20 and pnpm 10.
pnpm install
pnpm build # compile every package
pnpm test # vitest across the workspace
pnpm check # format + build + lint + typecheck + knip + testRun the extension in watch mode — WXT opens a browser with it loaded; build instead emits apps/extension/.output/chrome-mv3 to load unpacked yourself:
pnpm --filter @openwallet/extension dev
pnpm --filter @openwallet/extension buildRun the API alongside it:
pnpm --filter @openwallet/api devContributions are welcome — see CONTRIBUTING.md.
MIT © RedDuck Limited