Remiq is a stablecoin-powered remittance app built on Solana. Users load USDC via credit/debit card, send USDC to existing Remiq users by email, and cash out to a bank account through a service wallet workflow.
| Feature | Status | Notes |
|---|---|---|
| Email OTP auth | Live | Powered by Privy |
| Embedded Solana wallet | Live | Auto-created on login |
| Live USDC balance | Live | Helius devnet RPC, refreshes every 15s |
| Receive (QR code) | Live | Displays wallet address + QR |
| Send by email | Live | Sponsored P2P USDC transfer to existing Remiq/Privy users |
| Load via card | Live | Dodo Payments test environment (1-150 USDC top-ups) |
| Cash out to bank | Live | On-chain USDC → service wallet, 1-day processing |
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router), TypeScript |
| Styling | Tailwind CSS v4 |
| Auth + Wallets | Privy v3 (@privy-io/react-auth) |
| Blockchain | Solana devnet via @solana/web3.js v1 |
| Token transfers | @solana/spl-token |
| RPC provider | Helius |
| Payments (card) | Dodo Payments |
| QR codes | qrcode.react |
| Base58 encoding | bs58 |
remiq/
├── src/
│ ├── app/
│ │ ├── page.tsx # Landing page
│ │ ├── layout.tsx # Root layout with Privy provider
│ │ ├── globals.css # Global styles, design tokens, animations
│ │ ├── login/
│ │ │ └── page.tsx # Email OTP login/signup
│ │ ├── dashboard/
│ │ │ └── page.tsx # Main app: balance, send, receive, load, cash out
│ │ └── api/
│ │ ├── payments/ # Load, webhook, activity, and cash-out routes
│ │ ├── transfers/ # Sponsored send-by-email transfer route
│ │ └── users/ # Recipient lookup and bank profile routes
│ └── components/
│ └── PrivyWrapper.tsx # Privy provider config (Solana, embedded wallets)
├── .env.local # Secret keys (not committed)
├── package.json
├── supabase-send-by-email.sql # Supabase tables for users, transfers, and bank profiles
└── README.md
Create a .env.local file in the project root with the following:
| Variable | Description | Source |
|---|---|---|
NEXT_PUBLIC_PRIVY_APP_ID |
Privy app ID (public) | dashboard.privy.io |
PRIVY_APP_SECRET |
Privy app secret (server-only) | dashboard.privy.io |
NEXT_PUBLIC_HELIUS_API_KEY |
Helius RPC API key | dashboard.helius.dev |
DODO_PAYMENTS_API_KEY |
Dodo Payments secret key (server-only) | dashboard.dodopayments.com |
DODO_PRODUCT_ID |
Dodo one-time Pay What You Want top-up product ID | Create a PWYW product with $1 min and $150 max |
NEXT_PUBLIC_SERVICE_WALLET_ADDRESS |
Solana wallet that receives cash-out funds | Your service wallet |
SERVICE_WALLET_PRIVATE_KEY |
Server-only Solana service wallet secret key, JSON array or base58 | Your devnet service wallet |
DODO_PAYMENTS_WEBHOOK_KEY |
Dodo webhook signing secret | Dodo webhook settings |
APP_BASE_URL |
Public app URL used for Dodo checkout return URLs | Vercel/project domain |
SUPABASE_URL |
Supabase project URL for the server ledger | Supabase project settings |
SUPABASE_SERVICE_ROLE_KEY |
Server-only Supabase service role key for ledger writes | Supabase API settings |
- User enters email → receives OTP → verified by Privy
- On first login, Privy auto-creates an embedded Solana wallet
- Authenticated users are redirected to
/dashboard
- On dashboard load, the app calls Helius devnet RPC (
getTokenAccountsByOwner) with the Circle USDC devnet mint (4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU) - Balance sums all matching USDC token accounts and converts raw token units (6 decimals) to a human-readable USDC amount
- Refreshes every 15 seconds automatically, with a manual refresh button and optimistic updates after successful loads, sends, and cash-outs
- User clicks "Load Balance" in the dashboard
- Frontend calls
POST /api/payments/loadwith the user's email, Solana wallet address, and selected amount - Server validates a 1-150 USDC amount, creates a Supabase load intent, and creates a Dodo checkout with that dynamic top-up amount
- User is redirected to the Dodo hosted checkout page
- After payment, Dodo redirects back to
/dashboard?load=pending&load_id=... - Dodo sends a verified
payment.succeededwebhook to/api/payments/webhook - The backend idempotently sends devnet USDC from the service wallet to the user's Privy wallet
- Dashboard polls load status and refreshes balance once the Solana transfer is confirmed
Note: Configure
DODO_PRODUCT_IDas a one-time Pay What You Want top-up product with$1minimum and$150maximum. Remiq passes the selected top-up amount to Dodo in cents, with quantity fixed at1.
- Bank details step — User enters account holder name, bank name, account number, and IFSC code. Details are stored per Privy user in Supabase and pre-filled on return.
- Amount step — User enters USDC amount. Live balance is shown. Client and server validation reject zero, negative, fractional micro-unit, and unsafe amounts.
- On-chain transfer — Server prepares a sponsored Solana SPL token transfer transaction:
- Creates associated token account for service wallet (idempotent)
- Transfers specified USDC amount from user wallet to service wallet
- Uses the service wallet as fee payer, so the user only needs USDC for the cash-out amount
- User signs via Privy's
useSignTransaction - Server adds the service wallet signature, broadcasts, and confirms on devnet
- Backend ledger — Server verifies the signed transaction amount, source, destination, fee payer, and memo before storing the cash-out, bank details, and payout status in Supabase
- Intent binding — Cash-out transactions include a Remiq memo with the server-created cash-out ID, so public Solana signatures cannot be replayed with different bank details
- Confirmation — User sees "Payment will be processed within 1 working day" message
- Dashboard calls
/api/payments/activity?walletAddress=... - Activity is scoped to the current embedded wallet only
- Shows wallet-specific balance loads, cash-outs, sent transfers, and received transfers from Supabase
- Bank account details are never returned to the dashboard activity feed
- User enters a recipient email and amount
- Server resolves the recipient through Privy and requires an existing embedded Solana wallet
- Server prepares a sponsored P2P USDC transfer from sender wallet to recipient wallet with a Remiq memo
- User signs via Privy's
useSignTransaction - Server verifies recipient, amount, sender, fee payer, and memo before broadcasting
- Supabase records the transfer so sender and recipient both see it in Recent Activity
Defined in src/app/globals.css via Tailwind CSS v4 @theme:
| Token | Value | Usage |
|---|---|---|
--color-background |
#0A0A0A |
Page background (onyx black) |
--color-foreground |
#ffffff |
Primary text |
--color-cyber-yellow |
#FDE047 |
Brand accent, CTAs |
--color-onyx |
#0A0A0A |
Deep background |
--color-charcoal |
#171717 |
Card backgrounds |
--color-deep-gray |
#262626 |
Input backgrounds, borders |
Custom animations: animate-float, animate-ticker, animate-pulse-dot, animate-flow-1/2/3
# Install dependencies
npm install --legacy-peer-deps
# Start dev server
npm run dev
# Type-check
npx tsc --noEmit
# Build for production
npm run buildOpen http://localhost:3000 in your browser.
- Create an app at dashboard.privy.io
- Enable Email OTP login
- Enable Solana embedded wallets
- Set allowed domains to include
localhost:3000and your production domain - Copy App ID →
NEXT_PUBLIC_PRIVY_APP_IDand App Secret →PRIVY_APP_SECRET
- Create a free account at dashboard.helius.dev
- Create an API key that can be used for devnet and mainnet RPC calls
- Copy it →
NEXT_PUBLIC_HELIUS_API_KEY
- Create an account at dashboard.dodopayments.com
- Use the test environment for development
- Create a Pay What You Want one-time product with a $1-$150 range for top-ups
- Copy the product ID →
DODO_PRODUCT_ID - Generate an API key →
DODO_PAYMENTS_API_KEY - Add
http://localhost:3000to allowed redirect URLs - Set
APP_BASE_URLto the deployed app URL before using deployed webhooks - Add a webhook endpoint for
POST /api/payments/webhook - Subscribe to
payment.succeeded - Copy the webhook signing secret →
DODO_PAYMENTS_WEBHOOK_KEY
- Fund the service wallet with devnet SOL for fees
- Fund the service wallet's devnet USDC ATA with enough USDC to cover loads
- To test mainnet mode, fund the same service wallet public key on mainnet with SOL for fees and PUSD for liquidity
- Set
NEXT_PUBLIC_SERVICE_WALLET_ADDRESSto the public key - Set
SERVICE_WALLET_PRIVATE_KEYto the server-only secret key as either a JSON number array or base58 string - Do not expose
SERVICE_WALLET_PRIVATE_KEYin anyNEXT_PUBLIC_*variable
- Remiq stores load intents, processed webhook IDs, cash-out intents, cash-out records, user profiles, bank profiles, transfer intents, and transfer records in Supabase
- Server routes use
SUPABASE_SERVICE_ROLE_KEY; never expose it with aNEXT_PUBLIC_prefix - Row Level Security should stay enabled with deny-by-default policies for
anonandauthenticated - Cash-out bank details are stored per Privy user in Supabase for payout processing; encrypt or tokenize them before production launch
- Run
supabase-send-by-email.sqlin the Supabase SQL editor before using Send by Email or saved bank profiles - Run
supabase-network-asset-migration.sqlbefore using the Dashboard network toggle so devnet USDC and mainnet PUSD activity stay separated
- Privy remains the identity source of truth; set
PRIVY_APP_SECRETserver-side so API routes can resolve users by email - Remiq resolves recipients with Privy, caches normalized email and embedded Solana wallet in
user_profiles, then creates transfer intents and records in Supabase - Sends are sponsored stablecoin transfers: devnet uses USDC and mainnet uses Palm USD (PUSD). The server prepares the transaction with the service wallet as fee payer, the sender signs with Privy, and the server verifies recipient, amount, memo, mint, and sender before broadcasting
- MVP sends only to existing Remiq/Privy users with embedded Solana wallets
- The Dashboard can switch between devnet USDC and mainnet PUSD
- Devnet uses
https://devnet.helius-rpc.com/?api-key=...and USDC mint4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU - Mainnet uses
https://mainnet.helius-rpc.com/?api-key=...and Palm USD mintCZzgUBvxaMLwMhVSLgqJn3npmxoTo6nzMNQPAnwtHF3s - Mainnet balance, send, and cash-out operate on PUSD. Card loading remains disabled on mainnet until Dodo Payments production is integrated
Before going live, the following changes are required:
- Switch Solana network from
devnettomainnet-beta - Add Dashboard toggle for devnet USDC and mainnet Palm USD (PUSD)
- Add Helius mainnet RPC support via
https://mainnet.helius-rpc.com/?api-key=... - Add mainnet PUSD mint (
CZzgUBvxaMLwMhVSLgqJn3npmxoTo6nzMNQPAnwtHF3s) - Switch Dodo Payments to live environment and update API key
- Add server-side verification of Dodo webhook events before crediting accounts
- Replace local SQLite load ledger with a production database
- Encrypt or tokenize stored bank details before production
- Implement actual fiat-to-recipient bank payout logic in the service wallet backend
- Rate-limit and authenticate public mutation endpoints
- Add proper error tracking (e.g. Sentry)