A self-custody, multi-chain testnet wallet for Chrome — that dapps, x402 services, and AI agents can pay from, but never control.
Keys never leave the extension. Every payment is capped, prompted, and audit-logged.
Veilpay is a Manifest V3 Chrome extension that holds your own keys and speaks
three chains at once — EVM (Sepolia), Solana (devnet), and
Stellar (testnet). It exposes a normal wallet provider to dapps, answers
x402 payment challenges inside fetch, and ships an
MCP server so Claude and ChatGPT can
check balances and send testnet payments on your behalf — always inside
spending caps you set, and with approval prompts for anything above them.
Testnet only. Sepolia ETH, devnet SOL, testnet XLM. There is no mainnet path — by design, this is a place to experiment safely.
| 🔐 Self-custody | HD wallet (BIP-32/39), AES-GCM encrypted vault in IndexedDB, auto-lock |
| ⛓️ Three chains | EVM + Solana + Stellar accounts from one seed |
| 🌐 Every network | Mainnets and testnets — Ethereum, Base, Polygon, Arbitrum, Optimism, BNB, Avalanche, Solana, Stellar — plus custom RPCs; the selection persists per chain |
| 🕵️ Stealth payments | Stellar stealth sends: one-time addresses derived from the recipient's published meta-address, unlinkable on-chain (src/core/privacy/) |
| 🏊 Privacy pool (pXLM) | Shield XLM into a Soroban privacy pool, transfer privately by G… address, unshield on demand — Groth16 proofs, UTXO-style notes (src/core/spp/) |
| ➕ Custom tokens | Add ERC-20s, SPL mints, and Stellar assets (with trustlines) like any wallet |
| 🔗 WalletConnect | Pair with any WalletConnect dapp from the panel |
| 🖥️ Dapp provider | window.veilpay, EIP-1193 ethereum, and solana providers in pages |
| 💸 x402 payments | HTTP 402 payment challenges intercepted and settled from the wallet |
| 🤖 AI agent payments | MCP server (mcp/), hosted relay (relay/), OAuth pairing code |
| 🧢 Spending caps (VAP) | Per-origin grants with maxPerWindow limits, prompt above threshold |
| 🔔 Human approvals | Every transaction above your threshold opens an approval overlay |
| 📜 Audit ledger | Hash-chained, append-only record of every sensitive operation |
| 🚰 Built-in faucet | Fund every testnet from the extension |
A recipient publishes a stealth meta-address (two ed25519 points) on their account once — Receive → Private Payments → Enable. After that, any Veilpay payer can toggle Private payment on a Stellar XLM send and the funds land on a fresh one-time address that only the recipient can unlock:
- The payer's derivation and the announcement ride in the transaction's
MEMO_HASH(32 bytes). - The receiver scans recent announcements with their view key (the Monero model) and claims with an account merge — the whole balance, no dust.
- Keys are derived from the seed at
m/8888'/0'andm/8888'/1', so the recovery phrase restores private funds too.
A second, stronger privacy tier: shield public XLM into a Soroban privacy pool and spend it as private notes.
- Shield public XLM into pXLM notes — UTXO-style commitments in an on-chain Merkle tree that only you can read or spend.
- Transfer pXLM to any registered
G…address: the public ledger records only that the pool was used, not who paid whom or how much. - Unshield pXLM back into the wallet's public XLM balance any time.
- Every spend carries a Groth16 zero-knowledge proof; nullifiers prevent double-spends. Proving runs in the extension's offscreen document, and the private keys never leave the service worker — the prover asks the vault to sign each operation.
- Built on the Nethermind "Stellar Private Payments" reference contracts (our own testnet deployment); pool keys are derived from the wallet's own signature, so the recovery phrase restores private notes too.
The pool lives on Stellar testnet (the wallet refuses pool operations on
other networks). Two e2e proofs: npm run build && node scripts/spp-smoke/extension-e2e.mjs creates a wallet, funds it, registers
it, and drives shield → private transfer (to itself and to a second
registered wallet) → unshield through the real extension path; node scripts/spp-smoke/twowallet-drive.mjs proves the recipient side — two fresh
wallets, one private transfer, and the recipient's pool balance and unspent
notes show exactly the amount sent.
Prerequisites: Node.js 20+ and Chrome/Chromium.
git clone https://github.com/chiragchanchal/VeilpayExtension.git
cd VeilpayExtension
npm install
npm run buildThen load it into Chrome:
- Open
chrome://extensions - Enable Developer mode (top-right toggle)
- Click Load unpacked → select the
dist/folder - Pin Veilpay to your toolbar
First run: create a vault, save the seed phrase somewhere safe, and use the built-in faucet to fund your testnet accounts.
The extension ships everything needed for agent payments — no extra install.
- In the extension: Settings → Agent → Connect (relay mode). The panel shows an 8-character pairing code and copies the connector URL.
- In Claude: Settings → Connectors → Add custom connector. In ChatGPT: Settings → Connectors → Add MCP server.
- Paste
https://veilpay-relay.onrender.com/mcp(the default relay) — or your own deployment from the next section. - When the relay's pairing page opens, type the code from the extension, and approve.
You ask, the agent calls a tool, the wallet decides:
You: what's my Solana balance? Claude: (calls
get_balance) 4.25 SOL on devnet. You: send 0.5 to9xQe…Claude: (callssend_payment) Sent — the wallet asked you to approve it first, because it's above your cap.
Six tools are exposed: wallet_status, list_accounts, get_balance,
send_payment, list_grants, revoke_grant. Spending caps and approval
prompts apply to the agent exactly as they do to a dapp.
Full guide — local mode (Claude Code / Claude Desktop), relay mode, and troubleshooting: docs/MCP_SETUP.md
Prefer not to use the default hosted relay? Deploy your own in one click on Render's free tier:
Zero-dependency Node — render.yaml and a Dockerfile are included for any
other host. The relay only moves messages: it never sees keys, and cannot
sign anything. Design and threat model:
docs/AGENT_PAYMENTS.md.
┌────────────────┐ provider + approvals ┌──────────────────────────┐
│ dapp (page) │ ──────────────────────► │ content + inpage bridge │
└────────────────┘ └───────────┬──────────────┘
┌────────────────┐ x402 challenge ┌───────────▼──────────────┐
│ x402 service │ │ background worker │
└────────────────┘ │ vault · chains · grants │
┌────────────────┐ MCP over relay/stdio │ approvals · audit ledger│
│ Claude / GPT │ ──────────────────────► └──────────────────────────┘
└────────────────┘ (asks; never signs)
src/background/— service worker: message router, vault ops, chain clients, approval flows, agent bridgesrc/content/,src/core/x402/— page provider and the fetch interceptorsrc/core/vault/— AES-GCM encrypted key storage (PBKDF2 + WebCrypto)src/core/chains/— EVM, Solana, and Stellar transaction building/signing on @noble/curvessrc/ui/,src/popup/,src/sidepanel/,src/options/— React 19 + Tailwind surfacesrelay/— the hosted agent relay (OAuth + long-poll bridge, zero deps)mcp/— the local MCP server for Claude Code / Claude Desktopdocs/— architecture · agent payments · MCP setup · setup
- Keys stay home. Private keys live only in the extension's encrypted vault and are never exported, logged, or sent to any dapp, relay, or server.
- The relay and MCP server cannot sign. They relay requests; the extension enforces caps, prompts, and signs locally.
- Privileged actions are origin-gated. Vault unlock, pairing, and grant changes are only accepted from the extension's own UI, verified by Chrome-stamped sender fields — never from a page.
- Approvals are transient. Pending approvals live in session storage and are cancelled when the vault locks.
npm run dev # Vite watch mode → dist/
npm run test # full suite (512 tests)
npm run lint # ESLint, zero warnings allowed
npm run typecheck # strict TypeScript
npm run build # typecheck + production build
npm run gate:all # everything: types, lint, tests, build, size, secrets
npm run smoke:agent # live end-to-end check of the relay + MCP + OAuth stack
npm run sim:pairing # simulates the full pairing flow, incl. relay restarts
npm run x402:server # local x402 reference server for manual testingA dapp playground for the provider surface is included: run npm run dev
and open http://localhost:5173/dapp-demo.html — it exercises connect,
sign, send, and a full x402 payment against the wallet.
- Read docs/ARCHITECTURE.md first
- Follow the message protocol in
src/core/messaging/protocol.ts - Add tests for anything you change —
npm run gate:allmust pass - Keep the bundle lean:
npm run build:check-sizeenforces the budget
MIT — clone it, run it, build on it.