@powforge/semantic-kernel-l402
v0.1.0
Published
L402 Lightning payment middleware for Microsoft Semantic Kernel (JavaScript SDK) — wrap any async KernelFunction method with a pay-per-call Lightning gate backed by LNBits.
Downloads
142
Maintainers
Readme
@powforge/semantic-kernel-l402
L402 Lightning payment middleware for Microsoft Semantic Kernel
(JavaScript SDK). Wrap any async KernelFunction-shaped method with a
pay-per-call Lightning gate backed by LNBits.
Install
npm install @powforge/semantic-kernel-l402Requires Node 18+ (for global fetch). @microsoft/semantic-kernel is
an optional peer dependency — this package is a pure wrapper and works
without Semantic Kernel installed (use the bare wrapKernelFunctionWithL402
for any async function).
Use
Wrap an async method
const { wrapKernelFunctionWithL402 } = require('@powforge/semantic-kernel-l402');
const summarize = async (text) => `summary of ${text}`;
const paidSummarize = wrapKernelFunctionWithL402(summarize, {
lnbitsUrl: 'https://lnbits.example',
lnbitsApiKey: process.env.LNBITS_INVOICE_KEY,
satsAmount: 21,
});
// First call — no proof, returns a 402 envelope.
const challenge = await paidSummarize('hello world');
// {
// error: 'payment_required',
// invoice: 'lnbc...',
// payment_hash: 'abc123',
// sats: 21,
// next_step: 'Pay the invoice, then re-call with the payment_hash as paymentProof.'
// }
// Pay the invoice externally, then re-call with the payment_hash:
const result = await paidSummarize('hello world', 'abc123');
// "summary of hello world"Register a paid KernelFunction on a plugin
const { createL402KernelFunction } = require('@powforge/semantic-kernel-l402');
const { KernelFunction } = require('@microsoft/semantic-kernel');
const spec = createL402KernelFunction({
name: 'paid_summary',
description: 'Summarizes any text. Costs 21 sats per call.',
func: async (text) => `summary of ${text}`,
lnbitsUrl: 'https://lnbits.example',
lnbitsApiKey: process.env.LNBITS_INVOICE_KEY,
satsAmount: 21,
});
// Register on a kernel plugin via the canonical fromMethod path:
const kf = KernelFunction.fromMethod(spec.func, {
name: spec.name,
description: spec.description,
});
// kernel.addPlugin(...kf) — or attach to a plugin class — per your SK setup.Because KernelFunction.fromMethod invokes its method with a single
input argument, callers smuggle the payment proof through a JSON
envelope:
await spec.func(JSON.stringify({
__payment_proof__: 'abc123',
__tool_input__: 'hello world',
}));
// "summary of hello world"Payment flow
- Caller invokes the wrapped kernel function with no proof.
- Wrapper mints a Lightning invoice via LNBits and returns a 402
envelope
{ error: 'payment_required', invoice, payment_hash, sats }. - Caller pays the invoice over Lightning.
- Caller re-invokes the function with
payment_hashas the proof. - Wrapper checks LNBits for paid status (cached after first success)
and either executes the original function or returns
{ error: 'invoice_not_paid', payment_hash }.
Configuration
| Option | Required | Default | Description |
| --- | --- | --- | --- |
| lnbitsUrl | yes* | — | LNBits instance base URL |
| lnbitsApiKey | yes* | — | Invoice/read key (NOT admin) |
| satsAmount | no | 10 | Sats charged per call |
| memo | no | semantic-kernel-l402 | Invoice memo |
| fetchImpl | no | globalThis.fetch | Injectable fetch |
| createInvoiceFn | no | LNBits adapter | Override the invoice minter |
| checkPaidFn | no | LNBits adapter | Override the paid-check function |
| state | no | new in-memory | Shared payment cache |
* Required unless you inject createInvoiceFn + checkPaidFn directly
(useful for tests or non-LNBits backends).
Error envelopes
The wrapper never throws on the payment path — it returns a structured object so a Semantic Kernel planner / agent can read the error from the function output.
| error | Meaning |
| --- | --- |
| payment_required | No proof; pay the invoice and retry |
| invoice_not_paid | Proof points at an unpaid hash |
| payment_provider_unavailable | LNBits invoice mint failed |
| payment_verifier_unavailable | LNBits paid-check failed |
Why payment_hash, not preimage?
This package accepts the LNBits payment_hash as the "proof" rather
than the canonical L402 preimage. Trade-off:
- The hash is a hex string Semantic Kernel functions can carry through their single-argument method interface without binary serialization.
- LNBits' read endpoint is the authoritative oracle — the wrapper consults it on every uncached request.
- For full preimage-based macaroon verification (the canonical L402
flow) use
@powforge/mcp-l402-gate.
Sibling packages
| Package | Framework |
| --- | --- |
| @powforge/langchain-l402-middleware | LangChain.js |
| @powforge/paymcp-l402-provider | paymcp |
| @powforge/mcp-l402-gate | MCP (full macaroon flow) |
License
MIT
