An open-source mobile prototype exploring contactless payment requests, a self-custodial wallet and passkey access on Stellar testnet.
Nudge mobile app from the Nudge-Pay organization, built with Expo and React Native.
Backend: The NestJS API lives in Nudge-Pay/nudge-server.
- How Nudge uses Stellar
- Project status
- Prerequisites
- Setup
- EAS Development Build
- Quality checks (CI)
- C05 Spike documentation
- NFC development (C10)
- Scripts
- Documentation
- Environment variables
- Security
Nudge brings Stellar account and asset primitives into a mobile payment experience. A receiver prepares a request containing their Stellar public address, asset, amount and expiry, then shares it over NFC. The intended next step is for the payer to approve a Stellar transaction and for both devices to observe network confirmation. The client send/settlement integration is still unfinished.
The implemented wallet and request modules use @stellar/stellar-sdk:
| Capability | Implementation | Role in Nudge |
|---|---|---|
| Wallet keys | AccountService | Creates a Stellar keypair when needed and persists keys through the app's native secure-storage wrapper |
| Testnet onboarding | AccountService.fundTestnetAccount |
Uses Friendbot for testnet funding; automatic funding is blocked on mainnet |
| Account balances | BalanceService | Loads accounts through Horizon and reads native XLM and the configured USDC code/issuer pair |
| USDC trustlines | TrustlineService | Checks existing trustlines, available XLM reserves and key ownership; builds and signs changeTrust when setup is needed |
| Payment requests | PaymentRequestBuilder | Builds expiring requests from the receiver's public key, asset and decimal amount |
| Contactless transport | NFC codec and receive orchestrator | Encodes requests, enforces byte limits and coordinates broadcast, waiting and cancellation states |
NFC delivery reports that the request reached the other device. A successful payment requires a separate confirmed Stellar transaction. Passkeys currently protect the app's access flow; the authentication-to-payment integration still needs server wiring and physical-device verification.
The web build defaults to a testnet UI preview. Native wallet and trustline code uses the network endpoints and issuer configured in src/lib/env.ts. Mainnet requires explicit configuration and is not the verified operating mode of this prototype.
- XLM: Stellar's native asset, used by the wallet and its reserve checks.
- USDC: Selected using both asset code and configured issuer. Testnet values are for testing; they do not represent real funds.
- Amount precision: Wallet asset definitions allow seven decimal places. The current receive UI restricts USDC input to two decimals as a product rule; that is distinct from Stellar's asset precision.
See Stellar's asset model for code/issuer identification and amount precision.
- Coordinate the shared payment contract across both repositories.
- Cover trustline key ownership and reserve checks.
- Improve NFC byte-size and decoding coverage.
Nudge is an early Stellar testnet prototype under active development. It is not ready for real funds or production payment use.
| Area | Current status |
|---|---|
| Web preview | Live UI preview; browser passkey setup and NFC are unavailable |
| Mobile | Native onboarding, wallet and receive-flow code; requires a development build and physical-device validation |
| Sending payments | Client Send screen is a scaffold; end-to-end payment completion is not demonstrated |
| Authentication | Client auth uses local challenges and placeholder API responses; server verification integration remains unfinished |
| Shared payload | Client and server currently use different type/timestamp formats; reconciliation is tracked in the contribution backlog |
| Compatibility | Passkey RP domain and app/storage/NFC identifiers are pending a coordinated migration decision |
| Area | Current status |
| ---------------- | ------------------------------------------------------------------------------------------------------------------- |
| Web preview | Live UI preview; browser passkey setup and NFC are unavailable |
| Mobile | Native onboarding, wallet and receive-flow code; requires a development build and physical-device validation |
| Sending payments | Client Send screen is a scaffold; end-to-end payment completion is not demonstrated |
| Authentication | Client auth uses local challenges and placeholder API responses; server verification integration remains unfinished |
| Shared payload | Client and server share the payment-request version 1 wire format with ISO-8601 UTC timestamps |
| Compatibility | Passkey RP domain and app/storage/NFC identifiers are pending a coordinated migration decision |
See contributing and Wave preparation for current priorities. Architecture/build-plan documents include intended features and must not be treated as proof of completed functionality.
- Node.js v20+
- npm
- Xcode (iOS) or Android Studio (Android)
- Physical NFC devices for end-to-end NFC validation
Expo Go is not supported. NFC, passkeys, and SecureStore require a development build (
npx expo run:iosornpx expo run:android, or EAS dev build).
-
Clone and install dependencies:
npm ci cp .env.example .env
Note: Use
npm cifor fresh clones and continuous integration to guarantee reproducible dependency trees matchingpackage-lock.json.npm installshould only be used when intentionally modifying dependencies or updating the lockfile. -
Build requirements for native features:
- Run
npx expo prebuildandnpx expo run:android/npx expo run:iosfor device validation. - Use
npm run dev-clientto launch a dev-client session after native dependencies are installed.
- Run
-
Install and authenticate EAS CLI:
npm install -g eas-cli eas login
-
Create a development build:
npm run dev:build:android # or npm run dev:build:ios -
Start the dev client:
npm run dev-client
-
Native rebuild required after changes to
app.config.tsplugins, permissions, or native dependencies (expo-dev-client,expo-secure-store,react-native-nfc-manager,react-native-passkey).
Run before opening a PR:
npm run typecheck
npm run lint
npm run test
npm run format:check- Passkey ADR:
docs/adr-passkey-library.md - Stellar ADR:
docs/adr-stellar-sdk.md - NFC ADR:
docs/adr-nfc-library.md - Spike PoC page: open
/c05in the app after starting the dev-client.
The NFC core stack lives under src/features/nfc/:
| Module | Purpose |
|---|---|
services/NfcService.* |
Native abstraction (support checks, sessions) |
schemas/paymentRequest.ts |
Zod schema for payment_request.v1 payloads |
services/NfcPayloadCodec.ts |
Compact JSON encode/decode with size guard |
services/NfcWriter.ts / NfcReader.ts |
Writer (receiver) and reader (payer) sessions |
state/nfcSessionStore.ts |
Session state machine + nfcActive lock coordination |
services/nfc-spike.ts |
Manual PoC helpers for device verification |
npx expo prebuild --clean
npx expo run:ios
# or
npx expo run:androidimport { nfcSpikeCheckSupport } from '@/features/nfc/services/nfc-spike';
const { supported, enabled } = await nfcSpikeCheckSupport();See docs/adr-nfc-library.md for platform constraints and payload limits (880 bytes max).
| Command | Description |
|---|---|
npm start |
Start Expo dev server |
npm run dev-client |
Start Expo with dev-client |
npm run build |
Export the web app to dist |
npm run typecheck |
TypeScript check |
npm test |
Run unit tests |
npm run lint |
ESLint via Expo |
npm run format:check |
Prettier check (CI) |
Copy .env.example to .env and fill in your values (see the file for inline docs). Key groups:
| Variable group | Key variables |
|---|---|
| Stellar | EXPO_PUBLIC_STELLAR_NETWORK, EXPO_PUBLIC_HORIZON_URL, EXPO_PUBLIC_RPC_URL, EXPO_PUBLIC_USDC_ISSUER |
- Never commit secrets — keep keys, seed phrases, and
.envfiles out of source control. - Testnet values have no real-world value; treat testnet deployments as experimental.
- Keys never leave the wallet — signing is delegated to the user's Stellar wallet; the app does not store secret keys.
- Report vulnerabilities per
SECURITY.mdwhere present rather than opening a public issue.
MIT. Existing Expo attribution is preserved.
