Repository navigation
Conversation
536021a to
09ab729
Compare
|
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.
09ab729 to
428ff98
Compare
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.
7151140 to
e222388
Compare
2e03949 to
8d13b14
Compare
8d13b14 to
c35d4f3
Compare
c35d4f3 to
e206556
Compare
📸🪓 Test evidence: CTX mainnet chain map, status machine, redemption links
ctx session ctx payment locked ctx payment sent ctx card paid 🪓 HACK-FORCED: redeem links Captured by the agent's in-app test run (build-and-test). |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default 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 e206556. 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.
e206556 to
867f3b5
Compare














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 it reaches an end state.Chain coverage for every mainnet asset CTX quotes: BCH, BTC, DASH, ETH, LTC, XLM, XMR, ZANO and ZEC. Each chain code was read back from a real
POST /gift-cardsresponse, one order percryptoCurrency, rather than transcribed from a currency list.A native-payment guard. A token quote splits the pair (
paymentCryptoChainstaysETHwhilepaymentCryptoCurrencybecomesETH.USDC), and both the wallet lookup and the native-unit conversion key off the chain alone, so a0.010000USDC quote would have resolved the ethereum wallet and sent 0.01 ETH for a one-cent card.isCtxNativePaymentrejects it instead.CTX's documented status machine:
unpaid,paid,fulfilled(terminal success),rejected(which can still move torefunded),refunded(terminal).rejectedis a failure but deliberately not terminal, so it does not stop the poll. The poll now runs at 5s untilpaidand backs off to 30s afterwards, because fulfilment is the merchant's and can outlast the session.redeemUrlandbarcodeUrlon the card. Redemption is not a path the client builds: a fulfilled card carries both URLs, each embedding its own JWT distinct from the session token. That is why a hand-built/gift-cards/{id}/barcodeanswers401 token invalidfor a session token every other route accepts.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, following the redemption links (their shape is now known and modelled, but nothing opens them), a merchant-browsing UI, a shared provider interface over both APIs, and identity rotation.
Test plan
ctxSpendCrypto,ctxSpendAuthandctxSpendPurchase. 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.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 pay against staging. The mainnet rows of the chain table are therefore unexercised: CTX's production endpoint exists but does not yet permit pubkey auth for our client (
400 authentication method not allowed for this clienton the firstPOST /loginleg, against a 200 from the identical request to staging).Re-driven end to end after those changes: ordered another Amazon card at USD 0.01, paid 0.000004 ETH from the Sepolia wallet, and watched CTX move it to
paid. Card99f59d44-216c-4a0f-b620-27cdfed996d1. The redemption-link rows cannot be reached naturally, since staging never fulfils a card, so that frame was captured under a forced card state and is labelled as such in the evidence comment.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
Prototype persists signing keys and can trigger real on-chain sends when used, but it is developer-gated and does not alter the shipping Phaze purchase path; mistakes in amount mapping or identity handling could still strand orders or keys.
Overview
Adds a developer-mode prototype for CTX’s spend-api alongside the existing Phaze gift card flow: anonymous secp256k1 pubkey register/login, JWT session handling with keys in encrypted
account.dataStore, and HTTP clients for/me,/merchants, and/gift-cards.From Gift Card Account Info, developers can connect (show pubkey, user, merchant count), order a fixed staging test card, pay via the normal send scene (chain/network → Edge wallet, native amount with ceiling rounding, native-only quotes), and poll card status until terminal (
fulfilled/refunded) with faster polling until paid.New modules:
ctxSpendCrypto,ctxSpendAuth,ctxSpendApi,ctxSpendPurchase,ctxSpendTypes;PLUGIN_API_KEYS.ctxSpendin env; locales; design doc; Jest coverage for crypto, identity races, and payment/status helpers. Phaze reveal/copy behavior is unchanged; CTX UI is gated on developer mode and env config.Reviewed by Cursor Bugbot for commit 867f3b5. Bugbot is set up for automated code reviews on this repo. Configure here.