my2sats
← Back to posts
Build a Lightning Address Wallet with Coco and npub.cash

Build a Lightning Address Wallet with Coco and npub.cash

egge·
cococashulud16lightningaddress

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 init

If 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-tools

These 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.ts

You 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.