Add CTX spend-api pubkey auth prototype - #6147
Open
j0ntz wants to merge 4 commits into
Open
Conversation
Contributor
Author
j0ntz
force-pushed
the
jon/ctx-spend-prototype
branch
from
August 10, 2026 21:02
536021a to
09ab729
Compare
j0ntz
marked this pull request as ready for review
August 10, 2026 21:02
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Implements the anonymous secp256k1 register/login protocol documented by CTX-com/spend-api-pubkey-auth-demo, plus the read endpoints it unlocks. The keypair is the CTX account: it is generated on first use and kept in the Edge account's encrypted dataStore, so the same anonymous CTX user is recovered across launches and devices. Tokens are derived state and are never persisted. The protocol crypto is kept free of React Native imports so its wire format is unit testable in Node, and it avoids BigInt because Hermes falls back to a big-integer shim that DataView.setBigUint64 rejects.
j0ntz
force-pushed
the
jon/ctx-spend-prototype
branch
from
August 10, 2026 21:16
09ab729 to
428ff98
Compare
j0ntz
force-pushed
the
jon/ctx-spend-prototype
branch
from
August 10, 2026 21:25
428ff98 to
7151140
Compare
Gives the pubkey auth prototype a surface that can be driven in the running app: connecting establishes the keypair identity, logs in, and reads the authenticated user, company, permission count, and merchant catalog size. The scene already exists as the developer-facing readout for this plugin, so the prototype rides along without touching the shipping purchase flow.
Documents the protocol as implemented: the wire format, where the keypair lives and why, the token lifecycle, and the decisions behind each, including the two places the reference script's crypto could not be copied verbatim.
j0ntz
force-pushed
the
jon/ctx-spend-prototype
branch
from
August 10, 2026 21:35
7151140 to
e222388
Compare
Contributor
Author
Contributor
Author
j0ntz
force-pushed
the
jon/ctx-spend-prototype
branch
from
August 17, 2026 21:40
2e03949 to
8d13b14
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8d13b14. Configure here.
Orders a card with POST /gift-cards, which allocates a payment address and quotes the crypto amount, then hands that address and amount to the normal send flow and polls the card until CTX reports it fulfilled. The request field names and the order shape are what staging actually accepts; the placeholder gift card cleaner from the read-only pass was a guess and is replaced by the real one. Staging quotes every asset on testnet, so the payment resolves to the one testnet chain Edge carries a wallet for, sepolia.
j0ntz
force-pushed
the
jon/ctx-spend-prototype
branch
from
August 17, 2026 21:49
8d13b14 to
c35d4f3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.









Technical Design Document
ctx-spend-pubkey-auth.md
CHANGELOG
Does this branch warrant an entry to the CHANGELOG?
Dependencies
none
Description
Asana: https://app.asana.com/0/1215088146871429/1216403654258326
CTX published a reference script for an anonymous secp256k1 register/login protocol against their spend-api (CTX-com/spend-api-pubkey-auth-demo). This is a prototype client for that protocol, wired into the existing EdgeSpend gift card plugin.
GET /meagainst CTX returns gift-card-scoped permissions (giftcard:create,giftcard:list,giftcard:redeem,merchant:list) andGET /merchantsreturns a 118-entry brand catalog, so CTX is a competing provider for what the Phaze plugin already does. The client is added alongside Phaze; the shipping purchase flow is untouched.The design doc committed in this PR covers the protocol, the decisions, and the two places the reference script's crypto could not be ported verbatim:
src/docs/ctx-spend-pubkey-auth.md.What is here:
ctxSpendPurchase.tspluscreateGiftCard/getGiftCard— ordering a card and paying for it.POST /gift-cardsreturns the card already carrying a single-use payment address and a crypto amount quoted atrate; the client maps CTX's chain-and-network pair to an Edge currency plugin, converts the quote to native units withbiggystring, and hands it to the ordinary send scene with the address, amount, and wallet tiles locked. The card is then polled until CTX credits the payment.ctxSpendCrypto.ts— the protocol wire format:sha256(uint64BE(nonce))digest, the 65-byte recoverable signature ([27 + recoveryId + 4] || R || S), public key recovery, and JWT expiry parsing. No React Native imports, so it is unit tested in Node.ctxSpendAuth.ts— the keypair is the CTX account. It is generated on first use and kept in the Edge account's encrypteddataStore, so the same anonymous CTX user is recovered across launches and devices. The reference script deliberately discards its keypair; a real client cannot. Tokens are derived state and are never persisted.ctxSpendApi.ts— the confirmed read surface (/me,/merchants,/gift-cards), with one re-auth retry on a 401.PLUGIN_API_KEYS.ctxSpend(clientId,baseUrl). There is no API key; the client id is registered server-side and the keypair identifies the user.A CTX section on the existing developer-facing gift card account info scene, so the session can be driven in the app.
Two substitutions the runtime forced, both covered in the doc's Decisions:
BigIntin the uint64 encoding.index.tsshimsBigIntwithbig-integerwhen it is missing, andDataView.setBigUint64rejects that shim.URL. React Native'sURLappends a trailing slash to any query-less path, and the spend-api routes/me/and/merchants/to 404.No new npm dependency:
@noble/curvesand@noble/hasheswere already present, and are the same libraries the reference script uses.Not in scope: replacing Phaze, redemption, a merchant-browsing UI, a shared provider interface over both APIs, and identity rotation.
Test plan
src/__tests__/ctxSpendCrypto.test.ts. The load-bearing one recovers the signing public key from the signature, which is the same check the server performs, so the wire format is verified without a network round trip. Vectors were cross-checked againstBuffer.writeBigUInt64BEandnode:crypto.verify-repo.shpasses: eslint,npm run prepare, and the full 536-case suite.https://staging.spend.ctx.com. The app generated its own keypair, registered it, and read live data: 12 permissions, 118 merchants, company Edge. The server named the anonymous userAnon 032129dfafter the public key's own prefix.End-to-end purchase, driven in-app on the sim against staging: ordered an Amazon card for USD 0.01, paid the quoted 0.0000052 ETH from the account's Sepolia wallet through the normal send flow, and watched CTX move the card to
paid. Card5d2964f9-3641-450e-8d77-e9f178b5b1da.Two notes on that run. Every staging quote is testnet (Monero on stagenet, Zcash/BTC/LTC/BCH/DASH/XLM/ZANO on theirs, ETH on Sepolia), and Sepolia is the only testnet Edge ships a wallet for, so ETH is the one rail the app can actually pay. And
fulfilmentStatusreachedorderedand stayed there for the rest of the run: merchant issuance is asynchronous and outside the client, so the polling stops atpaid.Screenshots in the test-evidence comments below.
Bugbot found 6 issues across three rounds, all fixed and re-tested on the sim. Four were in the first round: a dataStore read failure reading as "no identity" and silently minting a replacement keypair (the worst of them, since CTX has no recovery), malformed stored hex throwing past the replace path, a light-account status conflated with a failed write, and Connect not being retryable. The retry fix then surfaced two follow-ons it had created or exposed: an ungated button that could stack sessions, and the app-wide
retry: 2turning one failed connect into three login handshakes against a rate-limited API. Decision 8.6 in the design doc records the error-handling contract that came out of the first round.Requirements
If you have made any visual changes to the GUI. Make sure you have:
Note
Medium Risk
Touches encrypted identity storage and real on-chain sends for test orders; prototype is developer-gated but mishandling keypair races or payment rounding could strand CTX users or underpay addresses.
Overview
Adds a prototype client for CTX’s spend-api alongside the existing Phaze gift card flow. Phaze purchase paths are unchanged; CTX is wired only when developer mode is on and
PLUGIN_API_KEYS.ctxSpendis set.Auth & persistence: New modules implement secp256k1 register/login (nonce signing, JWT refresh), persist the signing keypair in encrypted
account.dataStore(Phaze-style layout), and gate light accounts that cannot store keys. Protocol crypto is isolated for Node unit tests.API & purchase: HTTP client covers
/me,/merchants, and gift-card create/read. A fixed staging test order maps chain/network to Edge wallets (e.g. ETH testnet →sepolia), converts quoted amounts with ceil viabiggystring, opens the normal send scene with locked tiles, and polls untilpaymentStatusis paid.UI:
GiftCardAccountInfoScenegains Connect/Buy actions and readouts for session and card state (still reachable for Phaze help without developer mode).Config & docs:
envConfigadds optionalctxSpend; strings and changelog updated; design docctx-spend-pubkey-auth.mddocuments the protocol and decisions.Reviewed by Cursor Bugbot for commit c35d4f3. Bugbot is set up for automated code reviews on this repo. Configure here.