Skip to content

feat: pay contacts via spark - #1754

Draft
ovitrif wants to merge 13 commits into
feat/breez-spark-lightningfrom
feat/paykit-spark-route
Draft

ovitrif wants to merge 13 commits into
feat/breez-spark-lightningfrom
feat/paykit-spark-route

Conversation

@ovitrif

@ovitrif ovitrif commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Twin: synonymdev/bitkit-ios#1191
Dependency:

This PR lets Spark pay a Paykit contact who only accepts on-chain bitcoin, when it costs less than a Pubky Swaps provider.

Description

  • Quotes a Spark withdrawal of exactly the amount to the contact's on-chain address at medium speed, with Spark's fee on top, when the Spark balance covers both (SparkRepo.withdrawalRouteLeg).
  • Passes that leg to bitkit-core's Paykit router with the Pubky Swaps providers found among the user's contacts. The router returns the route with the lower fee; a swap wins a tie, since its hash lock needs no one's trust.
  • When the router picks Spark, the confirm screen shows Route: Spark with the leg's fee in the fee row, and the swipe withdraws exactly the amount to the contact's address from Spark. The contact receives exactly the amount entered.
  • Builds on bitkit-core 0.8.0-swaps.3, a prerelease cut from the router change.
  • Behind PUBKY_SWAPS_ROUTES on regtest debug builds only, like the swap route.

What it needs to ship

  • Lifecycle cases first-class in testing: the app killed after the leg starts and before it confirms, and a restore from the seed while a leg is in flight.
  • Timing on real devices: quote to completion, in the foreground and in the background.
  • Fees: the confirm screen shows the leg's fee under the Lightning "Instant" label, which reads as a Lightning fee.
  • Merge order: the dependencies above first; this branch stacks on them.
  • Two-leg routes (Spark Lightning paying a Pubky Swaps hold invoice) stay out until single-leg routes have real-device data.

Design

N/A — no design available.

Preview

A wallet whose spending balance is held on Spark pays a contact who accepts only on-chain bitcoin. The router finds no swap provider among the wallet's contacts and picks a Spark withdrawal, the confirm screen shows Route: Spark, and the contact receives exactly 20,000 sats on Spark's regtest chain (2x speed).

android-spark-j12x.mp4

QA Notes

Setup

  • Pubky stack: synonymdev/bitkit-docker main: docker compose up -d, then ./pubky-marketplace up and ./pubky-marketplace seed, then docker compose --profile marketplace --profile payment-requests up -d --no-build fixture-issuer rc56-peer. No swap provider is needed.
  • App: E2E=true E2E_BACKEND=local E2E_LOCAL_HOST=127.0.0.1 E2E_HOMESERVER_PUBKY=$(./pubky-marketplace info | jq -r .homeserver_z32) PUBKY_SWAPS_ROUTES=true ./gradlew assembleDevDebug, then adb reverse 6286, 6287, 6288, 15411, 15412, 60001, 23735, 23000 and 23010 before first launch.
  • Spark: dev builds run Spark on Lightspark's hosted regtest, which needs no key and runs its own chain. The node's chain does not matter here: Savings stays empty.
  • Wallet: a new wallet with no channels; a Pubky profile on the local homeserver; fund Spending by sending 50,000 sats from Lightspark's web regtest faucet to the deposit address on Settings › Advanced › Lightning Connections › Spark, which Spark claims after one confirmation; save the fixture-issuer as a contact (bitkit://contact?pubky=<pubky>, from curl 127.0.0.1:23012/health).
  • Contact: bitkit-docker's fixture-issuer, which publishes only btc-regtest-p2wpkh.

Journeys

  • J1: new paykit-spark-route.xml — a wallet whose spending balance is on Spark pays an on-chain-only contact through a Spark withdrawal, and the contact receives exactly 20,000 sats

Manual Tests

N/A

Evidence

  • J1 on a regtest Android emulator (API 35), at 28a63f8 on bitkit-core 0.8.0-swaps.3:
    • the amount screen offered only Spending; the confirm screen showed Route: Spark and the leg's fee;
    • Bitcoin Sent showed ₿ 20,000;
    • Spark's withdrawal 56263be8… is confirmed on Lightspark's regtest chain and pays exactly 20,000 sats to the contact's address.

Automated Checks

  • added SparkRepoTest cases: a withdrawal quoted as a route leg, and no leg when the balance cannot cover the amount plus the fee
  • ran SparkRepoTest and AppViewModelSendFlowTest at 28a63f8: pass
  • ran just lint (detekt and Android Lint): pass

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
@ovitrif

ovitrif commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 036346a: adds the paykit-spark-route.xml journey (J1). Ran it on a regtest Android emulator against Lightspark's hosted regtest. The router picked a Spark withdrawal, and 56263be8… pays exactly 20,000 sats to the contact. The recording, setup and evidence are in the description.

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Regtest APK

Built from c08023a (run).

Download bitkit-dev-debug universal APK (expires in 30 days).

@ovitrif ovitrif added this to the 3.2.0 milestone Nov 3, 2026
@ovitrif

ovitrif commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator Author

Added to the 3.2.0 milestone: Spark via Breez SDK is the 3.2.0 highlight, and this PR stacks on #1697.

@ovitrif ovitrif changed the title feat: pay on-chain contacts from spark feat: pay contacts via spark Nov 3, 2026

This branch has not been deployed

No deployments
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