Skip to content

Repository files navigation

RedDuck

OpenWallet Mobile

A non-custodial wallet for iOS and Android — one seed across EVM, Solana and Bitcoin, with swaps.


OpenWallet Mobile is a React Native port of OpenWallet, carrying over its chain core — a BIP-39 seed, SLIP-0010/BIP-32 derivation, an encrypted-at-rest vault, and per-chain signing behind a single ChainAdapter contract — and dropping everything that needed a server. It runs without one: prices come straight from CoinGecko and swaps straight from LI.FI on EVM and Jupiter on Solana, each over its keyless public tier, with optional API keys in Settings for a higher rate limit. Key material exists only inside withPrivateKey callbacks in the wallet layer and is zeroed when they settle; screens reach the wallet through one facade and never hold a key, a Wallet, or a chain package. The KDF runs full-strength scrypt (N=2¹⁸) through a native OpenSSL binding, because the same parameters in pure JavaScript cost roughly a minute per derivation on Hermes.

Nothing in this repository has been audited. Test networks ship alongside the main ones and are labelled as such.

Built with

Area Technology
Language TypeScript (Node.js ≥ 20, strict, type-aware ESLint)
App Expo SDK 57 + React Native 0.86, React 19, React Navigation
Cryptography @noble/* / @scure/*
KDF react-native-quick-crypto — native scrypt
EVM viem — signing, ERC-20, fee tiers, ENS
Solana @solana/web3.js — SPL, priority fees
Bitcoin bitcoinjs-lib — native SegWit, PSBT, RBF
Prices CoinGecko, called directly
Swaps LI.FI on EVM, Jupiter on Solana, called directly
Storage AsyncStorage for the encrypted vault and app settings
Tooling pnpm workspaces, Vitest, Prettier

How it works

flowchart LR
    A[BIP-39 mnemonic] --> B[SLIP-0010 / BIP-32 derivation]
    B --> C[Encrypted vault]
    C --> D{ChainAdapter}
    D --> E[EVM chains]
    D --> F[Solana]
    D --> G[Bitcoin]
    H[Screens] -->|walletApi| D
    D --> I[JSON-RPC nodes]
    J[CoinGecko] -->|prices| D
Loading
  1. 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, and the password is never persisted, so a cold start always locks.
  2. 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.
  3. Every chain package exposes the same ChainAdapter: derive, validate, quote fees, build, sign, broadcast. Adding a chain doesn't touch the send flow.
  4. The extension split this across a background worker and a popup over a message protocol. A React Native app is one process, so the protocol is gone — but the seam it enforced is kept: screens import wallet-api.ts and nothing below it, which makes "where can key material appear" a question answered by reading imports.

Workspace

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/swap-contract The shapes every aggregator client returns, so the wallet holds no provider-specific parsing
@openwallet/lifi-client LI.FI quotes and token registry for EVM swaps
@openwallet/jupiter-client Jupiter quotes and token search for Solana swaps
@openwallet/mobile The Expo app: onboarding, accounts, networks, send, receive, swap, settings

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. These packages are vendored copies of the extension's, trimmed of the features below, and are not synced automatically.

What the port leaves out

Removed Why
The OpenWallet API No server to run — the app calls CoinGecko, LI.FI and Jupiter directly over their keyless tiers
NFTs (ERC-721/1155, EIP-6551) Out of scope, and cut from the vendored chain-evm rather than merely hidden
Smart accounts (ERC-4337) Needs a hosted bundler and paymaster — the server dependency this port exists to shed
dApp connections (EIP-1193) An injected provider is a browser mechanism; parity on mobile means WalletConnect

Getting started

Prerequisites: Node.js ≥ 20, pnpm 10, and Xcode or Android Studio for the native build.

pnpm install
pnpm build       # compile every package — Metro reads dist, not src
pnpm test        # vitest across the workspace
pnpm check       # format + build + lint + typecheck + test

Build and launch the app. The first run generates the native project and compiles it, which takes a while; later runs take seconds:

pnpm ios
pnpm android

Run these from the repository root rather than from apps/mobile — the root scripts compile the packages first, then hand off to Expo in the right directory. While editing a packages/* file, keep pnpm build:watch running alongside. Expo Go is not enough, since the app links native modules of its own; native projects are generated by expo prebuild and are not checked in.

Notes

  • .npmrc sets node-linker=hoisted. Metro resolves modules by walking real parent directories and cannot follow pnpm's symlinked layout.
  • The vault lives in AsyncStorage, not SecureStore. It is already ciphertext under the user's password, and SecureStore documents values over ~2 KB as unsupported on Android — which a vault holding a seed plus imported keys exceeds.
  • Every API key is optional. Settings → API keys takes CoinGecko, LI.FI and Jupiter keys; each provider has a keyless tier that works, and a key only raises the rate limit. A rate-limited price shows up as a missing price rather than an error.
  • Swaps are same-chain only, EVM through LI.FI and Solana through Jupiter. Bitcoin has no on-chain DEX to aggregate, so it has no swap screen.
  • Testnet mode (Settings) shows only test networks and hides the swap tab; off, only mainnets are listed. Custom networks stay visible either way.
  • Public RPCs throttle. Any network's endpoint can be overridden in the app under Networks.

License

MIT © RedDuck Limited

About

Non-custodial multi-chain wallet for iOS and Android — testnet only, one seed across EVM, Solana and Bitcoin.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages