
Build a Lightning Address Wallet with Coco and npub.cash
Building a Lightning Address Wallet with Coco and npub.cash
If you want a Lightning Address that behaves like an actual wallet, not just a forwarding alias, Coco + npub.cash is a clean way to get there. Coco gives you a local Cashu wallet with SQLite persistence, and npub.cash provides a free Lightning Address tied to a Nostr key. The combo is simple: generate keys, hook Coco into npub.cash, and listen for incoming quotes.
This walkthrough keeps everything local and script-driven. You’ll end up with a npub1...@npubx.cash address that receives sats and logs incoming payments as they settle.
1. Initiate the project
Create a fresh Bun project so we can run a single TypeScript file:
mkdir lud16wallet
cd lud16wallet
bun initIf Bun is new for you, it’s just a fast runtime + package manager. The goal is to keep the demo tiny and frictionless.
2. Install dependencies
We need Coco (core + SQLite storage), the npub.cash plugin, plus Nostr and mnemonic tooling:
bun install coco-cashu-core@rc coco-cashu-sqlite3@rc sqlite3 coco-cashu-plugin-npc @scure/bip39 nostr-toolsThese are all small, battle-tested pieces. Nothing exotic. The only persistent piece is a SQLite DB in your project folder.
3. Create the wallet script
Create index.ts and drop in the following code:
import { generateMnemonic, mnemonicToSeed } from "@scure/bip39";
import { wordlist } from "@scure/bip39/wordlists/english.js";
import { initializeCoco } from "coco-cashu-core";
import { NPCPlugin } from "coco-cashu-plugin-npc";
import { SqliteRepositories } from "coco-cashu-sqlite3";
import { finalizeEvent, getPublicKey, nip19 } from "nostr-tools";
import { privateKeyFromSeedWords } from "nostr-tools/nip06";
import { Database } from "sqlite3";
const mnemonic = generateMnemonic(wordlist);
const seed = await mnemonicToSeed(mnemonic);
const secretKey = privateKeyFromSeedWords(mnemonic);
const npub = nip19.npubEncode(getPublicKey(secretKey));
const npcPlugin = new NPCPlugin(
"https://npubx.cash",
async (event) => finalizeEvent(event, secretKey),
{ useWebsocket: true },
);
const db = new Database("coco.db");
const repo = new SqliteRepositories({ database: db });
const coco = await initializeCoco({
repo,
seedGetter: async () => seed,
plugins: [npcPlugin],
});
coco.on("mint-quote:redeemed", (payment) => {
console.log("Received a payment:", payment.quote.amount);
});
console.log("Lightning Address:", `${npub}@npubx.cash`);A few things to understand here:
- Mnemonic + seed give us deterministic keys, so the address stays consistent if you keep the words.
- npub.cash is just a Lightning Address gateway for Nostr keys. It uses your signed events to prove ownership.
- Coco handles mint quotes and redemption behind the scenes. You don’t need to manually poll anything.
If you want a stable wallet, save the mnemonic somewhere safe. Right now this script prints a new address every run.
4. Run the wallet
bun run index.tsYou should see output like:
Lightning Address: npub1...@npubx.cash
Send sats to that Lightning Address. When the payment settles, Coco fires the mint-quote:redeemed event and logs the amount.
What you just built
You now have a local Lightning Address wallet that:
- Receives payments directly via
npubx.cash - Persists balances in SQLite (
coco.db) - Uses deterministic keys from a mnemonic
- Emits events as payments settle
This is a clean foundation for anything bigger: a CLI wallet, a lightweight backend, or a local daemon that updates your UI in realtime.
If you want the next step, add a file to store the mnemonic on first run, then rehydrate it on later runs. That’s enough to turn this script into a usable wallet you can keep.