Skip to content

chore: adopt Paykit 0.1.1-rc6 - #1237

Draft
ben-kaufman wants to merge 5 commits into
masterfrom
chore/paykit-rc6
Draft

ben-kaufman wants to merge 5 commits into
masterfrom
chore/paykit-rc6

Conversation

@ben-kaufman

@ben-kaufman ben-kaufman commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Twin: synonymdev/bitkit-android#1803

This PR:

  1. Updates Paykit from 0.1.1-rc2 to 0.1.1-rc6
  2. Checks that a Payment Request's proof fits one Paykit message before paying it
  3. Asks to shorten a note or subscription text that does not fit one Paykit message
  4. Explains a refused signup code or a failed connection when authorizing a signup link
  5. Stops counting USDT received before a request was made as paying that request

Description

  • Paykit bump: pins exact version 0.1.1-rc6 at revision 9919198a, the same way the rc2 bump did. The app compiled unchanged against rc6. Only test fixtures needed the new Payment Request record times and the acknowledged list in Private Payment List delivery reports.
  • Proof size: before a Lightning, on-chain or USDT Payment Request is paid, Bitkit measures the Payment Proof message with the SDK's new paymentProofEventLen, using the longest proof the rail can produce and the IDs it will send. A request whose proof would not fit the 1000-byte message limit shows "The payment request is no longer available." and nothing is paid. An Allowance checks this before it accepts the request automatically, so such a request stays manual instead of being accepted and then left unpaid. The USDT check used a hand-built copy of the message and now uses the same measurement. Bitkit's own requests use a fixed-length payment reference, so their longest proof always fits.
  • Proposal size: proposing a Payment Request or subscription that the SDK refuses with message_too_large shows "Shorten the note and try again." (wallet__payment_request_note_too_long, new) for a one-time request and the existing "Shorten the subscription name or description and try again." for a subscription, instead of the raw SDK error. Nothing is queued in either case.
  • Signup errors: in the authorization sheet, a signup code the homeserver refuses now shows "The homeserver did not accept this signup code. Try again or use another signup link." (profile__auth_error_signup_code_rejected, new) and a network, 5xx, 429 or PKARR failure, which rc6 reports as a transport error, shows "Check your connection and try again." (pubky_auth__identity_unavailable_desc, new on iOS with Android's translations). Other failures keep their description.
  • USDT receipts: a verified USDT transfer whose block time is more than a day before this wallet recorded its request (the new proposal record time) shows "Received · Before request" (wallet__payment_request_before_request, new) and does not pay the request or Billing Period. The day covers Arbitrum timestamp lag. Receipts and backups from earlier builds keep their meaning.
  • Reviewed with no app change needed: re-sign-in already treats transport errors as temporary, so restoring a session after a network failure now retries instead of reporting a failed restore. Pubky Ring sign-in already signs up only when republishIdentity finds no record. Platform callbacks already throw PaykitError. Bitkit uses Pubky shared state, so the stateRevision change does not apply. Accounting already reconciles state that requires it and automatic acceptance uses one trusted time. Requests that rc2 showed as invalid after a late Acceptance are shown from their new state. Encrypted Link sends check write slots before publishing, with no API change. The homeservers, relays and homegate Bitkit uses on staging and production chain to ISRG Root X1, which webpki-roots 1.0.11 keeps.

Out of Scope

  • Bitkit/Services/PaykitAllowanceManager.swift: automatic acceptance of Allowance offers still accepts each offer separately. acceptPendingAllowanceProposals would also accept a contact's offers in assets Bitkit does not list and cannot bind each acceptance to the offer Bitkit listed.
  • Profile removal and Disconnect: the captured grant observer and original-grant logout added in Paykit rc3 and rc4 are adopted in fix: bind profile removal to its original owner #1143, not here.
  • Bitkit/Services/PubkyService.swift: deleting a single contact still blocks the peer when the private list withdrawal reports a failure, as before.
  • Lightning and on-chain requests Bitkit receives payment for: Bitkit still shows them paid from the payer's proof, so the proposal record time only applies to verified USDT receipts.
  • Bitkit/Services/PaykitUsdtPaymentService.swift: the one-day tolerance covers Arbitrum timestamp lag. It does not add a margin for the payee's own clock skew.

Design

N/A — no design available. The change adds text to existing toasts and to the USDT receipt status.

Preview

N/A

QA Notes

Setup

  • J1: two Bitkit instances on local regtest with Paykit enabled, saved as each other's contacts and linked as identities, as in journeys/payment-requests/README.md (Request summary setup). No funds are needed.
  • J2: an isolated wallet with no Pubky identity and a controlled requesting app on the Pubky testnet integration fixture runtime (see journeys/pubky-auth/README.md). Complete one signup_grant URL on another wallet first, then use the same URL, whose token is now used, for the journey.
  • Both instances must run this build. Paykit rc6 writes its shared state in a newer format that builds on Paykit rc5 or older cannot read, so do not share an identity with a device on an older build.

Journeys

  • J1: new request-note-too-long.xml — a note that cannot fit one Paykit message is refused with "Shorten the note and try again." before anything is sent, and the shortened request sends
  • J2: new grant-signup-rejected-code.xml — a refused signup code shows the signup code message, the sheet stays open and no identity is created

Manual Tests

  • 1. A linked issuer sends a one-time Lightning request whose payment reference is 256 "💜" characters → Pay → "The payment request is no longer available." and no payment starts — an issuer that sends a chosen payment reference not in Capabilities
  • 2. A USDT payee receives a proof for a transfer made two days before its request → payment history shows "Received · Before request" and the request stays unpaid — a payer that proves an earlier USDT transfer not in Capabilities
  • 3. Authorize a signup link while the homeserver is unreachable → "Check your connection and try again." and the sheet stays open — network fault injection not in Capabilities

Automated Checks

  • added PaykitPaymentProofServiceTests.swift — Lightning and on-chain preparation accepts a request whose proof is exactly 1000 bytes and refuses one at 1001 bytes without storing anything, and the Allowance ID counts toward the size
  • added PaykitAllowanceExecutorTests.swift — a request whose proof cannot fit stays manual with no execution claim, Acceptance or payment
  • added PaykitRequestPricingTests.swift — the USDT placeholder is a valid proof of the longest shape, the USDT check accepts exactly 1000 bytes and refuses 1001, the Billing Period and quote ID count toward the size, and Bitkit's own requests leave room for their longest proof
  • added PaykitPaymentRequestServiceTests.swift — message_too_large becomes the note and subscription messages with nothing proposed, and other protocol errors pass through. A USDT transfer more than a day before the request record time, in Bitkit's or the SDK's timestamp format, is flagged, shown as Before request ahead of other labels and does not pay it, while one within the day or without a valid record time does
  • added PaykitPaymentStateBackupTests.swift — the before-request flag survives a backup, and an older backup or a receipt saved before the flag existed still counts as paid
  • added PubkyAuthApprovalSheetTests.swift — a refused signup code and a transport error get their messages, other errors keep their own
  • updated 15 test files — fixtures carry the new Paykit record and report fields
  • updated PaykitAllowanceTests.swift — Allowance fixtures use canonical UUID-v4 IDs, which the SDK requires in a Payment Proof
  • ran openssl s_client against a bundle built from webpki-root-certs 1.0.11 — homeserver, httprelay, homegate and nexus on pubky.app and staging.pubky.app, pkarr.pubky.app, pkarr.pubky.org and relay.pkarr.org verify, ending at ISRG Root X1

Evidence

  • On commit 60111132, a throwaway iPhone 17 simulator (iOS 26.5), Xcode 27.2: CI's unit test selection (xcodebuild test, skipping the integration suites as in unit-tests.yml) passed, BitkitTests 3189 executed with 1 skipped (UsdtNativeRecoveryIntegrationTests, needs a local fixture) and BitkitUITests 44 executed with 4 skipped, 0 failures. The same selection also passed on 265389e8 (the bump alone, 3176 executed) and cd4236ab (3186 executed). Head 1594952e only renames the changelog fragment.
  • SwiftFormat 0.64.0 lint is clean on the changed Swift files, node scripts/validate-translations.js reports 0 errors with the same warnings as master, and git diff --check is clean.
  • The published Paykit.xcframework.zip for v0.1.1-rc6 matches the Package.swift checksum 23bf9612c2bc7579dee8a40b18527936ba1a0531f788e13f9aae8f6286ccf3f5.

Gaps

  • J1, J2 and Manual Tests 1 to 3 were not run: device journeys are left to reviewers, and no two-instance regtest or Pubky testnet setup was used for this change.
  • An in-place upgrade from a build on Paykit rc2 with existing Paykit state was not run on a device. The SDK migrates that state when it first reads it.
  • The new tests were not run against the code without the change.

Models used

  • Planning/scoping: Claude Opus 5.5 (reasoning: Not exposed)
  • Implementation: Claude Opus 5.5 (reasoning: Not exposed)
  • Review: Claude Opus 5.5 (reasoning: Not exposed)
  • Review rounds: 1

The session metadata does not expose a reasoning setting.

🤖 Generated with Claude Code

ben-kaufman and others added 5 commits November 6, 2026 11:52
Pin paykit-rs to the published 0.1.1-rc6 release (revision 9919198a) and
update test fixtures for the new Payment Request record times and the
acknowledged list in Private Payment List delivery reports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measure the Payment Proof Event Message with the SDK's
paymentProofEventLen before a Lightning, on-chain or USDT Payment
Request payment, so a request whose proof cannot fit one Paykit
message is refused before anything is paid. The USDT check now uses
the same measurement instead of a hand-built copy of the message.

Ask to shorten the note or subscription text when the SDK refuses a
proposal with message_too_large, and explain a refused signup code or
a failed connection in the Pubky authorization sheet.

A verified USDT transfer made more than a day before this wallet
recorded its request is labelled Received, Before request and no longer
counts as paying it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measure the Payment Proof Event Message before an Allowance accepts a
Payment Request automatically, so a request whose proof cannot fit one
Paykit message is left for manual review instead of being accepted and
then failing to pay. Allowance test fixtures now use canonical UUID-v4
Allowance IDs, which the SDK requires in a Payment Proof.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cover that the proof size check counts the Allowance ID, Billing Period
and quote ID it will send, that Bitkit's own requests leave room for
their longest proof, that SDK-formatted record times are parsed, that
Before request wins over the other receipt labels, and that receipts
saved before the flag existed still count as paid. Use the SDK App ID
and the USDT chain ID from their shared constants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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