Skip to content

feat: ENG-177 Cashu NFC card provisioning flow#47

Draft
forge0x wants to merge 2 commits intomainfrom
feat/cashu-nfc-provisioning
Draft

feat: ENG-177 Cashu NFC card provisioning flow#47
forge0x wants to merge 2 commits intomainfrom
feat/cashu-nfc-provisioning

Conversation

@forge0x
Copy link

@forge0x forge0x commented Mar 7, 2026

Summary

Merchant screen for issuing Flash cards loaded with Cashu proofs (NUT-XX Profile B offline bearer payments).

New files

  • src/nfc/cashu-apdu.ts — APDU layer for CashuApplet (D2 76 00 00 85 01 02)
  • src/nfc/useCashuCard.ts — IsoDep NFC session hook (NfcTech.IsoDep)
  • src/graphql/cashu.tscashuCardProvision mutation + proof helpers
  • src/screens/CashuProvision.tsx — 6-step provisioning screen

Flow

  1. Merchant enters USD amount
  2. Sets card PIN
  3. Taps card → NFC reads blank card + pubkey
  4. Backend mints P2PK-locked proofs via cashuCardProvision GQL
  5. NFC writes proofs: SET_PIN + VERIFY_PIN + LOAD_PROOF × N
  6. Card ready for offline spending

Top-up: same flow, existing PIN used for VERIFY_PIN (skips SET_PIN).

Depends on

Next

ENG-178: tap-to-pay settlement screen (SPEND_PROOF → proof redemption at mint)

Adds merchant screen + NFC layer for issuing Flash cards loaded
with Cashu proofs (NUT-XX Profile B, offline bearer payments).

New files:
  src/nfc/cashu-apdu.ts       APDU builders + response parsers
    - buildSelectAid, buildGetInfo, buildGetPubkey, buildLoadProof,
      buildSetPin, buildVerifyPin + matching parsers
    - reconstructP2PKSecret() — rebuilds secret JSON from nonce+pubkey
      (avoids storing full ~120-byte JSON on 32-byte on-card field)
  src/nfc/useCashuCard.ts     IsoDep NFC session hook
    - startSession() / cleanup() — NfcTech.IsoDep lifecycle
    - readBlankCardPubkey() — verify blank + return pubkey
    - writeProofs() — SET_PIN (blank) + VERIFY_PIN + LOAD_PROOF × N
    - individual command methods for flexible use in ENG-178
  src/graphql/cashu.ts        GQL mutation + proof shape helpers
    - CASHU_CARD_PROVISION mutation document
    - extractNonceFromSecret() — pull 32-byte nonce from P2PK JSON
    - toCardWriteProof() — convert GQL proof to flat card-write format
  src/screens/CashuProvision.tsx  6-step provisioning screen
    - Step 1: enter USD amount
    - Step 2: set provisioning PIN
    - Step 3: NFC tap — read card (blank check or top-up detect)
    - Step 4: mint proofs via cashuCardProvision GQL
    - Step 5: write proofs via SET_PIN + VERIFY_PIN + LOAD_PROOF × N
    - Step 6: success / retry on error

Modified:
  src/types/routes.d.ts        added CashuProvision route
  src/routes/index.tsx         registered CashuProvision screen
  src/screens/Profile.tsx      added 'Issue Flash Card' entry button

Closes ENG-177
@linear
Copy link

linear bot commented Mar 7, 2026

Customer taps Flash card → merchant receives Lightning payment.

New files:
  src/utils/sha256.ts          Pure-TS SHA-256 (FIPS 180-4, zero deps)
                               Used to hash P2PK secrets for SPEND_PROOF signing
  src/nfc/cashu-melt.ts        Nutshell melt API client
                               createMeltQuote() + meltProofs() + selectProofsForAmount()
  src/screens/CashuPayment.tsx 6-state tap-to-pay screen
                               waiting → reading → spending → melting → success/error

Payment flow:
  1. NFC: SELECT + GET_INFO + GET_PUBKEY + read unspent proof slots
  2. Proof selection: greedy minimum-set covering amountCents
  3. NFC: SPEND_PROOF × N — SHA256(P2PKsecret) → Schnorr sig per proof
  4. API: createMeltQuote(mint, 'usd', bolt11) → quoteId
  5. API: meltProofs(mint, quoteId, proofs+witness) → paid
  6. Navigate to Success screen

Proof witness format: '{"signatures":["<64-byte-schnorr-hex>"]}'
Mint URL from CASHU_MINT_URL env (default: https://forge.flashapp.me)

Modified:
  src/nfc/useCashuCard.ts          added spendProof(slotIndex, msg) method
  src/screens/Invoice.tsx          added 'Pay with Flash Card' button
  src/screens/Keypad.tsx           stores usdCents in invoice slice on create
  src/store/slices/invoiceSlice.ts added usdCents field
  src/types/env.d.ts               added CASHU_MINT_URL
  src/types/routes.d.ts            added CashuPayment route params
  src/routes/index.tsx             registered CashuPayment screen

Closes ENG-178
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.

2 participants