Skip to content

feat(payments): add the server payments registration entry point and gateway payment options - #113

Draft
harsh04044 wants to merge 7 commits into
ContextVM:mainfrom
harsh04044:feat/cep8-server-payments-registration
Draft

feat(payments): add the server payments registration entry point and gateway payment options#113
harsh04044 wants to merge 7 commits into
ContextVM:mainfrom
harsh04044:feat/cep8-server-payments-registration

Conversation

@harsh04044

Copy link
Copy Markdown

Part of #100

Stacked on #112 (which is stacked on #111). Diff base 74e4d2e. I'll rebase onto main once the parents merge, same as the earlier PRs in this stack.

This adds with_server_payments, the call that actually turns the payment stack on. The middlewares from the last two PRs were only ever constructed by tests; now contextvm_sdk::payments::with_server_payments(&mut transport, options) wires everything up in one place, before start():

  • builds the PMI-to-processor map once and shares it across both middlewares
  • sets the announcement tags: one pmi per processor, one cap per priced capability, and a payment_interaction=explicit_gating tag when the policy is Optional
  • records the negotiation policy
  • registers the transparent middleware, and (under Optional) the explicit-gating one with a fresh authorization store

The property that matters: one bool (policy == Optional) decides the availability tag, the recorded policy, and whether the gating middleware gets registered. So a server can never negotiate a client onto explicit gating without having the middleware that answers it, and a transparent-only server never advertises a mode it rejects.

The gateway gets a payment_options field on GatewayConfig (plus a builder), used in both NostrMCPGateway::new and serve_handler.

Client-side payments (auto-pay, retries) and the docs are the next PR.

The main thing to review: two deliberate divergences from ts

ts's withServerPayments never fails. Ours returns Result and refuses two calls:

  1. Calling it after start(). This works in ts because its middleware list is read on every dispatch. In rs the middlewares and the policy freeze into the event loop at start(), so a late registration would update the announcement tags but register nothing: the server would advertise payments while executing every priced request for free. Hence the hard error.
  2. Calling it twice. In ts this silently double-charges: the tag setters replace but addInboundMiddleware appends, so a second call registers a second middleware pair and every priced request gets two invoices. Not a guess; we ran the real ts function twice in a small spike and saw two createPaymentRequired calls for one request. The rs guard checks the recorded policy, which also means calling the entry point after hand-setting the policy is refused: the entry point owns the policy.

Both checks run before anything is mutated, so a refused call leaves the transport untouched. The two error messages are part of the API now (the gateway and the next PR propagate them).

Also in here

  • Three pub(crate) accessors on the transport (started-ness, session timeout, recorded policy). Nothing new becomes pub.
  • Two leftovers from the transparent-middleware PR, now closed: the notification sender gets the configured payment_ttl as its snapshot TTL instead of the crate default, and a payment_ttl above the session timeout logs a warning once at registration.
  • Like ts, nothing is validated: empty processor or capability lists register fine and fail at request time. The only additions are two log-only warnings (the TTL one above, and priced capabilities with no processors).
  • Tests: 9 e2e tests driving both lifecycles through this entry point over the mock relay, including the CEP-22 reassembled priced call, sweep survival, grant isolation across mid-session mode changes, and the double-registration refusal. Plus 6 unit tests, 2 transport wiring tests, 1 conformance test. One [[test]] Cargo entry, a CHANGELOG bullet. No new dependencies, no FFI change.

Known quirks (same as ts, documented in the rustdoc, not changed)

  • The entry point replaces any caller-set extra announcement tags, and calling the tag setters after registration desyncs announcements from the captured payment senders. Configure tags through the options and let the entry point own them.
  • Duplicate processors emit duplicate pmi tags; only the last one is selectable.
  • A priced capability with no name or an unknown method is charged for but never advertised.

Note

The ts double-registration double charge should be reported to the ts-sdk, alongside the earlier findings (the wire id on gating errors, the ttl: 0 grant, the negotiation latch question).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant