contextvm-sdk 0.1.1

Rust SDK for the ContextVM protocol — MCP over Nostr
Documentation
---
title: Client payments
description: Configure handlers, PMI support, and client behavior for CEP-8 paid servers
---

# Client payments

Client payments are implemented as middleware around your client transport.

When a server replies with `notifications/payment_required`, the payments layer:

1. selects a `PaymentHandler` for the PMI
2. asks the handler to pay `pay_req`
3. continues waiting for the original request completion

## Handlers

A `PaymentHandler` implements one PMI (one payment rail). The built-in handler supports Lightning BOLT11 over NWC.

```ts
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)

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

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`)

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)

```ts
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)

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 = -32000`
- `error.message = "Payment declined by client policy"`
- `error.data` includes `{ 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

You can configure multiple handlers if you support multiple payment rails.

```ts
const paidTransport = withClientPayments(baseTransport, {
  handlers: [
    lightningHandler,
    // futureHandler,
  ],
});
```

The first handler that matches the server-selected PMI is used.