Skip to content

feat: pay contacts via spark - #184

Draft
ovitrif wants to merge 14 commits into
feat/pubky-swaps-routefrom
feat/paykit-service-routes
Draft

ovitrif wants to merge 14 commits into
feat/pubky-swaps-routefrom
feat/paykit-service-routes

Conversation

@ovitrif

@ovitrif ovitrif commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Dependency: #182
Refs:

This PR lets services the wallet holds a balance on, such as Spark, compete with Pubky Swaps providers as legs in the Paykit router.

Description

  • Adds service_legs to PaykitRouteRequest: routes through a service the wallet holds a balance on, each already quoted by the app's engine for that service. A Spark withdrawal (feat: add spark via breez sdk bitkit-android#1697, feat: add spark via breez sdk bitkit-ios#1128) can pay an on-chain-only contact from Spending without a Pubky Swaps provider.
  • Adds PaykitRoute::Service { leg }, which the app runs with that service's own send.
  • When no balance reaches the contact directly, the router keeps the cheapest service leg that delivers exactly the amount to an endpoint the contact published and has not expired, asks the providers for a swap quote, and returns whichever costs less in fees. A swap wins a tie, since its hash lock needs no one's trust. A service leg states who it trusts in atomicity: trusted for Spark.
  • A failed provider search falls back to the service leg instead of failing the route.
  • The router never talks to Spark, which lives only in the apps, so this PR adds no dependency.

Lightning through Spark needs no service leg: the apps count that balance as Lightning-spendable, and Direct covers it. One route still has one leg: a service paying a provider's hold invoice is not routed here.

Cut as the prerelease 0.8.0-swaps.3. synonymdev/bitkit-android#1732 and synonymdev/bitkit-ios#1170 build on it with no legs; synonymdev/bitkit-android#1754 and synonymdev/bitkit-ios#1191 pass Spark legs.

Cross-platform impact

What it needs to ship

  • App adapters: Spark legs are in feat: pay contacts via spark bitkit-android#1754 and feat: pay contacts via spark bitkit-ios#1191.
  • Lifecycle cases first-class in testing: the app killed after commit, after funding and before the claim, and a restore from the seed mid-swap.
  • Timing on real devices: quote to contract, in the foreground and in the background.
  • Device proof: a Spark journey. Spark's hosted regtest runs its own chain without Lightning.
  • Chained legs (a service paying a provider's hold invoice) stay out until single-leg routes have real-device data.

Preview

N/A — router logic only; covered by unit tests.

QA Notes

Setup

N/A — no device testing required: the change is covered by unit tests, and no app passes service legs yet.

Automated Checks

  • added router.rs unit tests: the cheapest service leg to a published endpoint, dropping expired legs and legs that deliver another amount, and the fee comparison with the swap winning a tie
  • added client.rs unit tests: a service route without providers, and the reason when neither exists
  • ran cargo fmt --check, cargo clippy --lib --tests (no findings in pubky_swaps) and cargo test --lib pubky_swaps: pass
  • not run: the ignored live test against a regtest provider, which this PR only extends with an empty service_legs

Models used

  • Planning/scoping: claude-opus-5-5 (reasoning: medium)
  • Implementation: claude-opus-5-5 (reasoning: medium)
  • Review: Not performed

@ovitrif ovitrif self-assigned this Nov 2, 2026
# Conflicts:
#	src/modules/pubky_swaps/client.rs
@ovitrif

ovitrif commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 92df03c: merges #182's review fixes (7621dfc).

  • Swap legs are refused on mainnet: best_swap_route returns no quote there, so a Spark or Arkade leg still routes. Test: routes_through_a_service_on_mainnet_where_swaps_are_refused.
  • The offer binding, the 30-candidate cap and the wallet-assigned route id now apply in the refactored probe loop.
  • cargo test --lib pubky_swaps (20 tests) and clippy pass. The prerelease 0.8.0-swaps.3 predates this merge.

# Conflicts:
#	src/modules/pubky_swaps/router.rs
@ovitrif ovitrif changed the title feat: pay contacts through arkade and spark feat: pay contacts through spark Nov 3, 2026
@ovitrif

ovitrif commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator Author

Removed Arkade from this PR for now, so service routes are Spark-only: the Arkade PRs are closed and Bitkit uses Spark for Lightning. The router itself never named a service, so 32c5735 only changes the docs, the binding comments and the tests that used an Arkade leg. cargo test --lib pubky_swaps passes (30 tests).

@ovitrif ovitrif changed the title feat: pay contacts through spark feat: pay contacts via spark Nov 3, 2026
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