
Research
/Security News
77 Firefox Extensions Linked to Crypto Wallet and Credential Theft
Socket uncovered 77 linked Firefox extensions, including 40 that steal wallet secrets or credentials and 37 deceptive sports-score shells.
@bolthub/pay
Advanced tools
The bolthub payments SDK: charge for an MCP tool or HTTP endpoint — and pay for them — in a few lines. Lightning-only (L402) micropayments for AI-agent tool calls
The bolthub payments SDK — both sides of the sale, zero runtime dependencies.
Charge for an MCP tool (or HTTP endpoint) in a few lines, and pay for them
automatically. Lightning-only: settles over the L402 rail. Implements the
bolthub Tool Payment Profile; absorbed
@bolthub/agent (the HTTP L402 client + wallet adapters) in 0.4.0.
Status: the package follows SemVer from
0.1.0; the wire format it speaks (TPP0.1) is a draft and may evolve before 1.0. Breaking wire changes bump the minor version while the package is 0.x.
The agent↔tool protocol standardises what a tool does but has no slot for
what it costs. @bolthub/pay fills that slot: a paid tool answers an unpaid
call with a payment_required challenge, and runs only once the buyer pays the
Lightning invoice and returns a valid proof.
bun add @bolthub/pay
import { createPaywall, l402Rail } from "@bolthub/pay";
// A rail needs a signing secret (≥32 bytes) and something that makes invoices.
// Wrap your wallet (LND / NWC / LNbits / Phoenixd) or a bolthub facilitator.
const pay = createPaywall({
rails: [
l402Rail({
secret: process.env.PAY_SECRET!,
invoiceProvider: {
async createInvoice(amountSat, memo) {
const { invoice, paymentHash } = await myWallet.makeInvoice(amountSat, memo);
return { invoice, paymentHash };
},
},
}),
],
});
// Register it. `resource` defaults to the tool name; a proof is accepted only
// for the resource it was minted against.
pay.tool(
server,
"get_satellite_image",
"Recent high-res satellite imagery for a lat/lon and date.",
schema,
{ price: { amount: 2000 } }, // 2000 sat per call
async (args) => ({ content: [{ type: "text", text: await fetchImage(args) }] }),
);
Or wrap a handler directly (equivalent), if you prefer to call server.tool yourself:
server.tool(
"get_satellite_image",
schema,
pay(
{ price: { amount: 2000 }, resource: "get_satellite_image" },
async (args) => ({ content: [{ type: "text", text: await fetchImage(args) }] }),
),
);
Unpaid call → an error result whose _meta["ai.bolthub/payment"] holds the
challenge:
{
"status": "payment_required",
"price": { "amount": 2000, "asset": "sat" },
"resource": "get_satellite_image",
"offers": [
{ "scheme": "l402", "amount": 2000, "asset": "sat",
"token": "…", "invoice": "lnbc20n1p…" }
],
"expiresAt": 1751400000000
}
The buyer pays an offer, then re-calls the tool with the proof in the
request _meta:
{ "ai.bolthub/payment": { "scheme": "l402", "proof": "<token>:<preimageHex>" } }
The proof verifies → your handler runs.
A payment-blind client just sees a normal tool error ("Payment required: 2000 sat …") and moves on; nothing breaks.
const ad = pay.advertise({ amount: 2000 }); // → { version, price, model, rails }
// attach to the tool's _meta["ai.bolthub/payment"] so an agent can budget first
ToolClient is the seller wrapper's counterpart. It calls a tool, and if it
gets a payment_required challenge it pays the L402 offer and retries, staying
within a budget you set. (Known as PayingClient before 0.3.0; that name
remains as a deprecated alias.)
import { ToolClient, l402Payer } from "@bolthub/pay";
const client = new ToolClient({
payers: [l402Payer({ wallet: myLightningWallet })], // pays L402 invoices
maxTotal: { sat: 10_000 }, // per-asset budget
onPaid: (i) => console.log(`paid ${i.amount} ${i.asset} via ${i.scheme}`),
});
// callTool handles challenge → pay → retry transparently:
const result = await client.callTool(mcpClient, "get_satellite_image", { lat, lon });
l402Payer's wallet is the same WalletAdapter the built-in adapters
implement, so LndWallet, PhoenixdWallet, NwcWallet, etc. drop straight in.
For paywalled HTTP endpoints (a gateway answering 402 Payment Required
with a WWW-Authenticate: L402 challenge), use L402Client — merged in from
@bolthub/agent in 0.4.0. It pays the embedded Lightning invoice and retries
with the proof, caching session tokens between calls:
import { L402Client, LndWallet } from "@bolthub/pay";
const client = new L402Client({
wallet: new LndWallet({ host, macaroon }),
budgetSats: 10_000,
maxPerRequestSats: 100,
});
const resp = await client.get("https://acme.gw.bolthub.ai/v1/weather", {
params: { city: "berlin" },
});
Wallet adapters: LndWallet, LnbitsWallet, PhoenixdWallet, NwcWallet
(pass any NWC connection, e.g. @getalby/sdk's NWCClient), and WebLnWallet
for the browser build. walletFromEnv() builds one from the standard env vars
(LND_REST_HOST/LND_MACAROON, LNBITS_URL/LNBITS_ADMIN_KEY,
PHOENIXD_URL/PHOENIXD_PASSWORD, NWC_URI). attenuate() narrows an L402
macaroon offline to delegate a restricted credential to a sub-agent.
L402Client adds three agent-native behaviors on top of pay-and-retry:
Prepaid bundles. On a per_request endpoint that offers them,
buyBundle(url, n) pays once for an N-use credential; ordinary calls to that
URL then spend it down with no further payment until it runs out. You pass the
size, not the price, so a client can't underpay, and a bundle is scoped to the
one endpoint you bought it for.
await client.buyBundle("https://acme.gw.bolthub.ai/v1/data", 100);
for (let i = 0; i < 100; i++) await client.get("https://acme.gw.bolthub.ai/v1/data");
Free retries on origin failure. When the origin is unreachable or answers
5xx/408/429 after you have paid, your proof stays spendable and the client
re-sends for free. On by default (retryOnUpstreamFailure).
Verifiable receipts. Point the client at a receipt store and every paid call records a preimage-backed receipt you can export and verify offline. Redacted exports keep the expense record but strip the preimage.
import { L402Client, FileReceiptStore } from "@bolthub/pay";
const client = new L402Client({ wallet, receiptStore: new FileReceiptStore() });
// ... paid calls ...
const csv = client.exportReceipts({ format: "csv", redact: true });
Budget is the shared per-asset pool. Hand the same instance to a
ToolClient (MCP-wire payments) and an L402Client (HTTP-402 payments) and
together they can never spend past maxTotal — reservations are synchronous,
so even concurrent calls across the two paths can't jointly overspend:
import { Budget, ToolClient, L402Client, l402Payer } from "@bolthub/pay";
const budget = new Budget({ maxTotal: { sat: 10_000 }, maxPerCall: { sat: 500 } });
const tools = new ToolClient({ payers: [l402Payer({ wallet })], budget });
const http = new L402Client({ wallet, budget });
If you subscribe to bolthub webhooks (invoice.settled, billing.*, and
friends), verifyWebhook checks the delivery signature and returns the parsed
event. It enforces a replay window (default 5 minutes) and uses a
constant-time compare. Every failure throws WebhookVerificationError with a
typed code such as signature_mismatch or timestamp_out_of_tolerance.
import { verifyWebhook, WebhookVerificationError } from "@bolthub/pay";
// Express: use express.raw() so req.body is the raw bytes.
app.post("/webhooks/bolthub", express.raw({ type: "application/json" }), (req, res) => {
try {
const event = verifyWebhook({
secret: process.env.BOLTHUB_WEBHOOK_SECRET!,
payload: req.body,
signature: req.header("x-webhook-signature")!,
timestamp: req.header("x-webhook-timestamp")!,
});
if (event.event === "invoice.settled") {
// event.data is the invoice payload
}
res.sendStatus(200);
} catch (err) {
if (err instanceof WebhookVerificationError) return res.sendStatus(400);
throw err;
}
});
The signature covers the raw request body, so capture it before any JSON parser runs. Node-only: this export is not in the browser build.
The paywall core is rail-agnostic. Implement PaymentRail
(assets, createOffer(price, resource), and verify(proof, ctx)) and pass it
in rails. The core never sees rail-specific bytes, so a new rail is purely
additive.
resource, and time-limited (default 15 min).resource, or any unverifiable proof, means no service.resource mismatch is rejected).See the spec for the wire format and the HTTP transport profile.
MIT
FAQs
The bolthub payments SDK: charge for an MCP tool or HTTP endpoint — and pay for them — in a few lines. Lightning-only (L402) micropayments for AI-agent tool calls
The npm package @bolthub/pay receives a total of 83 weekly downloads. As such, @bolthub/pay popularity was classified as not popular.
We found that @bolthub/pay demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
Socket uncovered 77 linked Firefox extensions, including 40 that steal wallet secrets or credentials and 37 deceptive sports-score shells.

Security News
NIST disclosed an unreleased AI tool called V-etalon and opened a broad inquiry into NVD modernization after years of automation plans produced no public enrichment system.

Security News
In his AI Council 2026 talk, Feross Aboukhadijeh covers recent package compromises, vulnerability discovery, and a more automated security model.