feat(swift): mpp/session client#178
Conversation
Greptile SummaryThis PR adds the Swift client-side MPP payment-channel session intent to
Confidence Score: 5/5Safe to merge. All previously-flagged bugs are addressed, the implementation is well-tested, and the cross-SDK conformance vectors are now CI-enforced. All five bugs identified in prior review rounds have been resolved: the signerIndex bounds check is now explicit, the programId constant uses preconditionFailure instead of force-try, the salt silent-nil path is documented and bad strings now throw, the SessionStream commit endpoint is correct, and openPullAction takes an explicit tokenAccount parameter. Cost accumulation in the stream reader uses plain += which traps on overflow. The watermark/nonce logic mirrors the Go reference exactly and is backed by golden-vector cross-SDK tests enforced in CI. No new correctness issues were found. No files require special attention. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant App as SessionStream
participant Server as Playground Server
participant Chain as Solana via Operator
App->>Server: GET stream unauthenticated
Server-->>App: 402 session challenge with recentBlockhash
Note over App: PaymentChannelSession.open builds partial-signed tx
App->>Server: GET stream with open credential
Server->>Chain: co-sign and broadcast open tx
Server-->>App: 200 SSE stream
loop SSE deliveries
Server-->>App: data chunk with cost string
App->>App: "total += cost, traps on overflow"
end
Server-->>App: data DONE
App->>Server: POST deliveries to reserve
Server-->>App: MeteringDirective
Note over App: SessionConsumer.commitDirective signs voucher
App->>Server: POST commit with signed voucher
Server-->>App: CommitReceipt committed or replayed
alt committed
Note over App: recordVoucher advances watermark
else replayed
Note over App: reconcileSettled clamps to min of settled and prepared
end
loop poll up to 8 times
App->>Server: GET receipt for channelId
Server-->>App: finalized with settle signature
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant App as SessionStream
participant Server as Playground Server
participant Chain as Solana via Operator
App->>Server: GET stream unauthenticated
Server-->>App: 402 session challenge with recentBlockhash
Note over App: PaymentChannelSession.open builds partial-signed tx
App->>Server: GET stream with open credential
Server->>Chain: co-sign and broadcast open tx
Server-->>App: 200 SSE stream
loop SSE deliveries
Server-->>App: data chunk with cost string
App->>App: "total += cost, traps on overflow"
end
Server-->>App: data DONE
App->>Server: POST deliveries to reserve
Server-->>App: MeteringDirective
Note over App: SessionConsumer.commitDirective signs voucher
App->>Server: POST commit with signed voucher
Server-->>App: CommitReceipt committed or replayed
alt committed
Note over App: recordVoucher advances watermark
else replayed
Note over App: reconcileSettled clamps to min of settled and prepared
end
loop poll up to 8 times
App->>Server: GET receipt for channelId
Server-->>App: finalized with settle signature
end
Reviews (15): Last reviewed commit: "Update swift/Examples/PayKitDemo/PayKitD..." | Re-trigger Greptile |
… salt decode Address solana-foundation#178 review: guard the signature-slot subscript so a payer index outside the required-signer prefix throws instead of crashing (greptile P1), and document the silent-nil behaviour of the string-or-number salt decode (greptile P2).
Mirror the swift solana-foundation#178 review fix: guard the signature-slot index so a payer outside the required-signer prefix throws instead of going out of bounds.
Address solana-foundation#178 review (Ludo): the demo now fetches the playground's /openapi.json on launch and renders every priced operation (from each route's x-payment-info offers) as the tappable endpoint collection, instead of a static catalog, then consumes one over MPP. Adds OpenAPI.swift (JSONSerialization decode of paths + x-payment-info), points the app at the playground on :3000, refreshes the README, and updates the screenshot to the live OpenAPI-driven flow (Stock quote settled over MPP).
Per maintainer review on solana-foundation#178.
Mirror the swift solana-foundation#178 review fix: guard the signature-slot index so a payer outside the required-signer prefix throws instead of going out of bounds.
53bdaea to
ba3f79d
Compare
… salt decode Address solana-foundation#178 review: guard the signature-slot subscript so a payer index outside the required-signer prefix throws instead of crashing (greptile P1), and document the silent-nil behaviour of the string-or-number salt decode (greptile P2).
Address solana-foundation#178 review (Ludo): the demo now fetches the playground's /openapi.json on launch and renders every priced operation (from each route's x-payment-info offers) as the tappable endpoint collection, instead of a static catalog, then consumes one over MPP. Adds OpenAPI.swift (JSONSerialization decode of paths + x-payment-info), points the app at the playground on :3000, refreshes the README, and updates the screenshot to the live OpenAPI-driven flow (Stock quote settled over MPP).
Per maintainer review on solana-foundation#178.
617d15e to
009977c
Compare
Adds the client-side MPP payment-channel session intent to the Swift SDK, mirroring the Rust reference spine and the Go reference. What - PayCore PaymentChannels: the 48-byte Ed25519 voucher preimage, channel + event-authority PDA derivation, and the open instruction plus the payer-signed (operator-fee-payer-unsigned) open transaction. Client subset only; the server-side settle/finalize/distribute, the Ed25519 precompile, and the BLAKE3 distribution hash are out of scope for this client-only SDK (the open passes its recipients inline rather than hashed). - Session wire types: SessionRequest, OpenPayload, SignedVoucher/VoucherData (cumulativeAmount with a cumulative read-alias), commit/topUp/close payloads, MeteringDirective/MeteringUsage, CommitReceipt, and the internally-tagged SessionAction codec (the action key, salt as a decimal string, the topUp tag). - ActiveSession: monotonic cumulative watermark, nonce accounting, retry-safe prepare/record voucher, the reconcileSettled lost-response clamp (Go parity), and the open/voucher/topUp/close action builders. recordVoucher binds the voucher to the active channel and advances the nonce to at least nonce + 1, matching Go RecordVoucher. - SessionConsumer: validate, sign, commit, advance. The local watermark advances only on a committed receipt and reconciles (clamped to the prepared voucher, never regressing) on a replayed one. - Session credential framing (serializeSessionCredential) and a solana/session challenge dispatch accessor. Verification - swift test is green; the session source files are at 94 to 99 percent line coverage. - The runner manifest declares the session intent and the conformance runner gained a voucherPreimage branch driving the real PaymentChannels.voucherMessageBytes. Both frozen session-voucher vectors pass through the cross-SDK conformance driver (byte-identical to Go), now enforced in CI in the harness-swift job. Review feedback addressed - Bounds-check the open-tx payer signer index subscript (greptile P1). - Replace the force-try programId with a lazy precondition guard (greptile P2). - Document the silent-nil salt decode and throw on non-numeric strings (greptile P2). - Pass the commit endpoint URL (not the stream URL) to the delivery reservation (greptile P1). - Add an explicit tokenAccount parameter to openPullAction instead of misusing the channel PDA (greptile P1). - Restore the original demo screenshot. - Mark mpp/session as supported in the compatibility matrix. Notes - Client-only. Operability caveats that apply to a client are satisfied (the session reuses the MPP charge mint resolver, so localnet USDC resolves to the mainnet mint); the server-side caveats are not applicable. - The session credential uses the same sorted-keys canonicalization the existing Swift charge credential uses.
009977c to
649b5a6
Compare
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Adds the client-side MPP payment-channel session intent to the Swift SDK, mirroring the Rust reference spine (
rust/crates/mpp+rust/crates/core) and the Go reference.What
PaymentChannels: the 48-byte Ed25519 voucher preimage, channel + event-authority PDA derivation, and theopeninstruction plus the payer-signed (operator-fee-payer-unsigned) open transaction. Client subset only; the server-side settle/finalize/distribute, the Ed25519 precompile, and the BLAKE3 distribution hash are out of scope for this client-only SDK (the open passes its recipients inline rather than hashed).SessionRequest,OpenPayload,SignedVoucher/VoucherData(cumulativeAmountwith acumulativeread-alias), commit/topUp/close payloads,MeteringDirective/MeteringUsage,CommitReceipt, and the internally-taggedSessionActioncodec (theactionkey, salt as a decimal string, thetopUptag).ActiveSession: monotonic cumulative watermark, nonce accounting, retry-safe prepare/record voucher, thereconcileSettledlost-response clamp (Go parity), and the open/voucher/topUp/close action builders.recordVoucherbinds the voucher to the active channel and advances the nonce to at leastnonce + 1, matching GoRecordVoucher.SessionConsumer: validate, sign, commit, advance. The local watermark advances only on a committed receipt and reconciles (clamped to the prepared voucher, never regressing) on a replayed one.serializeSessionCredential) and asolana/sessionchallenge dispatch accessor.Verification
swift testis green; the session source files are at 94 to 99 percent line coverage.sessionintent and the conformance runner gained avoucherPreimagebranch driving the realPaymentChannels.voucherMessageBytes. Both frozensession-vouchervectors pass through the cross-SDK conformance driver (byte-identical to Go), now enforced in CI in theharness-swiftjob.Notes