A React Native + Expo mobile app demonstrating Coinbase's Onramp v2 API with CDP Embedded Wallets, Apple Pay integration, and real-time push notifications.
Install Testflight and try the mobile app using this invite link!
- 🔐 Embedded Wallet: Automatic wallet creation via CDP
- 💳 Multiple Payment Methods: Apple Pay and Coinbase Widget
- 🌐 Multi-Network: EVM networks: Base Mainnet (gasless for USDC/EURC/cbBTC via Paymaster), Base Sepolia, Ethereum Mainnet, Ethereum Sepolia; Solana networks: Solana Mainnet, Solana Devnet.
- 🔔 Push Notifications: Real-time transaction updates
- 💸 Gasless Transfers: Paymaster support on Base (USDC, EURC, cbBTC)
- 📜 Transaction History: Complete purchase tracking
- 🧪 Sandbox Mode: Test without real transactions
- 📊 User Limits API: Real-time weekly spending & lifetime transaction limits for Apple Pay
- 🆘 Failed Transaction Support: Email support flow for failed transactions
- 💱 Crypto Cash Out (Offramp): Sell crypto back to fiat via a Coinbase-hosted flow, with on-chain transfer executed directly from the embedded wallet
- 🔍 Event Log: History tab surfaces Apple Pay WebView events and server-side webhook events for visibility into the transaction lifecycle
Onramp.Apple.Pay.Flow.MP4
Embedded.Wallet.Flow.MP4
- React Native + Expo Router
- TypeScript
- Onramp V2 Apple Pay integration
- CDP React Native SDK
- Node.js/Express backend
- Expo Notifications
- Node.js v20+
- iOS device or simulator
- Coinbase CDP account
# Clone and install dependencies
git clone <your-repo-url>
cd onramp-v2-new
npm install
cd server && npm install && cd ..- Go to CDP Portal
- Create a new project (or use existing)
- Copy your Project ID
- Generate API Keys (you'll need the key name and private key in
Ed25519)
# Copy template
cp .env.example .envEdit .env:
# CDP Project ID from portal
EXPO_PUBLIC_CDP_PROJECT_ID=your_project_id_here
# Local server (will update with ngrok URL in step 5)
EXPO_PUBLIC_BASE_URL=http://localhost:3000
# Crypto implementation selector
# - Set to 'true' for Expo Go (npx expo start)
# - Set to 'false' for development builds (npx expo run:ios)
EXPO_PUBLIC_USE_EXPO_CRYPTO=true# Copy template
cp server/.env.example server/.envEdit server/.env:
# CDP API Credentials from portal
CDP_API_KEY_ID=your_api_key_id
CDP_API_KEY_SECRET=your_private_key_here
# Optional: Webhook signing secret (for push notifications)
WEBHOOK_SECRET=your_webhook_secret
# Optional: APNs credentials (for production iOS push notifications)
# Leave empty to use Expo Push Service in development
APNS_KEY_ID=
APNS_TEAM_ID=
APNS_KEY=
# Optional: Database URL (for production deployment)
# Leave empty to use in-memory storage in development
# Supports Redis, MongoDB, or any compatible database
DATABASE_URL=Open two terminal windows:
# Terminal 1: Start backend server
cd server
npm run dev
# Should see: "Server running on http://localhost:3000"# Terminal 2: Start Expo
# Option A: Expo Go (requires USE_EXPO_CRYPTO=true in .env)
npx expo start
# Scan QR code with Expo Go app or press 'i' for iOS simulator
# Option B: Development build (set USE_EXPO_CRYPTO=false in .env)
npx expo run:ios
# Builds and installs native iOS app with full crypto supportNote: Webhooks enable real-time push notifications for transaction updates. The app works fully without webhooks, but you won't receive push notifications.
localhost. To enable webhooks, you need a publicly accessible URL.
Options:
- Development: Use a tunneling service (ngrok, localtunnel, etc.) to expose your local server
- Production: Deploy to a hosting platform (Vercel, Railway, Render, etc.)
Setup steps:
-
Get a public URL for your backend (e.g.,
https://your-domain.comorwebhook.site) -
Update
.env:EXPO_PUBLIC_BASE_URL=https://your-public-url
-
Create webhook subscription: Follow CDP webhook documentation using your webhook URL:
https://your-public-url/webhooks/onramp -
Restart Expo (press
rin terminal)
Testing on physical device without webhooks:
# Get your local network IP
ipconfig getifaddr en0
# Update .env: EXPO_PUBLIC_BASE_URL=http://192.168.1.100:3000- Open Expo Go app on your iOS device or iOS Simulator
- Scan the QR code from Terminal 2
- App will load on your device or simulator
Push notifications work automatically via Expo Push Service - no additional setup needed!
- Sign In: Enter your email → Verify code
- Wallet Created: Embedded wallet auto-created
- Complete Profile:
- Verify phone (optional for testing)
- Select region
- Select Sandbox / Production mode for Onramp transaction
- Home Tab: Fill out onramp form
- Select network (Base, Ethereum, Solana)
- Select asset (USDC, ETH, SOL, etc.)
- Enter amount
- Choose Payment:
- Apple Pay: Native iOS payment
- Coinbase Widget: Opens in-app browser (ASWebAuthenticationSession) with a deep link redirect back to the app on completion — see video below
Coinbase Widget setup notes:
- The app display name shown in the widget (e.g. "Onramp Mobile Demo") comes from Onramp Display Name in the CDP Portal
redirectUrlmust be added to your Onramp Domain Allowlist — see security requirements
IMG_1499.MP4
- Complete Purchase
- Receive Notification: Transaction status notification on Production
Test without real transactions:
- Go to Profile Tab
- Toggle Sandbox Mode ON
- Features enabled:
- Optional phone verification
- Any wallet address override accepted
- No real blockchain transactions
- Email verification still required for server authentication
Note: Sandbox mode auto-resets on app restart for safety.
Convert crypto in your mainnet wallet back to fiat:
- Go to Profile Tab → scroll to your mainnet balances
- Tap Cash Out (Offramp) on any balance row (Base, Ethereum, or Solana)
- Complete the Coinbase-hosted sell flow in the browser — select amount, payment method, and confirm
- After tapping "Cash out now", the app reopens automatically
- Review the locked amount and destination address (set by Coinbase)
- Tap Send Now to execute the on-chain transfer from your embedded wallet to Coinbase
Note: You have 30 minutes to complete the on-chain send after confirming in the Coinbase widget. Offramp is mainnet-only and requires a real wallet balance.
IMG_1012.MP4
/app/ # Expo Router pages
├─ (tabs)/ # Bottom tab navigation
│ ├─ index.tsx # Home: Onramp form
│ ├─ profile.tsx # Settings & wallet
│ └─ history.tsx # Transaction history
├─ auth/ # Email/phone verification
└─ transfer.tsx # Token transfer
/components/ # React components
├─ onramp/ # Onramp-specific UI
└─ ui/ # Reusable UI components
/hooks/ # Custom hooks
└─ useOnramp.ts # Onramp logic & API calls
/utils/ # Helper functions
├─ sharedState.ts # Global state
├─ create*.ts # Onramp v2 API
└─ fetch*.ts # Onramp v1 API
/server/ # Backend proxy
└─ src/app.ts # Express server
The app creates two wallet types:
- EOA: Standard wallet (externally owned account)
- Smart Account: ERC-4337 account abstraction
Important: The app displays Smart Account balances only. All EVM onramp funds automatically go to the Smart Account.
For security, API keys are never exposed to the client. Backend signs short‑lived ES256 JWTs with CDP API keys; only the backend ever sees API secrets.
Client App → Backend Proxy → Coinbase API
(has API keys)
This prevents API key theft if someone inspects your app.
Mobile client obtains a user access token from the CDP RN SDK and sends it to the backend; backend validates with the CDP End User API before calling Onramp or wallet APIs.
Onramp webhooks are HMAC‑signed; backend verifies signatures before processing events and sending push notifications.
Wallet keys are managed via the CDP SDK and stored in secure device storage; transport is TLS‑only.
On Base network, transfers of USDC, EURC, or BTC are gasless (no ETH needed for gas) thanks to Coinbase Paymaster.
- Verify
EXPO_PUBLIC_CDP_PROJECT_IDis correct - Check CDP Portal for project status
- iOS Simulator: Push notifications do not work on iOS Simulator. Use a physical device.
- Should work automatically in Expo Go on physical devices (uses Expo Push Service)
- For webhooks, verify your backend has a public URL (localhost won't work)
- Check
.envhas correctEXPO_PUBLIC_BASE_URL - Verify webhook subscription is created in CDP Portal
- Restart Expo after updating
.env
- Enable Sandbox Mode for testing
- Verify phone verification is complete (for Apple Pay)
- Check backend logs:
cd server && npm run dev
- Enable Sandbox Mode in Profile tab
- All transactions will be simulated
- No real blockchain interaction
cd server
npm run dev
# Watch console for API requests/responsesCheck Expo logs:
# In Expo terminal (Terminal 2)
# Look for lines with [PUSH] prefix- Sign out from Profile tab
- Force close app
- Relaunch
| Variable | Description |
|---|---|
EXPO_PUBLIC_CDP_PROJECT_ID |
Your CDP project ID |
EXPO_PUBLIC_BASE_URL |
Backend server URL (public URL for webhooks, or http://localhost:3000 for local testing) |
EXPO_PUBLIC_USE_EXPO_CRYPTO |
true for Expo Go (npx expo start), false for dev builds (npx expo run:ios) |
| Variable | Description |
|---|---|
CDP_API_KEY_ID |
CDP API key ID |
CDP_API_KEY_SECRET |
CDP API private key |
| Variable | Description |
|---|---|
WEBHOOK_SECRET |
Webhook signing secret from CDP Portal (required for push notifications) |
APNS_KEY_ID |
Apple Push Notification service key ID (for production iOS notifications) |
APNS_TEAM_ID |
Apple Developer Team ID |
APNS_KEY |
APNs private key (.p8 file content) |
DATABASE_URL |
Database URL for production deployment (supports Redis, MongoDB, etc. - uses in-memory storage if not set) |
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.