Payment infrastructure for African crypto onramp/offramp. Built with Rust for speed, reliability, and safety.
Aframp connects African payment systems (M-Pesa, MTN Money, Airtel Money) with blockchain networks using African stablecoins. Users connect their wallet, buy/sell crypto with CNGN stablecoins, and pay bills—all without creating an account.
Core features:
- Non-custodial crypto transactions
- African stablecoin (CNGN) integration
- Multi-chain support (Stellar, Ethereum, Bitcoin)
- Real-time payment processing
- Bill payment services
CNGN is a blockchain-based stable currency pegged to African currencies, designed specifically for the African market. Learn more at afristablecoin.org. cngnstablecoin.org
Key benefits:
- Price stability pegged to local currencies
- Fast, low-cost transactions
- Built for African financial infrastructure
- Reduced volatility for everyday transactions
- Framework: Axum
- Database: PostgreSQL + SQLx
- Cache: Redis
- Async Runtime: Tokio
- Blockchain: Stellar SDK (CNGN primary chain), web3, bitcoin crates
- Jobs: Tokio tasks + Redis queues
src/
├── api/ # HTTP handlers and routes
│ ├── wallet.rs # Wallet connection endpoints
│ ├── onramp.rs # Buy CNGN/crypto endpoints
│ ├── offramp.rs # Sell CNGN/crypto endpoints
│ └── bills.rs # Bill payment endpoints
├── services/ # Business logic
│ ├── transaction.rs
│ ├── payment.rs
│ ├── blockchain.rs
│ └── bill.rs
├── models/ # Database models
├── chains/ # Blockchain integrations
│ ├── stellar/ # CNGN stablecoin & Stellar
│ ├── ethereum/
│ └── bitcoin/
├── payments/ # Payment provider adapters
│ ├── flutterwave.rs
│ ├── paystack.rs
│ └── mpesa.rs
├── workers/ # Background jobs
├── middleware/ # Auth, logging, rate limiting
├── config.rs # Configuration
└── main.rs
- Rust 1.75+
- PostgreSQL 14+
- Redis 6+
- Stellar Horizon access (testnet or mainnet)
Clone and build:
git clone https://github.com/yourusername/aframp-backend.git
cd aframp-backend
cargo buildCopy example config:
cp .env.example .envRequired variables:
# Server
HOST=0.0.0.0
PORT=8000
RUST_LOG=info
# Database
DATABASE_URL=postgresql://user:password@localhost/aframp
DATABASE_MAX_CONNECTIONS=20
# Redis
REDIS_URL=redis://localhost:6379
# Blockchain Networks
STELLAR_NETWORK=testnet
STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
# cNGN Stablecoin Configuration
CNGN_ASSET_CODE=cNGN
CNGN_ISSUER_ADDRESS=GXXX... # cNGN issuer account on Stellar
CNGN_SUPPORTED_CURRENCIES=NGN,KES,ZAR,GHS # African currencies
# Other Chains (optional)
ETHEREUM_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/your-key
BITCOIN_RPC_URL=https://blockstream.info/testnet/api
# Payment Providers
FLUTTERWAVE_SECRET_KEY=your_key_here
FLUTTERWAVE_PUBLIC_KEY=your_key_here
PAYSTACK_SECRET_KEY=your_key_here
MPESA_CONSUMER_KEY=your_key_here
MPESA_CONSUMER_SECRET=your_secret_here
# Security
JWT_SECRET=your_random_secret_min_32_chars
RATE_LIMIT_PER_MINUTE=100
# Optional: Notifications
SENDGRID_API_KEY=your_key
TWILIO_ACCOUNT_SID=your_sid
TWILIO_AUTH_TOKEN=your_tokenRun migrations:
sqlx migrate runCheck migration status:
sqlx migrate infoRollback last migration:
sqlx migrate revertDevelopment mode with hot reload:
cargo watch -x runStandard run:
cargo runProduction build:
cargo build --release
./target/release/aframp-backendAPI starts on http://localhost:8000
Run all tests:
cargo testRun specific test:
cargo test test_onramp_flowRun with output:
cargo test -- --nocaptureIntegration tests (needs testnet):
cargo test --features integration# Get wallet balance (includes CNGN balance)
GET /api/wallet/balance?address=GXXX...
# Get supported chains
GET /api/wallet/chains# Get quote for buying CNGN
POST /api/onramp/quote
{
"from_currency": "KES",
"to_asset": "CNGN",
"amount": "5000"
}
# Initiate CNGN purchase
POST /api/onramp/initiate
{
"wallet_address": "GXXX...",
"from_currency": "KES",
"to_asset": "CNGN",
"amount": "5000",
"payment_method": "mpesa"
}
# Check transaction status
GET /api/onramp/status/:tx_id# Get quote for selling CNGN
POST /api/offramp/quote
{
"from_asset": "CNGN",
"to_currency": "KES",
"amount": "100"
}
# Initiate withdrawal
POST /api/offramp/initiate
{
"wallet_address": "GXXX...",
"from_asset": "CNGN",
"to_currency": "KES",
"amount": "100",
"withdrawal_method": "mpesa",
"phone_number": "+254712345678"
}# Get bill providers
GET /api/bills/providers?country=KE
# Pay bill with CNGN
POST /api/bills/pay
{
"wallet_address": "GXXX...",
"provider": "kplc",
"account_number": "123456789",
"amount": "50",
"asset": "CNGN"
}# Get CNGN exchange rates
GET /api/rates?from=KES&to=CNGN
# Get fee structure
GET /api/feesFull API docs available at /api/docs when server is running.
Workers run as Tokio tasks:
Transaction Monitor - Watches Stellar blockchain for CNGN confirmations
Payment Processor - Polls payment provider APIs
Webhook Handler - Processes incoming webhooks
Settlement Worker - Handles fund settlements
Workers start automatically with the main server.
Each provider implements the PaymentProvider trait:
#[async_trait]
pub trait PaymentProvider {
async fn initiate_payment(&self, request: PaymentRequest) -> Result<PaymentResponse>;
async fn verify_payment(&self, reference: &str) -> Result<PaymentStatus>;
async fn process_withdrawal(&self, request: WithdrawalRequest) -> Result<WithdrawalResponse>;
}Supported providers:
- Flutterwave: Multi-country support
- Paystack: Nigeria, Ghana, South Africa, Kenya
- M-Pesa: Kenya direct integration
Add new providers in src/payments/providers/.
Primary chain for CNGN stablecoin transactions:
// Send CNGN payment
let payment = stellar_service.send_payment(
&recipient_address,
"CNGN",
"100"
).await?;
// Establish trustline for CNGN (first-time users)
let trustline = stellar_service.create_trustline(
&user_address,
"CNGN",
&cngn_issuer_address
).await?;For ERC-20 tokens and future CNGN ERC-20 bridge:
let tx = ethereum_service.transfer_token(
&token_address,
&recipient,
amount
).await?;Lightning Network support planned.
Before users can receive CNGN, they need to establish a trustline:
// Check if trustline exists
let has_trustline = stellar_service
.check_trustline(&wallet_address, "CNGN", &cngn_issuer)
.await?;
// Create trustline if needed
if !has_trustline {
stellar_service
.create_trustline(&wallet_address, "CNGN", &cngn_issuer)
.await?;
}CNGN maintains peg to local currencies:
// Convert NGN to CNGN
let cngn_amount = conversion_service
.convert("NGN", "CNGN", "50000")
.await?;
// Convert CNGN to KES
let kes_amount = conversion_service
.convert("CNGN", "KES", "100")
.await?;Redis-backed limits:
- Onramp: 10 requests/minute per wallet
- Offramp: 10 requests/minute per wallet
- Quotes: 30 requests/minute per IP
- General API: 100 requests/minute per IP
Configure in src/middleware/rate_limit.rs.
Providers send webhooks to /webhooks/:provider:
POST /webhooks/flutterwave
POST /webhooks/paystack
POST /webhooks/mpesa
All webhooks verify signatures before processing.
Wallet Management
- Non-custodial design
- Users control private keys
- Server never stores private keys
CNGN Security
- Trustline verification before transactions
- Asset issuer validation
- Transaction signing verification
API Security
- Rate limiting on all endpoints
- Request validation with strong typing
- SQL injection protection via SQLx
- CORS configured for frontend only
Data Safety
- Monetary values stored as strings (no float precision issues)
- Database transactions for atomic operations
- Idempotency keys for payment operations
Logs use tracing:
RUST_LOG=debug cargo run # Debug level
RUST_LOG=info cargo run # Info level (default)
RUST_LOG=warn cargo run # Warnings onlyKey metrics tracked:
- CNGN transaction success/failure rates
- Payment provider response times
- Stellar blockchain confirmation times
- API endpoint latency
- Trustline creation rates
Production: Logs ship to CloudWatch (or your monitoring stack).
Database connection fails
→ Check PostgreSQL is running and credentials are correct
CNGN transaction fails
→ Verify trustline exists and account has XLM for fees
Trustline creation fails
→ Ensure wallet has minimum XLM balance (1.5 XLM) for trustline
Payment webhook not received
→ Check provider IP whitelist and webhook URL configuration
Redis connection timeout
→ Ensure Redis is running: redis-cli ping
SQLx compile errors
→ Run cargo sqlx prepare to generate query metadata
CNGN issuer not found
→ Verify CNGN_ISSUER_ADDRESS is correctly set in .env
Recommended:
# Watch for changes and rebuild
cargo install cargo-watch
cargo watch -x run
# Check for common mistakes
cargo clippy
# Format code
cargo fmt
# Security audit
cargo audit
# Generate docs
cargo doc --openDocker:
docker build -t aframp-backend .
docker run -p 8000:8000 --env-file .env aframp-backendProduction:
- Use release builds:
cargo build --release - Set
RUST_LOG=infoorwarn - Configure connection pools appropriately
- Enable HTTPS (use reverse proxy like nginx)
- Set up health checks:
GET /health
See our contributing guide for detailed instructions on setting up your development environment, coding standards, testing guidelines, and pull request process.
Please read our Code of Conduct to understand the standards we expect from contributors.
MIT - see LICENSE file