Client payments
Client payments
Section titled “Client payments”Client payments are implemented as middleware around your client transport.
When a server replies with notifications/payment_required, the payments layer:
- selects a
PaymentHandlerfor the PMI - asks the handler to pay
pay_req - continues waiting for the original request completion
Handlers
Section titled “Handlers”A PaymentHandler implements one PMI (one payment rail). The built-in handler supports Lightning BOLT11 over NWC.
import { LnBolt11NwcPaymentHandler, withClientPayments,} from '@contextvm/sdk/payments';
const handler = new LnBolt11NwcPaymentHandler({ nwcConnectionString: process.env.NWC_CLIENT_CONNECTION!,});
const paidTransport = withClientPayments(baseTransport, { handlers: [handler],});PMI advertisement (pmi tags)
Section titled “PMI advertisement (pmi tags)”CEP-8 allows clients to advertise which PMIs they can pay by including pmi tags on requests.
Why it matters:
- If the server supports multiple PMIs, your tags help it select a compatible one.
- The order expresses preference (first = most preferred).
When you wrap a transport with withClientPayments(...), the SDK will automatically inject pmi tags derived from your configured handler list.
Payment rejection
Section titled “Payment rejection”If the server rejects a request without charging, it emits notifications/payment_rejected correlated to the request.
This is a policy outcome (no invoice, no settlement).
Your app should typically:
- stop retrying the same request (unless the policy might change)
- surface the optional message to users/operators
Client-side payment policy (paymentPolicy)
Section titled “Client-side payment policy (paymentPolicy)”You can optionally intercept notifications/payment_required locally and decide whether the client should proceed with payment.
This is designed for:
- interactive UX (UI prompts)
- LLM-driven consent
- headless “Code Mode” policies (budgets / allowlists)
import { withClientPayments, type PaymentHandlerRequest,} from '@contextvm/sdk/payments';
const paidTransport = withClientPayments(baseTransport, { handlers: [handler], paymentPolicy: async ( req: PaymentHandlerRequest, ctx, ): Promise<boolean> => { // ctx?.method examples: "tools/call", "prompts/get", "resources/read" // ctx?.capability examples: "tool:add", "prompt:welcome", "resource:greeting://alice" if (req.amount > 500) return false; if (ctx?.capability === 'tool:expensive_tool') return false; return true; },});Decline behavior (fail-fast)
Section titled “Decline behavior (fail-fast)”When paymentPolicy returns false, the SDK will fail the original request immediately (instead of hanging until timeout) on transports that support correlation (such as NostrClientTransport).
The synthesized JSON-RPC error uses:
error.code = -32000error.message = "Payment declined by client policy"error.dataincludes{ pmi, amount, method, capability }when available
Similarly, if a handler’s canHandle returns false, the SDK will fail-fast with "Payment declined by client handler" when correlation exists.
Multiple handlers
Section titled “Multiple handlers”You can configure multiple handlers if you support multiple payment rails.
const paidTransport = withClientPayments(baseTransport, { handlers: [ lightningHandler, // futureHandler, ],});The first handler that matches the server-selected PMI is used.