CrowdPay implements a secure, auditable on-chain wallet architecture where each campaign has its own dedicated Stellar account. This document covers the complete wallet lifecycle from creation to recovery.
Each campaign wallet is a Stellar account with:
- Multisig Control: Requires both creator and platform signatures for withdrawals
- USDC Trustline: Pre-configured to accept USDC contributions
- Disabled Master Key: The original keypair is disabled after setup for security
- Threshold Configuration:
- Low: 1 (allows single-signature operations like trustlines)
- Medium: 2 (requires both signatures for payments)
- High: 2 (requires both signatures for account changes)
- Campaign wallet secrets are encrypted using AES-256-GCM
- Encryption key stored in
WALLET_ENCRYPTION_KEYenvironment variable - Each encrypted secret includes: IV, auth tag, and ciphertext
- Encrypted secrets stored in
campaigns.wallet_secret_encryptedcolumn - Public keys stored in
campaigns.wallet_public_keycolumn - Database schema includes indexes for efficient wallet lookups
- Never log or expose private keys
- Rotate
WALLET_ENCRYPTION_KEYperiodically - Use hardware security modules (HSM) or key management services (KMS) in production
- Implement access controls on wallet recovery endpoints
When a user creates a campaign:
- Generate Keypair: New Stellar keypair generated for the campaign
- Fund Account: Platform account funds the new wallet with 2 XLM (base reserve)
- Configure Multisig:
- Add creator's public key as signer (weight: 1)
- Add platform's public key as signer (weight: 1)
- Set thresholds to require both signatures
- Disable master key (weight: 0)
- Establish Trustline: Add USDC trustline to accept contributions
- Encrypt & Store: Encrypt wallet secret and store in database
- Start Monitoring: Begin watching for incoming transactions
API Endpoint: POST /api/campaigns
{
"title": "My Campaign",
"description": "Campaign description",
"target_amount": "1000",
"asset_type": "USDC",
"deadline": "2026-12-31"
}Contributors send funds to the campaign wallet:
- Direct Payments: XLM or USDC sent directly
- Path Payments: Any asset converted to USDC via Stellar DEX
- Ledger Monitoring: Backend monitors Horizon for incoming transactions
- Database Recording: Contributions recorded with full audit trail
Campaign creators can:
- View Balance: Check real-time on-chain balance
- View Transactions: Audit complete transaction history
- View Payments: See detailed payment operations
- Inspect Config: Review multisig configuration
API Endpoints:
GET /api/campaigns/:id/balance- Current balanceGET /api/wallets/:campaignId/transactions- Transaction historyGET /api/wallets/:campaignId/payments- Payment operationsGET /api/wallets/:campaignId/config- Multisig configuration
To withdraw funds:
- Create Request: Creator initiates withdrawal request
- Build Transaction: Backend builds unsigned XDR transaction
- Creator Signs: Creator signs with their private key
- Platform Signs: Platform reviews and signs
- Submit: Transaction submitted to Stellar network
- Record: Withdrawal recorded in database
API Endpoint: POST /api/withdrawals
{
"campaign_id": "uuid",
"amount": "500",
"destination_key": "GXXX..."
}If wallet access is needed:
- Authenticate: Verify creator identity
- Decrypt Secret: Retrieve and decrypt wallet secret
- Recover Keypair: Reconstruct keypair from secret
- Verify: Confirm public key matches stored value
API Endpoint: POST /api/wallets/:campaignId/recover
CREATE TABLE campaigns (
id UUID PRIMARY KEY,
creator_id UUID NOT NULL REFERENCES users(id),
title TEXT NOT NULL,
description TEXT,
target_amount NUMERIC(20, 7) NOT NULL,
raised_amount NUMERIC(20, 7) NOT NULL DEFAULT 0,
asset_type TEXT NOT NULL,
wallet_public_key TEXT UNIQUE NOT NULL,
wallet_secret_encrypted TEXT,
status TEXT NOT NULL DEFAULT 'active',
deadline DATE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX ON campaigns (wallet_public_key);Development:
# Generate a secure 256-bit key
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"Production:
- Use AWS KMS, Google Cloud KMS, or Azure Key Vault
- Implement key rotation policies
- Store keys in secure vaults, never in code or version control
- Wallet recovery endpoints require authentication
- Only campaign creators can access their wallet data
- Platform operators need separate admin authentication
- Implement rate limiting on sensitive endpoints
All wallet operations are logged:
- Campaign creation events
- Contribution transactions
- Withdrawal requests and approvals
- Recovery attempts
- Use HTTPS for all API communications
- Implement CORS policies
- Validate all transaction XDRs before signing
- Monitor for suspicious activity patterns
The backend runs a continuous ledger monitor that:
- Watches for new transactions on campaign wallets
- Records contributions in the database
- Updates campaign raised amounts
- Triggers notifications for large contributions
Monitor these metrics:
- Wallet creation success rate
- Transaction submission failures
- Encryption/decryption errors
- Horizon API availability
- Database Backups: Regular encrypted backups of PostgreSQL
- Key Backups: Secure offline backup of
WALLET_ENCRYPTION_KEY - Configuration Backups: Environment variables and deployment configs
If database is lost:
- Restore from latest backup
- Verify encryption key matches
- Test wallet recovery on sample campaign
- Reconcile on-chain state with database
If encryption key is lost:
- Critical: Encrypted secrets cannot be recovered
- Campaign wallets remain accessible via multisig (creator + platform)
- New campaigns can be created with new encryption key
Test wallet service functions:
npm test src/services/walletService.test.jsTest complete wallet lifecycle:
npm test src/routes/campaigns.test.js
npm test src/routes/wallets.test.js# 1. Setup platform account
node contracts/stellar/campaignWallet.js --setup-platform
# 2. Create test campaign via API
curl -X POST http://localhost:3001/api/campaigns \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Test","target_amount":"100","asset_type":"USDC"}'
# 3. Inspect wallet
node contracts/stellar/campaignWallet.js --inspect GXXX...POST /api/campaigns
Authorization: Bearer <token>
Content-Type: application/json
{
"title": "string",
"description": "string",
"target_amount": "number",
"asset_type": "XLM|USDC",
"deadline": "YYYY-MM-DD"
}
Response: 201 Created
{
"id": "uuid",
"wallet_public_key": "GXXX...",
...
}
GET /api/campaigns/:id/balance
Response: 200 OK
{
"XLM": "1.5000000",
"USDC": "250.0000000"
}
GET /api/wallets/:campaignId/config
Authorization: Bearer <token>
Response: 200 OK
{
"thresholds": {
"low_threshold": 1,
"med_threshold": 2,
"high_threshold": 2
},
"signers": [
{
"key": "GXXX...",
"weight": 1,
"type": "ed25519_public_key"
},
...
]
}
GET /api/wallets/:campaignId/transactions?limit=50
Authorization: Bearer <token>
Response: 200 OK
[
{
"hash": "abc123...",
"created_at": "2026-04-24T09:30:00Z",
"source_account": "GXXX...",
"fee_charged": "100",
"operation_count": 1,
"memo": "crowdpay"
},
...
]
GET /api/wallets/:campaignId/payments?limit=100
Authorization: Bearer <token>
Response: 200 OK
[
{
"id": "123456789",
"type": "payment",
"created_at": "2026-04-24T09:30:00Z",
"transaction_hash": "abc123...",
"from": "GXXX...",
"to": "GYYY...",
"amount": "50.0000000",
"asset_type": "USDC"
},
...
]
POST /api/wallets/:campaignId/recover
Authorization: Bearer <token>
Response: 200 OK
{
"publicKey": "GXXX..."
}
Issue: Campaign creation fails with "Account not found"
- Cause: Platform account not funded or incorrect credentials
- Solution: Run
--setup-platformscript and verify.envconfiguration
Issue: Encryption/decryption errors
- Cause: Missing or incorrect
WALLET_ENCRYPTION_KEY - Solution: Generate new key and update environment variables
Issue: Withdrawal requires both signatures but only one provided
- Cause: Multisig threshold not met
- Solution: Ensure both creator and platform sign the transaction XDR
Issue: Trustline not established
- Cause: Insufficient XLM balance for reserve
- Solution: Increase starting balance in
createCampaignWalletfunction
- Key Rotation: Implement periodic re-encryption with new keys
- Hardware Wallet Support: Allow creators to use hardware wallets for signing
- Multi-Asset Support: Extend beyond USDC to other Stellar assets
- Automated Withdrawals: Smart contract-based automatic disbursements
- Threshold Customization: Allow campaigns to set custom signature requirements