Index Agentica

Give an agent a Lightning wallet with Nostr Wallet Connect

How to let an agent pay Lightning invoices and check its balance through Nostr Wallet Connect (NIP-47), using a budget-capped connection from Alby Hub or LNbits, the Alby JS SDK or the Alby MCP server.

Type
Guide
Author
Agentica Author
Published
Last verified
Difficulty
intermediate
Time
30 min

Prerequisites

Nostr Wallet Connect (NWC) lets an app, or an agent, control a Lightning wallet it does not run. The wallet hands out a connection string with its own key and its own permissions, and the agent sends encrypted payment requests over Nostr relays. The agent never holds node keys or channel state, and you can revoke one connection without touching the others. That makes it a good fit for giving an agent a small, capped allowance.

No real payments were made while writing this guide. The code below was syntax-checked and run against the example connection string in the NIP-47 spec, which has no wallet behind it, so only the error path was exercised.

What NWC is (from NIP-47)

NIP-47 defines three roles: the client (your agent), the wallet service (the software in front of the Lightning node) and one or more relays that carry messages between them. The wallet service publishes an info event of kind 13194 listing the commands it supports. The client sends requests as kind 23194 events and gets responses as kind 23195. Content is encrypted, NIP-44 is preferred and NIP-04 is deprecated, and the two sides agree on the scheme through an encryption tag.

sequenceDiagram
  participant A as Agent (NWC client)
  participant R as Nostr relay
  participant W as Wallet service (Alby Hub / LNbits)
  A->>R: kind 23194, encrypted {"method":"pay_invoice", ...}
  R->>W: forward request
  W->>W: check permissions and budget, pay
  W->>R: kind 23195, encrypted {"result":{"preimage":...}}
  R->>A: forward response
Mermaid diagram (source shown; not rendered client-side)

The connection string is a URI with the wallet's public key, the relay and a secret the client signs with:

nostr+walletconnect://b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.damus.io&secret=71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c

(That is the spec's example. A real one is a spending credential: treat it like an API key.)

The commands an agent mostly needs:

CommandWhat it doesKey fields
pay_invoicePay a BOLT11 invoiceparams invoice, optional amount (msats); result preimage, fees_paid
get_balanceRead the balanceresult balance in msats
make_invoiceCreate an invoice to receiveamount, description
lookup_invoiceCheck an invoice's statuspayment_hash or invoice
get_infoWhich methods this connection may callmethods

Errors come back with a code such as INSUFFICIENT_BALANCE, QUOTA_EXCEEDED, RESTRICTED, UNAUTHORIZED, RATE_LIMITED, NOT_IMPLEMENTED or, for pay_invoice, PAYMENT_FAILED. The spec leaves limits to the wallet: a connection key may carry arbitrary constraints, such as a budget. Optional extras, such as the budget query used below, are kept in the separate NWC extensions repo rather than in NIP-47 itself.

Step 1: create a capped connection

Make a separate connection for each agent, with only the methods it needs and a budget that resets.

Alby Hub

Alby Hub is a self-custodial wallet service (Apache-2.0). In the UI, add an app connection and set its permissions, budget and expiry. The same options are available as query parameters on the /apps/new page, which is handy when a setup script links you to it:

ParameterMeaning
nameLabel for the connection
max_amountBudget in msats per renewal period
budget_renewalnever, daily, weekly, monthly or yearly
request_methodsSpace-separated allowed methods, e.g. pay_invoice get_balance (default pay_invoice)
expires_atWhen the connection stops working
isolated=trueThe connection gets its own balance and transaction list, but no custom methods, budget or expiry

For an agent, allow pay_invoice get_balance get_info, set a daily budget, and give it an expiry. If you want the agent's spending fully separated from the rest of the wallet, use an isolated connection and fund it with only what it may spend; note that this replaces the budget rather than adding to it.

LNbits

LNbits (MIT) supports NWC through the NWC Service Provider extension (nwcprovider, MIT):

  1. Install the extension from the LNbits admin UI.
  2. As admin, open /nwcprovider/admin and set the relay, for example wss://relay.nostrconnect.com.
  3. In a wallet, open the extension, press +, and fill in a description, expiry, permissions and spending limits.
  4. Press Connect to get the pairing URL or QR code.

Use a dedicated LNbits wallet for the agent, so the wallet balance is a second cap on top of the connection limits.

Step 2: connect the agent

Option A: the Alby JS SDK

npm install @getalby/sdk @getalby/lightning-tools

NWCClient from @getalby/sdk/nwc wraps the NIP-47 commands (getInfo, getBalance, payInvoice, makeInvoice, lookupInvoice, listTransactions and more) plus getBudget. This script checks balance and budget, then pays an invoice only if it is under a cap you set in code:

// agent-wallet.mjs: check balance and budget, then pay an invoice only under a hard per-payment cap.
import { NWCClient } from "@getalby/sdk/nwc";
import { Invoice } from "@getalby/lightning-tools";

const MAX_SATS_PER_PAYMENT = 1000; // your own cap, on top of the wallet-side budget

const nwc = new NWCClient({ nostrWalletConnectUrl: process.env.NWC_URL });

try {
  const info = await nwc.getInfo();               // which methods this connection may call
  console.log("methods:", info.methods);

  const { balance } = await nwc.getBalance();     // millisats (NIP-47)
  console.log("balance (sats):", Math.floor(balance / 1000));

  const budget = await nwc.getBudget();           // extension; {} if no budget is set
  if ("total_budget" in budget) {
    console.log(`budget: ${budget.used_budget}/${budget.total_budget} msat, renews ${budget.renewal_period}`);
  }

  const pr = process.argv[2];                     // a bolt11 invoice, if you want to pay one
  if (pr) {
    const invoice = new Invoice({ pr });
    if (invoice.satoshi > MAX_SATS_PER_PAYMENT) throw new Error(`refusing: ${invoice.satoshi} sats > cap`);
    if (invoice.expiryDate && invoice.expiryDate < new Date()) throw new Error("refusing: invoice expired");
    const { preimage, fees_paid } = await nwc.payInvoice({ invoice: pr });
    console.log("paid; preimage:", preimage, "fees (msat):", fees_paid);
  }
} catch (e) {
  console.error("NWC error:", e.code ?? "", e.message); // e.g. QUOTA_EXCEEDED, INSUFFICIENT_BALANCE, RESTRICTED
  process.exitCode = 1;
} finally {
  nwc.close();
}

Run it with NWC_URL="nostr+walletconnect://..." node agent-wallet.mjs to read the balance, or add an invoice as the first argument to pay it. Tested with @getalby/sdk 8.0.3 and @getalby/lightning-tools 9.0.1. getBudget returns used_budget, total_budget, renewal_period and optionally renews_at, or an empty object when the connection has no budget. Amounts in NWC are in millisats; Invoice.satoshi is in sats.

The SDK also has a simpler LN class in @getalby/sdk/lnclient (await new LN(credentials).pay("lnbc...")) if you do not need the raw commands.

Option B: the Alby MCP server

For an agent that speaks MCP, the Alby MCP server exposes the wallet as tools. Its nwc tools are get_balance, get_info, get_wallet_service_info, list_transactions, lookup_invoice, make_invoice and pay_invoice; its lightning tools are fetch_l402, fiat_to_sats, parse_invoice and request_invoice.

Run it locally so the connection secret stays on your machine:

{
  "mcpServers": {
    "alby": {
      "command": "npx",
      "args": ["-y", "@getalby/mcp"],
      "env": { "NWC_CONNECTION_STRING": "nostr+walletconnect://..." }
    }
  }
}

With MODE=HTTP it serves http://localhost:3000/mcp instead, bound to 127.0.0.1. Alby also hosts it at https://mcp.getalby.com/mcp (and /sse), with the connection string sent as a Bearer token, for example in Claude Code:

claude mcp add --transport http alby https://mcp.getalby.com/mcp \
  --header "Authorization: Bearer nostr+walletconnect://..."

The hosted option means Alby receives your connection secret, so give it only a tightly capped connection. For more on wiring servers, see Add MCP servers to Claude Code and Connect an agent to a remote MCP server.

For Lightning agents more broadly, Lightning Labs' Lightning Agent Tools is an alternative route built around its own node software.

Step 3: budgets and safety

How NWC relates to L402 and x402

NWC is a way to control a wallet. L402 and x402 are ways for a server to ask for payment over HTTP 402. They work at different layers, so an agent can use NWC as the wallet behind either.

@getalby/lightning-tools joins these up. fetch402 detects L402, X402 or MPP from the response headers and pays with any wallet that implements payInvoice({ invoice }), which NWCClient does:

import { fetch402 } from "@getalby/lightning-tools/402";
import { NWCClient } from "@getalby/sdk/nwc";

const nwc = new NWCClient({ nostrWalletConnectUrl: process.env.NWC_URL });
const res = await fetch402("https://example.com/protected-resource", {}, { wallet: nwc });
console.log(await res.json());
nwc.close();

fetch402 pays whatever the server asks, so wrap the wallet with your own amount check, as in Step 2, before handing it over. The Alby MCP server's fetch_l402 tool does the same for MCP agents; it does not follow redirects. To compare these rails with cards and stablecoins, see Agent payment rails.

Troubleshooting

Directory entries in this guide

Related

Sources

Machine-readable