This file provides guidance to AI agents when working with code in this repository.
WPay Mobile POS is a React Native point-of-sale application that enables merchants to accept cryptocurrency payments via WalletConnect. The app allows merchants to:
- Generate QR codes for payment requests
- Accept payments through WalletConnect-compatible wallets
- Print thermal receipts for completed transactions
- Manage merchant settings and configurations
- Support multiple branded variants (white-labeling)
The app is built with Expo and React Native, supporting Android, iOS, and Web platforms.
- React Native: 0.81.5
- Expo: ^54.0.23 (with Expo Router for navigation)
- TypeScript: ~5.9.2
- React: 19.1.0
- @tanstack/react-query: Data fetching and caching
- zustand: State management (lightweight alternative to Redux)
- react-hook-form: Form handling
- expo-router: File-based routing
- react-native-thermal-pos-printer: Thermal printer integration
- react-native-qrcode-skia: QR code generation
- @shopify/react-native-skia: Graphics rendering
- expo-secure-store: Secure credential storage
- react-native-mmkv: Fast key-value storage
- @sentry/react-native: Error tracking and monitoring
- ESLint: Code linting
- Prettier: Code formatting
- Jest: Testing framework
- patch-package: Library patching for custom fixes
pos-app/
├── app/ # Expo Router screens (file-based routing)
│ ├── index.tsx # Home screen
│ ├── amount.tsx # Amount input screen
│ ├── scan.tsx # QR code display & payment polling
│ ├── payment-success.tsx # Success screen with receipt printing
│ ├── payment-failure.tsx # Failure screen
│ ├── settings.tsx # Settings & configuration
│ └── logs.tsx # Debug logs viewer
├── components/ # Reusable UI components
├── constants/ # Theme, variants, spacing, etc.
├── hooks/ # Custom React hooks
├── services/ # API client and payment services
├── store/ # Zustand state stores
├── utils/ # Utility functions
└── assets/ # Images, fonts, icons
The app uses Zustand for state management with two main stores:
-
useSettingsStore(store/useSettingsStore.ts)- Merchant ID and API key
- Theme mode (light/dark)
- Selected variant
- Device ID
- Biometric authentication settings
- Printer connection status
-
useLogsStore(store/useLogsStore.ts)- Debug logs for troubleshooting
- Log levels: info, warning, error
Uses Expo Router with file-based routing:
- Routes are defined by file structure in
app/directory - Navigation via
router.push(),router.replace(),router.dismiss() - Type-safe routing with TypeScript
-
Home Screen (
app/index.tsx)- "New sale" button to start payment
- "Settings" button for configuration
- Validates merchant setup before allowing payments
-
Amount Input (
app/amount.tsx)- Custom numeric keyboard component
- Amount formatting (always 2 decimal places)
- Form validation with react-hook-form
-
QR Code Display (
app/scan.tsx)- Generates payment request via API
- Displays QR code for wallet scanning
- Polls payment status every 2 seconds
- Handles payment success/failure navigation
- Shows WalletConnect loading animation
-
Payment Success (
app/payment-success.tsx)- Animated expanding circle background
- Displays payment details
- Option to print receipt
- "New Payment" button to start over
-
Payment Failure (
app/payment-failure.tsx)- Displays error information
- Allows retry or return to home
- Thermal Printer Support (
utils/printer.ts)- Bluetooth/USB printer connection
- Receipt generation with:
- Variant-specific logo (base64 encoded)
- Transaction ID, date, payment method
- Amount in USD
- Token symbol and amount (if applicable)
- Network name
- Automatic paper cutting after print
- Error handling and logging
- Merchant Setup (
app/settings.tsx)- Merchant ID input
- API key configuration (stored securely)
- Device ID generation/management
- Variant selection dropdown
- Theme mode toggle (light/dark)
- Biometric authentication toggle
- Printer connection testing
- Test receipt printing
- App version display
- Logs viewer access
- Secure Storage: API keys stored in
expo-secure-store - Biometric Authentication: Face ID / Touch ID support
- PIN Protection: Optional PIN modal for sensitive actions
- Secure Credentials: Never logged or exposed
- Light/Dark Mode: System-aware theme switching
- Variant Support: Multiple branded variants (see Variants System section)
- Dynamic Colors: Theme colors adapt based on variant selection
- Accessibility: Proper contrast ratios maintained
- Base URL from
EXPO_PUBLIC_API_URLenvironment variable - Request/response interceptors
- Error handling
startPayment(request)
- Creates new payment request
- Requires merchant ID and API key
- Returns payment ID and QR code URI
getPaymentStatus(paymentId)
- Polls payment status
- Returns payment state (pending, completed, failed)
- Includes transaction details when completed
All API requests include:
Api-Key: Merchant API keyMerchant-Id: Merchant identifierSdk-Name: "pos-device"Sdk-Version: "1.0.0"Sdk-Platform: "react-native"
Required environment variables (.env):
EXPO_PUBLIC_PROJECT_ID="" # WalletConnect project ID
EXPO_PUBLIC_SENTRY_DSN="" # Sentry error tracking DSN
SENTRY_AUTH_TOKEN="" # Sentry authentication token
EXPO_PUBLIC_API_URL="" # Payment API base URL
EXPO_PUBLIC_GATEWAY_URL="" # WalletConnect gateway URLCopy .env.example to .env and fill in values.
- Node.js (LTS version recommended)
- Android Studio (for Android development)
- Xcode (for iOS development on macOS)
- Expo CLI
-
Install dependencies
npm install
-
Set up environment variables
cp .env.example .env # Edit .env with your values -
Create native folders
npm run prebuild
-
Start development server
npm run android # Android npm run ios # iOS npm run web # Web
npm start: Start Expo dev servernpm run android: Run on Androidnpm run ios: Run on iOSnpm run web: Run on webnpm run android:build: Build Android release APKnpm run lint: Run ESLintnpm test: Run Jest tests
app/_layout.tsx: Root layout with navigation setupapp/index.tsx: Home screen entry pointapp/amount.tsx: Payment amount inputapp/scan.tsx: QR code display and payment pollingapp/payment-success.tsx: Success screen with animationsapp/payment-failure.tsx: Error handling screenapp/settings.tsx: Settings and configuration
services/client.ts: API client configurationservices/payment.ts: Payment API functionsservices/hooks.ts: React Query hooks for API callsapi/payment.ts: Payment API types/interfacesapi/payment-status.ts: Payment status types
utils/printer.ts: Thermal printer integrationutils/currency.ts: Currency formatting utilitiesutils/misc.ts: Date formatting and helpersutils/navigation.ts: Navigation helpersutils/secure-storage.ts: Secure storage wrapperutils/biometrics.ts: Biometric authentication helpers
store/useSettingsStore.ts: App settings and configurationstore/useLogsStore.ts: Debug logging store
constants/theme.ts: Base theme color definitionsconstants/variants.ts: Variant configurationsconstants/printer-logos.ts: Base64-encoded printer logosconstants/spacing.ts: Spacing scale constants
components/qr-code.tsx: QR code display componentcomponents/numeric-keyboard.tsx: Custom numeric inputcomponents/pin-modal.tsx: PIN entry modalcomponents/button.tsx: Themed button componentcomponents/themed-text.tsx: Theme-aware text component
This POS app supports a variants system that allows for minor UI customizations while maintaining the same core functionality. Variants enable white-labeling and branding customization for different clients or use cases.
-
Theme System (
constants/theme.ts)- Defines base color palette for light and dark modes
- Provides default colors used across the app
- Colors can be overridden by variants
-
Variants Configuration (
constants/variants.ts)- Defines available variants and their customizations
- Each variant can override theme colors, logos, and default theme mode
- Variants are selected via settings and stored in Zustand store
-
Printer Logos (
constants/printer-logos.ts)- Contains base64-encoded logos for thermal printer receipts
- Each variant has its own printer logo
- Default logo uses
brand.pngconverted to base64
Each variant is defined with:
- name: Display name (e.g., "Solflare", "Binance")
- brandLogo: Image asset for UI branding (loaded via
require()) - brandLogoWidth: Optional width override for brand logo
- printerLogo: Base64-encoded string for receipt printing
- defaultTheme: Optional default theme mode ("light" or "dark")
- colors: Color overrides for light and dark themes
Variants can override any color from the base theme:
- Colors are merged with base theme colors
- Only specified colors are overridden; others use defaults
- Both light and dark theme overrides are supported
solflare: {
name: "Solflare",
brandLogo: require("@/assets/images/variants/solflare_brand.png"),
printerLogo: SOLFLARE_LOGO_BASE64,
defaultTheme: "dark",
colors: {
light: {
"icon-accent-primary": "#FFEF46",
"bg-accent-primary": "#FFEF46",
"bg-payment-success": "#FFEF46",
"text-payment-success": "#202020",
"border-payment-success": "#363636",
"text-invert": "#202020",
},
dark: {
// Similar overrides for dark theme
},
},
}- default: Base variant with blue accent colors (#0988F0)
- solflare: Yellow/gold branding (#FFEF46)
- binance: Yellow branding (#FCD533)
- phantom: Purple branding (#AB9FF2)
- solana: Purple branding (#9945FF)
Commonly overridden colors in variants:
bg-accent-primary: Primary accent backgroundbg-payment-success: Payment success screen backgroundicon-accent-primary: Accent icon colortext-payment-success: Text color on success screenborder-payment-success: Border color for success elementstext-invert: Inverted text (for dark backgrounds)
import { useTheme } from "@/hooks/use-theme-color";
const Theme = useTheme();
// Theme["bg-payment-success"] will use variant override if setVariants are stored in Zustand store (store/useSettingsStore.ts):
- Selected variant persists across app sessions
- Can be changed in Settings screen
- Affects all themed components immediately
-
Add variant logo image
- Place in
assets/images/variants/<variant-name>_brand.png - PNG format recommended
- Place in
-
Convert logo to base64 for printer
- Use online tool or command:
base64 -i assets/images/variants/<variant-name>_brand.png - Add to
constants/printer-logos.tsasexport const <VARIANT>_LOGO_BASE64
- Use online tool or command:
-
Define variant in
constants/variants.ts- Add variant name to
VariantNametype - Import printer logo base64
- Add variant configuration to
Variantsobject - Specify color overrides for light/dark themes
- Add variant name to
-
Update version code (if needed)
- Increment
expo.android.versionCodeinapp.json
- Increment
// 1. In printer-logos.ts
export const MYVARIANT_LOGO_BASE64 = "data:image/png;base64,...";
// 2. In variants.ts
import { MYVARIANT_LOGO_BASE64 } from "./printer-logos";
export type VariantName =
| "default"
| "solflare"
| "binance"
| "phantom"
| "solana"
| "myvariant"; // Add here
export const Variants: Record<VariantName, Variant> = {
// ... existing variants
myvariant: {
name: "My Variant",
brandLogo: require("@/assets/images/variants/myvariant_brand.png"),
printerLogo: MYVARIANT_LOGO_BASE64,
defaultTheme: "light",
colors: {
light: {
"bg-accent-primary": "#CUSTOM_COLOR",
"bg-payment-success": "#CUSTOM_COLOR",
// ... other overrides
},
dark: {
// ... dark theme overrides
},
},
},
};-
Color Contrast: When overriding colors, ensure sufficient contrast for accessibility
- Light backgrounds need dark text
- Dark backgrounds need light text
- Some variants use
text-invertoverride for better contrast
-
Printer Logos: Must be base64-encoded PNG strings
- Format:
"data:image/png;base64,<base64-string>" - Used in thermal printer receipts
- Logo size is automatically handled by printer library
- Format:
-
Default Theme: Variants can specify a default theme mode
- Users can still switch themes manually
- Default applies on first launch
-
Payment Success Color: The
bg-payment-successcolor is used for:- Payment success screen background (expanding circle animation)
- Success screen buttons
- Success screen text (via
text-payment-success)
-
Variant Persistence: Selected variant is stored in Zustand store
- Persists across app restarts
- Can be changed in Settings screen
- Open Settings screen
- Select different variants from dropdown
- Verify:
- Brand logo changes in header
- Accent colors update throughout app
- Payment success screen uses variant colors
- Receipt printing uses variant logo
constants/theme.ts: Base theme colorsconstants/variants.ts: Variant definitionsconstants/printer-logos.ts: Printer logo base64 stringsstore/useSettingsStore.ts: Variant selection stateapp/settings.tsx: Variant selection UIhooks/use-theme-color.ts: Theme color hook with variant support
-
Required Files (get from mobile team or 1Password):
android/secrets.propertiesandroid/app/wc_rn_upload.keystore
-
Build Release APK:
npm run android:build
Output:
android/app/build/outputs/apk/release/app-release.apk -
Install via USB:
adb devices # Get device ID adb -s <DEVICE_ID> install android/app/build/outputs/apk/release/app-release.apk
app.json.
- Increment version code: Update
expo.android.versionCodeinapp.jsonfor each change - Current version code: Check the current value in
app.jsonand increment by 1 - Why: Android requires a unique version code for each release. Without incrementing, new builds cannot be installed over previous versions
- Example: If current version code is
15, change it to16for your changes - Current version code: 16
- @tanstack/react-query: Manages API calls, caching, and polling for payment status
- zustand: Lightweight state management for settings and logs
- expo-router: File-based routing system
- react-native-thermal-pos-printer: Bluetooth/USB thermal printer integration
- react-native-qrcode-skia: QR code generation for payment requests
- expo-secure-store: Secure storage for API keys and sensitive data
- react-native-mmkv: Fast key-value storage for non-sensitive data
- expo-local-authentication: Biometric authentication (Face ID/Touch ID)
- @sentry/react-native: Error tracking and crash reporting
- react-hook-form: Form handling and validation
- react-native-reanimated: Animations (used in payment success screen)
import { useTheme } from "@/hooks/use-theme-color";
const Theme = useTheme();
// Access colors: Theme["bg-accent-primary"]import { router } from "expo-router";
// Navigate to screen
router.push("/amount");
// Navigate with params
router.push({
pathname: "/scan",
params: { amount: "10.00" }
});
// Replace current screen
router.replace("/payment-success");
// Dismiss modal
router.dismiss();import { usePaymentStatus } from "@/services/hooks";
const { data, isLoading, error } = usePaymentStatus(paymentId, {
enabled: !!paymentId,
refetchInterval: 2000, // Poll every 2 seconds
});import { secureStorage, SECURE_STORAGE_KEYS } from "@/utils/secure-storage";
// Store
await secureStorage.setItem(SECURE_STORAGE_KEYS.MERCHANT_API_KEY, apiKey);
// Retrieve
const apiKey = await secureStorage.getItem(SECURE_STORAGE_KEYS.MERCHANT_API_KEY);console.log() statements in production code.
-
Use the logging system: For debugging, use the app's built-in logging system via
useLogsStore:import { useLogsStore } from "@/store/useLogsStore"; const addLog = useLogsStore((state) => state.addLog); addLog("info", "Payment completed", "payment-success", "handlePrintReceipt");
-
Remove console.logs before committing: Always remove any
console.log(),console.error(), or other console statements before committing code. -
View logs in app: Users can view logs in the Settings screen → View Logs
-
Production builds: Console statements can impact performance and expose sensitive information in production builds.
- Follow TypeScript best practices
- Use ESLint and Prettier for consistent formatting
- Prefer functional components with hooks
- Use TypeScript types/interfaces for all props and data structures
- Minimal comments: Do not add comments unless absolutely necessary to understand the code. Code should be self-documenting through clear naming and structure. Avoid explanatory comments that describe what the code does - the code itself should be clear enough.
- No unused variables: Ensure code changes do not leave unused variables, imports, or functions. ESLint will flag these - fix them before committing.
- No trailing whitespace: New code must not have trailing whitespace at the end of lines. Most editors can be configured to remove trailing whitespace on save.
- Run lint after changing code: Always run
npm run lintafter making code changes to ensure code quality and catch any formatting or linting issues before committing. - Check TypeScript errors: Always run
npx tsc --noEmitafter making code changes to check for TypeScript errors. Fix any TypeScript errors in files you've modified before committing. Note: Pre-existing TypeScript errors in other files can be ignored if they're unrelated to your changes.
- Avoid testing mocked components: Component tests that mock underlying UI primitives (PressableScale, Pressable, QRCodeSkia, etc.) don't provide real value - they just test that mocks work correctly, not actual component behavior. For meaningful component testing, either:
- Use the real components (may require native setup)
- Focus on testing business logic in hooks/utils instead
- Use E2E tests for UI behavior
- Focus on business logic: Unit tests should focus on utilities, stores, services, and hooks that contain actual business logic rather than UI rendering.
- Use testID for E2E tests: Always use
testIDprops to identify components in E2E tests (Maestro). Prefer testID over text strings as text can change and may be localized. Add testID to interactive components (buttons, inputs, etc.) that need to be targeted by E2E tests.
- Check Bluetooth permissions in Android settings
- Verify printer is paired and connected
- Check logs in Settings → View Logs
- Test connection via Settings → Test Printer Connection
- Verify merchant ID and API key in Settings
- Check network connectivity
- Review logs for API errors
- Ensure
EXPO_PUBLIC_API_URLis correctly configured
- Run
npm run prebuildafter dependency changes - Clear Metro cache:
npx expo start --clear - Clean Android build:
cd android && ./gradlew clean
- README.md: Setup and development instructions
- app.json: Expo configuration
- package.json: Dependencies and scripts
- tsconfig.json: TypeScript configuration