Skip to content

feat: CREQ (NUT-18) tap-to-pay support for Numo compatibility#4053

Open
kaloudis wants to merge 13 commits into
ZeusLN:masterfrom
kaloudis:creq
Open

feat: CREQ (NUT-18) tap-to-pay support for Numo compatibility#4053
kaloudis wants to merge 13 commits into
ZeusLN:masterfrom
kaloudis:creq

Conversation

@kaloudis
Copy link
Copy Markdown
Contributor

@kaloudis kaloudis commented May 3, 2026

Description

Adds CREQ (NUT-18) tap-to-pay support, enabling ZEUS to send and receive Cashu ecash payments via NFC, compatible with the Numo POS app and other NUT-18 wallets.

CREQ is the Cashu payment request format defined in NUT-18. A merchant broadcasts a CREQ containing the requested amount, unit, and accepted mints. The payer reads it, creates a Cashu token from local proofs, and writes the token back. This PR implements both sides of that exchange: ZEUS can act as a payer on Android and iOS, and as a merchant on Android via HCE.

The payer flow uses a single-tap model with no confirmation dialog, matching Numo's behavior. When the user taps the NFC icon on the Send screen and encounters a CREQ tag, ZEUS automatically reads the request, creates a token from the best matching local mint, and writes it back to the merchant, all within one NFC session. For CREQ strings received via QR code or clipboard (including BIP-21 URIs with a creq= parameter), a confirmation view is shown instead, with an option to tap the merchant's device to deliver the token.

The merchant flow leverages the writable tag support already present in react-native-hce 0.3.0. When a user creates a Lightning invoice in ReceiveEcash, ZEUS also generates a CREQ and broadcasts it via NFC with the writable flag enabled. When a payer writes a token back, the HCE_STATE_WRITE_FULL event fires and the token is automatically redeemed.

Key changes

  • utils/CREQUtils.ts - NUT-18 CREQ encode/decode/detect using CBOR (via cborg)
  • utils/NFCUtils.ts - IsoDep APDU helpers, readCREQFromTag, writeTokenToTag, payViaNfcTap (single-session auto-pay), buildNdefTextMessage, NDEF text record parser
  • utils/AddressUtils.ts - BIP-21 ?creq= parameter extraction
  • utils/handleAnything.ts - CREQ routing: standalone, from BIP-21, and multi-method via ChoosePaymentMethod
  • views/Cashu/CREQPayment.tsx - Payer confirmation view for QR/clipboard flow
  • views/Send.tsx - NFC button attempts CREQ single-tap auto-pay before falling back to regular tag scan
  • views/ChoosePaymentMethod.tsx - Accepts and passes through creq param
  • components/LayerBalances/PaymentMethodList.tsx - "Ecash (tap-to-pay)" row for unified BIP-21 URIs containing CREQ
  • components/NFCButton.tsx - writable and onTokenReceived props for merchant HCE mode
  • components/CollapsedQR.tsx - Passthrough for writable NFC props
  • views/Cashu/ReceiveEcash.tsx - Generates CREQ from invoice, broadcasts via writable HCE, redeems received tokens

Platform support

Role Android iOS
Payer (read CREQ, write token) Yes Yes (CoreNFC IsoDep)
Merchant (broadcast CREQ, receive token via HCE) Yes No (no HCE)

Test plan

  • Android merchant: create invoice in ReceiveEcash, tap with Numo or compatible wallet, verify token redeemed
  • Android payer: open Numo POS with amount, tap NFC icon in ZEUS Send view, verify single-tap payment completes
  • iOS payer: same as Android payer, verify IsoDep APDU flow works via CoreNFC
  • Scan a bitcoin:?creq=creqA...&lightning=lnbc... QR code, verify ChoosePaymentMethod shows "Ecash (tap-to-pay)" option
  • Scan a standalone creqA... QR code, verify CREQPayment confirmation view appears

PR Type

  • New feature
  • Bug fix
  • Code refactor
  • Configuration change
  • Locales update
  • Quality assurance
  • Other

Checklist

  • I’ve run yarn run tsc and made sure my code compiles correctly
  • I’ve run yarn run lint and made sure my code didn’t contain any problematic patterns
  • I’ve run yarn run prettier and made sure my code is formatted correctly
  • I’ve run yarn run test and made sure all of the tests pass

Testing

If you modified or added a utility file, did you add new unit tests?

  • No, I’m a fool
  • Yes
  • N/A

I have tested this PR on the following platforms (please specify OS version and phone model/VM):

  • Android
  • iOS

I have tested this PR with the following types of nodes (please specify node version and API version where appropriate):

On-device

  • LDK Node
  • Embedded LND

Remote

  • LND (REST)
  • LND (Lightning Node Connect)
  • Core Lightning (CLNRest)
  • Nostr Wallet Connect
  • LndHub

Locales

  • I’ve added new locale text that requires translations
  • I’m aware that new translations should be made on the ZEUS Transfix page and not directly to this repo

Third Party Dependencies and Packages

  • Contributors will need to run yarn after this PR is merged in
  • 3rd party dependencies have been modified:
    • verify that package.json and yarn.lock have been properly updated
    • verify that dependencies are installed for both iOS and Android platforms

Other:

  • Changes were made that require an update to the README
  • Changes were made that require an update to onboarding

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for Cashu tap-to-pay (NUT-18) using NFC. It includes the implementation of CREQ payment request encoding/decoding, new UI components for CREQ payments, and integration with the existing NFC scanning logic. Feedback focuses on critical runtime issues such as the incorrect handling of event subscriptions in NFCButton.tsx, the use of non-standard global TextEncoder/TextDecoder in React Native, and missing type validation during CBOR decoding. Additionally, there are recommendations to refactor duplicated logic for finding compatible mints and to avoid inline require statements to align with standard React patterns.

Comment thread components/NFCButton.tsx
Comment thread components/NFCButton.tsx
Comment thread utils/NFCUtils.ts Outdated
Comment thread utils/NFCUtils.ts Outdated
Comment thread views/Send.tsx Outdated
Comment thread components/LayerBalances/PaymentMethodList.tsx Outdated
Comment thread utils/CREQUtils.ts Outdated
Comment thread utils/NFCUtils.ts Outdated
@kaloudis kaloudis added this to the v13.1.0 milestone May 12, 2026
Comment thread views/Cashu/CREQPayment.tsx Outdated
Comment thread utils/NFCUtils.ts Outdated
@kaloudis kaloudis requested a review from ajaysehwal May 26, 2026 19:14
style={[styles.rectButton, rowDisabled && { opacity: 0.5 }]}
onPress={() => {
if (rowDisabled) return;
const creqParams = decodeCREQ(creq);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think we should use try/catch here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants