A private wallet for every site you connect, right in your browser. Kivuli derives a distinct, unlinkable account for each dApp origin and receives money at addresses only you can connect to yourself, built on the Opaque protocol stealth-address rails. Base mainnet by default, with Ethereum Sepolia available behind developer options for free end-to-end testing.
Beta. Distributed as a GitHub-release zip, loaded unpacked. Unaudited. On Base it holds real money; read the security model below and never keep more in a beta wallet than you are testing with.
- A wallet per site. Connecting to a dApp derives a fresh wallet for that origin alone, exposed over EIP-6963. Sites never see your other money or each other, and because each wallet is funded from its own stealth note, no shared funding address links them on-chain either. That second half is what makes the unlinkability real rather than cosmetic; it is also the part no shipping wallet does today.
- Private receiving. Receive hands out plain
0x…addresses from a seed-derived ladder, so any ordinary wallet can pay you, plus a meta-address for stealth-aware senders. Each payment lands at a fresh one-time address that only you can find, and sweeps where you want it. - Honest approvals. Transactions are decoded into what actually changes for you; token allowances get editable spending caps; unlimited approvals (including the Permit2 sentinel) are refused; suspicious sites are interrupted before they can ask.
- A full surface, not just a popup. The same wallet runs in the popup, Chrome's side panel, and an expanded tab. USDC on Base is tracked out of the box via the curated token registry.
- One secret. Everything (the meta-address, every receive address, every per-site wallet) derives from a 12-word phrase. The backup ceremony verifies the words before the wallet activates, because on this distribution channel the phrase is the recovery path.
- Keys stay in the service worker. The master secret exists only in service-worker memory while unlocked. The vault is encrypted under your passcode; the scan cache and the origin-to-address map are encrypted at rest under keys derived from the unlocked wallet, so a locked wallet serves nothing.
- Isolation per origin. Each dApp origin gets its own account, and spending from it requires that origin's connection.
- A small, unprobeable surface. Install-time host permissions cover only
the built-in RPC endpoints (custom ones are granted individually), and the
build strips
web_accessible_resources, so pages cannot probe for Kivuli's presence. - Not audited yet. There has been no external audit. Vulnerability reports go through the private path in SECURITY.md.
- Download
kivuli-extension-v<version>.zipfrom the latest GitHub release. - Verify it if you like: each release ships
SHA256SUMS.txtand a GitHub build-provenance attestation (gh attestation verify <zip> --repo kivuliapp/extension). - Unzip into a folder you will keep, then
chrome://extensions, Developer mode, Load unpacked.
Unpacked installs never auto-update, and removing the extension deletes the wallet vault. Before updating anything, read docs/release.md: it is short, and the update procedure in it is what keeps your vault alive.
Node 20 (.nvmrc).
npm ci
npm run build # typecheck + production build + hardening → dist/
npm test # full vitest suiteLoad dist/ as an unpacked extension via chrome://extensions (Developer
mode). Other useful scripts:
npm run dev # popup UI on mock data (no extension APIs needed)
npm run storybook # component workbench
npm run demo:dev # the test bench (see below) at :5173
npm run demo:build # bench → demo-dist/, deployable as a static site
npm run release # dist/ packaged as the release zip, with verificationStealth receiving needs a counterparty, so a fresh install has nothing to
show until somebody pays you. demo/ is that somebody: a standalone page
that pays a meta-address from a funded wallet and doubles as the dApp for
provider testing. It has two modes, picked at the top of the page:
- Ethereum Sepolia (default): free, full battery, including deploying a mintable ERC-20 and the adversarial signature payloads.
- Base mainnet: real money, clearly labeled. Nothing is deployed; token buttons run against canonical Base USDC with cent-scale amounts.
The loop: load dist/ unpacked, copy your meta-address from Receive,
fund a separate wallet, open the bench, pay yourself, watch the note land in
Kivuli, sweep it. Then connect the bench to Kivuli and run the smoke
tests: the address it reports should be unique to that origin.
docs/runtime-qa.md is the full first-load checklist (free, Sepolia);
docs/runtime-qa-base.md is the budgeted real-money pass on Base. Host the
bench (npm run demo:build, any static host) before handing the extension
to testers.
This build is deliberately narrow. Scope is decided in exactly two places:
ENABLED_CHAINS in src/lib/config.ts and capabilities() in
src/ui/services/bridge.ts. The UI reads both rather than guessing:
- EVM only, one network at a time. Base mainnet by default; Ethereum Sepolia behind Settings, Advanced, Developer options. dApp-driven network switching is refused; switching is a user decision.
- Local vault, no accounts server. Onboarding is a passcode plus a
12-word recovery phrase (
capabilities.localVault). The Kivuli login model (@handles, pay links, nothing to write down) lands with the server, behindkivuliLogin. - ETH moves, tokens show. Receive, sweep, and send work for native ETH. ERC-20 payments (USDC included) are detected and shown, but cannot be moved yet; they wait on the gasless rail. No Solana surface, no ENS, no custom networks.
src/background/: the MV3 service worker. Approvals queue, request decoding, origin tracking, RPC proxy, scanning and funding rails, keyring.src/content/+src/inpage/: the EIP-6963 provider bridge, re-bundled as self-contained IIFEs byscripts/finalize-dist.mjsso no web-accessible resources ship.src/ui/: popup, side panel, and approval UI over aWalletServiceseam (services/bridge.tsis the real one;services/mock.tspowers dev and Storybook).demo/: the test bench, its own vite root.
The docs go deeper: docs/release.md (release process and the pinned extension ID), docs/runtime-qa.md and docs/runtime-qa-base.md (runbooks), docs/service-delta.md (the service seam's design record), and docs/ux-research.md (design brief).
See CONTRIBUTING.md for setup, test expectations, commit style, and the recommended branch protection. Bugs and feature requests go through the issue forms; vulnerabilities go through SECURITY.md, never a public issue.
GPL-3.0-or-later.