A production-ready cross-border remittance platform built on the Stellar Network, enabling fast, secure, and low-cost payments across African countries using USDC stablecoin and XLM.
AfriPay connects senders with registered payout agents who handle local fiat distribution, with the Stellar blockchain managing escrow, fee collection, and settlement. Designed for emerging markets where stablecoin remittance rails can significantly reduce cross-border payment costs.
AfriPay implements an escrow-based remittance flow on Stellar:
- A sender creates a remittance by depositing USDC/XLM into escrow via the Stellar network.
- A registered payout agent handles fiat distribution to the recipient off-chain.
- The agent confirms payout on-chain via the backend.
- The platform releases funds to the agent minus a configurable platform fee.
- Platform fees accumulate and are managed by the admin.
The system is transparent, auditable on Stellar Explorer, and modular enough to extend with Soroban smart contracts.
- Escrow-Based Transfers — Secure USDC/XLM deposits held until payout confirmation
- Agent Network — Registered agents handle fiat distribution off-chain
- Automated Fee Collection — Platform fees calculated and accumulated automatically
- Multi-Status Tracking — Remittances tracked through Pending, Completed, and Cancelled states
- Authorization Security — JWT-based role access control for all operations
- QR Payment Generator — Shareable QR codes for wallet addresses
- Contact List — Save frequent recipients for quick transfers
- Fraud Protection — Rate limiting and transaction velocity checks
- Event Logging — All transactions stored in PostgreSQL and linked to Stellar Explorer
- Cancellation Support — Senders can cancel pending remittances
- Multi-Currency UI — Display estimated values in NGN, GHS, KES, USD
| Layer | Technology |
|---|---|
| Frontend | React 18, Tailwind CSS |
| Backend | Node.js, Express.js |
| Blockchain | Stellar SDK, Horizon API |
| Database | PostgreSQL |
| Auth | JWT + bcrypt |
| Network | Stellar Testnet / Mainnet |
├── backend/
│ ├── src/
│ │ ├── controllers/
│ │ │ ├── authController.js # Register, login, JWT issuance
│ │ │ ├── walletController.js # Balance, QR code, transactions
│ │ │ ├── paymentController.js # Send payments, history, fraud check
│ │ │ └── contactsController.js # Frequent contacts CRUD
│ │ ├── middleware/
│ │ │ └── auth.js # JWT verification middleware
│ │ ├── routes/
│ │ │ ├── auth.js
│ │ │ ├── wallet.js
│ │ │ └── payments.js
│ │ ├── services/
│ │ │ └── stellar.js # Wallet generation, signing, broadcasting
│ │ ├── db.js # PostgreSQL connection pool
│ │ └── index.js # Express app entry point
│ ├── .env.example
│ └── package.json
├── frontend/
│ ├── src/
│ │ ├── pages/
│ │ │ ├── Welcome.jsx # Onboarding screen
│ │ │ ├── Register.jsx # Sign up + wallet creation
│ │ │ ├── Login.jsx # Authentication
│ │ │ ├── Dashboard.jsx # Balance, recent activity, currency toggle
│ │ │ ├── SendMoney.jsx # Send with two-step confirmation
│ │ │ ├── ReceiveMoney.jsx # QR code + address sharing
│ │ │ ├── TransactionHistory.jsx # Full history with filters
│ │ │ └── Profile.jsx # User info + contacts management
│ │ ├── components/
│ │ │ └── Layout.jsx # Mobile bottom nav shell
│ │ ├── context/
│ │ │ └── AuthContext.jsx # Global auth state
│ │ └── utils/
│ │ ├── api.js # Axios instance with JWT interceptor
│ │ └── currency.js # XLM conversion rates + formatters
│ ├── .env.example
│ └── package.json
├── contracts/
│ ├── escrow/ # Soroban smart contract (Rust/WASM)
│ │ ├── src/
│ │ │ └── lib.rs # Escrow contract implementation
│ │ ├── Cargo.toml
│ │ └── README.md # Contract ABI & documentation
│ ├── README.md # Contracts directory guide
│ ├── deploy.sh # Deployment script for all contracts
│ └── .gitignore
└── database/
└── schema.sql # PostgreSQL tables + indexes
PostgreSQL Tables:
users— Account credentials and profilewallets— Stellar public key + AES-256 encrypted secret keytransactions— All remittance records with status trackingcontacts— Saved frequent recipients per user
Platform fees are calculated using basis points (bps):
fee = amount * fee_bps / 10000
Examples:
250 bps = 2.5%
500 bps = 5.0%
AfriPay targets Stellar Protocol 19+. Note that the inflation operation was removed in Protocol 12 (2019) and is not used anywhere in this codebase. No setOptions calls set an inflationDest. Any SDK examples referencing inflation are outdated and should be ignored.
For regulatory compliance (fraud investigations, court orders), AfriPay supports the Stellar clawback operation on USDC assets. This allows the asset issuer to reclaim tokens from a user's account when legally required.
- Endpoint:
POST /api/admin/clawback(admin-only) - Requires the issuer account to have
AUTH_CLAWBACK_ENABLED_FLAGset on-chain - All clawback operations are recorded in the audit log with reason, amount, and transaction hash
- Configure
ISSUER_PUBLIC_KEYandISSUER_ENCRYPTED_SECRET_KEYin your.env
On registration, a Stellar keypair is automatically generated:
- Public key stored in the database
- Secret key encrypted with AES-256-CBC before storage
- Account funded via Friendbot on testnet
Sender → [approve USDC transfer] → Backend → [sign with keypair]
→ Stellar Horizon API → [broadcast transaction]
→ Transaction hash stored in DB
→ Visible on Stellar Expert Explorer
XLM— Native Stellar LumensUSDC— USD Coin on Stellar- Display conversion: NGN, GHS, KES, USD
- A Stellar testnet account (auto-created on registration)
psql -U postgres -c "CREATE DATABASE cbpa_db;"Then run migrations (see Database Migrations below).
cd backend
npm install
cp .env.example .env
# Fill in your values (see Environment Variables below)
npm run dev
# Server starts on http://localhost:5000-
Copy
.env.exampleto.envand customize:cp .env.example .env -
Start full stack:
docker compose up -d --build -
Access:
- Frontend: http://localhost:3000
- Backend API: http://localhost
AfriPay uses node-pg-migrate for schema version control. All schema changes must be made as numbered migration files inside database/migrations/ — never by editing schema.sql directly.
cd backend
npm run migratecd backend
npm run migrate:rollbackCreate a new file in database/migrations/ following the naming convention:
002_your_migration_name.js
Each file must export an up and a down function:
exports.up = (pgm) => {
// forward changes
};
exports.down = (pgm) => {
// reverse changes
};node-pg-migrate tracks applied migrations in a pgmigrations table that it creates automatically. Never delete or edit this table manually.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/auth/register | No | Register user + auto-create wallet |
| POST | /api/auth/login | No | Login, receive JWT |
| GET | /api/auth/me | Yes | Get current user profile |
| GET | /api/wallet/balance | Yes | Get wallet address + balances |
| GET | /api/wallet/qr | Yes | Generate QR code for address |
| GET | /api/wallet/contacts | Yes | List saved contacts |
| POST | /api/wallet/contacts | Yes | Add a contact |
| DELETE | /api/wallet/contacts/:id | Yes | Remove a contact |
| POST | /api/payments/send | Yes | Broadcast payment to Stellar |
| GET | /api/payments/history | Yes | Full transaction history |
| POST | /api/wallet/merge | Yes | Merge (close) account into another |
| POST | /api/support/tickets | Yes | Create a support/dispute ticket |
| GET | /api/support/tickets | Yes | List user's support tickets |
| POST | /api/admin/clawback | Admin | Clawback asset for compliance |
| GET | /api/admin/health | Admin | Full service health diagnostics |
| POST | /api/escrow/create | Yes | Create agent escrow (approved agents only) |
| POST | /api/escrow/:id/confirm | Yes | Agent confirms payout |
| POST | /api/escrow/:id/cancel | Yes | Sender cancels escrow |
AfriPay uses a registered agent network for fiat distribution. Only approved agents may be used as the agent_wallet parameter in POST /api/escrow/create.
1. Agent applies → POST /api/auth/register (creates a user account)
2. Agent submits their Stellar wallet address for approval
3. Admin reviews and approves → INSERT INTO agents (wallet_address, status='approved')
4. Agent is now eligible to receive escrow assignments
5. Admin may suspend an agent by setting status='suspended'
| Column | Type | Description |
|---|---|---|
| id | uuid | Primary key |
| user_id | uuid | FK to users table |
| wallet_address | text | Agent's Stellar public key (unique) |
| status | varchar(20) | pending / approved / suspended |
| country | varchar(10) | ISO country code (optional) |
| created_at | timestamptz | Registration timestamp |
| approved_at | timestamptz | Admin approval timestamp |
POST /api/escrow/create checks the agents table before creating any on-chain escrow:
- If
agent_walletis not found withstatus = 'approved', the request is rejected with HTTP 400:{ "error": "Agent is not registered in the AfriPay network" } - Only after a successful agent lookup does the backend proceed to sign and broadcast the Soroban escrow transaction.
PORT=5000
NODE_ENV=development
# PostgreSQL
DATABASE_URL=postgresql://user:password@localhost:5432/cbpa_db
# JWT
JWT_SECRET=your_super_secret_jwt_key_here
JWT_EXPIRES_IN=7d
# Stellar Network
# Use 'testnet' for development, 'mainnet' for production
STELLAR_NETWORK=testnet
STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
# AES-256 encryption key for private key storage (must be exactly 32 characters)
ENCRYPTION_KEY=your_32_character_encryption_key_
# Cache Configuration
# Balance cache TTL in seconds (default: 30)
# Time-To-Live for cached wallet balance data. Lower values ensure fresher data but increase load on Horizon.
# Higher values reduce load but may show stale balances after recent transactions.
# Cache is automatically invalidated after send, sendBatch, and sendPath operations.
BALANCE_CACHE_TTL_SECONDS=30
# CORS
FRONTEND_URL=http://localhost:3000For mainnet: set
STELLAR_NETWORK=mainnetandSTELLAR_HORIZON_URL=https://horizon.stellar.org
- Passwords hashed with bcrypt (cost factor 12)
- Stellar private keys encrypted with AES-256-CBC before DB storage — never stored in plaintext
- JWT authentication required on all protected routes
- Rate limiting: 100 req/15min globally, 10 req/15min on auth endpoints
- Fraud protection: blocks wallets exceeding 5 transactions in 10 minutes
- Input validation on all endpoints via
express-validator - CORS restricted to configured frontend origin
Balance Caching with Redis
Wallet balances are cached in Redis to reduce load on the Stellar Horizon API. The cache behavior is configurable:
- Configurable TTL: Set
BALANCE_CACHE_TTL_SECONDSin your environment (default: 30 seconds) - Automatic Invalidation: Cache is cleared immediately after:
- Standard payments (
POST /api/payments) - Batch payments (
POST /api/payments/batch) - Path payments (
POST /api/payments/send-path)
- Standard payments (
- Fallback Behavior: If Redis is not configured, balances are fetched live from Horizon on every request
- Stale Balance Risk: In production, ensure the TTL is low enough (15-30 seconds recommended) to prevent users from seeing significantly stale balances after receiving payments
To adjust the cache TTL in production, update the BALANCE_CACHE_TTL_SECONDS environment variable without restarting (if using a process manager with hot-reload support).
| Status | Description |
|---|---|
| pending | Remittance created, awaiting agent confirmation |
| completed | Payout confirmed, funds released |
| cancelled | Cancelled by sender, full refund issued |
| Scenario | HTTP Code | Response |
|---|---|---|
| Invalid credentials | 401 | Invalid email or password |
| Expired/missing JWT | 401 | Invalid or expired token |
| Duplicate email | 409 | Email already registered |
| Invalid amount | 400 | Amount must be greater than 0 |
| Transaction velocity exceeded | 429 | Transaction limit reached |
| Stellar broadcast failure | 400 | Transaction failed + extras |
- Register an account — a Stellar testnet wallet is auto-funded via Friendbot
- Copy your wallet address from the Dashboard
- Use Stellar Laboratory to send test XLM to your address
- Send a payment to another testnet address
- View the transaction on Stellar Expert (Testnet)
- Soroban smart contract escrow (Rust/WASM on Stellar) — contracts/escrow/
- Trustless three-party escrow model
- Automated fee calculation and collection
- Full event logging for transparency
- Comprehensive test coverage
- Multi-currency USDC support with on-chain fee deduction
- Agent registration and payout confirmation system
- Batch remittance processing
- Agent reputation system
- Dispute resolution mechanism
- Time-locked escrow options (auto-refund after 30 days)
- Push notifications for transaction events
- Integration with fiat on/off ramps (M-Pesa, Flutterwave, Paystack)
- Mobile app (React Native)
Contributions are welcome. Please ensure:
- Code follows existing patterns and style
- New features include appropriate error handling
- Environment variables are documented in
.env.example - No secrets or private keys are committed
- Stellar Developer Docs
- Stellar SDK (JavaScript)
- Horizon API Reference
- Stellar Expert Explorer
- Stellar Discord
- Soroban Smart Contracts
- Horizon Self-Hosting Guide — run your own Horizon node in production
- Wallet Backup & Recovery
MIT