Skip to content

feat(payments): negotiate CEP-8 payment interaction on the client transport - #108

Open
harsh04044 wants to merge 1 commit into
ContextVM:mainfrom
harsh04044:feat/cep8-client-negotiation
Open

feat(payments): negotiate CEP-8 payment interaction on the client transport#108
harsh04044 wants to merge 1 commit into
ContextVM:mainfrom
harsh04044:feat/cep8-client-negotiation

Conversation

@harsh04044

Copy link
Copy Markdown

Part of #100

Fifth piece of CEP-8, after the server negotiation in #107. Client half of the same handshake, so both sides can now be driven against each other in one process.

The client advertises its payment methods (pmi) and its requested interaction mode (payment_interaction) on outbound requests, and records the effective mode the server discloses back. No payment flow yet: nothing reacts to payment_required, retries, or ships a handler. That's with_client_payments. A client with no payments config publishes the same tags it did before, asserted on a published event rather than argued.

What's here

  • ClientNegotiationState behind one std::sync::Mutex, seeded from two new config fields
  • get_pending_negotiation_tags, wired in as the third argument of compose_outbound_tags, gated on is_request like the discovery tags
  • The latch, flipped after a successful publish at both publish sites
  • set_payment_interaction / set_client_pmis / get_effective_payment_interaction
  • The inbound learner in learn_server_discovery, recording the disclosed mode only when this client asked for explicit_gating
  • 18 integration tests in a new tests/payments_negotiation_e2e.rs, 4 unit tests

One Cargo.toml line for the new test file's required-features. No new dependency, no FFI change.

Notes

  • Why does one getter carry three different rules? The tags have three different lifetimes and folding them into one condition is how you get it wrong. pmi rides every request and never latches; payment_interaction goes out only when the requested mode differs from the one last published; neither configured means nothing at all. A requested transparent is emitted explicitly, so a downgrade stays distinguishable from "no preference".
  • Why flip the latch in two places? Same shape as ts. Miss the oversized site and an oversized first request re-sends the mode on every later request; miss the normal one and it's never re-sent. Only the oversized path tells those apart, so each has its own test. The emitted mode is threaded down by value rather than re-read at the flip, so a set_payment_interaction landing mid-publish can't latch a mode that never went out.
  • Why is src/proxy/mod.rs in the diff? Two lines, test-only. It builds NostrClientTransportConfig as a full struct literal in a unit test, where #[non_exhaustive] doesn't protect it.

Heads up

Three deliberate differences from ts, each documented on the item. The latch stores the last published mode instead of a bool, so setting a mode and setting it back with no publish in between emits nothing where ts sends a redundant upsert. The observed mode isn't cleared on close(), matching what we already do for the CEP-35 state and unreachable anyway since the client transport can't be restarted. And a client that downgrades itself freezes its observed mode, which is exact ts parity: resetting it unilaterally would put the two SDKs' getters out of step on the same wire trace.

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