Proof of Reserves System
Overview
The proof of reserves system allows the platform to track and verify that traders have the assets they claim to have under management. This builds trust with investors by providing transparency into the actual holdings backing each trading opportunity.
Schema Architecture
New Enum: ReserveAssetType
enum ReserveAssetType {
broker_account // MT5, MT4, or other brokerage accounts
blockchain_wallet // Cryptocurrency wallets
bank_account // Traditional bank accounts
}
Updated Model: ProofOfReserve
Enhanced to track reserves across multiple asset types and link them to opportunities and traders.
Key Fields:
opportunityId- Links reserve to a specific opportunitytraderId- Links reserve to the trader managing the opportunityassetType- Type of asset (broker_account, blockchain_wallet, bank_account)brokerAccountId- For MT5/broker accountswalletAddress- For blockchain walletsblockchain- Blockchain type (BSC, Ethereum, Cardano)reportedBalance- Balance claimed by traderverifiedBalance- Balance verified by system/admincurrency- Currency/token symbolscreenshotUrl- Evidence screenshotapiResponse- API response from balance query (JSON)verificationMethod- How balance was verifiednotes- Additional notes
New Model: OpportunityReserveAccount
Links opportunities to their associated reserve accounts (both broker accounts and wallets).
Purpose: Associates specific accounts/wallets with opportunities so you can:
- Display which accounts back each opportunity
- Query all accounts for a specific opportunity
- Track which trader manages which accounts
Key Fields:
opportunityId- The opportunity this account backstraderId- The trader who controls this accountassetType- Type of assetbrokerAccountId- Link to BrokerAccount (for MT5/broker accounts)walletAddress- Wallet address (for blockchain wallets)blockchain- Blockchain type (for wallets)label- Human-readable label (e.g., "Main Trading Account", "Reserve Wallet")isActive- Whether this account is currently active
Usage Examples
1. Register MT5 Account for an Opportunity
// First, create or link a broker account
const brokerAccount = await prisma.brokerAccount.create({
data: {
brokerName: "MT5",
accountIdentifier: "12345678",
currency: "USD",
currentBalance: 50000.00,
}
});
// Link it to an opportunity
const reserveAccount = await prisma.opportunityReserveAccount.create({
data: {
opportunityId: 1,
traderId: 2,
assetType: "broker_account",
brokerAccountId: brokerAccount.id,
label: "Main MT5 Trading Account",
isActive: true,
}
});
2. Register Blockchain Wallet for an Opportunity
const reserveAccount = await prisma.opportunityReserveAccount.create({
data: {
opportunityId: 1,
traderId: 2,
assetType: "blockchain_wallet",
walletAddress: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
blockchain: "Ethereum",
label: "USDT Reserve Wallet",
isActive: true,
}
});
3. Record Proof of Reserves Snapshot
// For MT5 account
const proof = await prisma.proofOfReserve.create({
data: {
snapshotDate: new Date(),
opportunityId: 1,
traderId: 2,
assetType: "broker_account",
brokerAccountId: brokerAccount.id,
reportedBalance: 50000.00,
verifiedBalance: 50000.00,
currency: "USD",
screenshotUrl: "/uploads/mt5-screenshot-2025-10-15.png",
verifiedById: adminUserId,
verificationMethod: "Manual screenshot review",
notes: "Balance verified via MT5 mobile app screenshot",
}
});
// For blockchain wallet
const proofWallet = await prisma.proofOfReserve.create({
data: {
snapshotDate: new Date(),
opportunityId: 1,
traderId: 2,
assetType: "blockchain_wallet",
walletAddress: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
blockchain: "Ethereum",
reportedBalance: 25000.00,
verifiedBalance: 25000.00,
currency: "USDT",
apiResponse: {
blockNumber: 12345678,
balance: "25000000000", // in wei
timestamp: new Date().toISOString()
},
verifiedById: systemUserId,
verificationMethod: "Etherscan API",
}
});
4. Query All Reserve Accounts for an Opportunity
const reserveAccounts = await prisma.opportunityReserveAccount.findMany({
where: {
opportunityId: 1,
isActive: true,
},
include: {
brokerAccount: true,
trader: {
select: {
id: true,
fullName: true,
email: true,
}
},
opportunity: {
select: {
id: true,
name: true,
}
}
}
});
// Calculate total reserves
const totalReserves = reserveAccounts.reduce((sum, account) => {
if (account.brokerAccount) {
return sum + Number(account.brokerAccount.currentBalance);
}
return sum;
}, 0);
5. Get Latest Proof of Reserves for an Opportunity
const latestProofs = await prisma.proofOfReserve.findMany({
where: {
opportunityId: 1,
},
orderBy: {
snapshotDate: 'desc',
},
take: 10, // Last 10 snapshots
include: {
brokerAccount: true,
verifiedBy: {
select: {
fullName: true,
}
}
}
});
// Group by asset type
const proofsByType = {
broker_accounts: latestProofs.filter(p => p.assetType === 'broker_account'),
blockchain_wallets: latestProofs.filter(p => p.assetType === 'blockchain_wallet'),
bank_accounts: latestProofs.filter(p => p.assetType === 'bank_account'),
};
6. Display Reserves on Opportunity Page
async function getOpportunityReserves(opportunityId: number) {
// Get all reserve accounts
const accounts = await prisma.opportunityReserveAccount.findMany({
where: {
opportunityId,
isActive: true,
},
include: {
brokerAccount: true,
}
});
// Get latest proof for each account
const proofsData = await Promise.all(
accounts.map(async (account) => {
const latestProof = await prisma.proofOfReserve.findFirst({
where: {
opportunityId,
assetType: account.assetType,
...(account.brokerAccountId && { brokerAccountId: account.brokerAccountId }),
...(account.walletAddress && { walletAddress: account.walletAddress }),
},
orderBy: {
snapshotDate: 'desc',
},
include: {
verifiedBy: {
select: { fullName: true }
}
}
});
return {
account,
latestProof,
};
})
);
return proofsData;
}
Automated Balance Verification
For MT5 Accounts
If you have MT5 API credentials, you can automate balance queries:
import { MT5Client } from 'your-mt5-library';
async function verifyMT5Balance(brokerAccountId: number) {
const account = await prisma.brokerAccount.findUnique({
where: { id: brokerAccountId }
});
// Decrypt API key and query MT5
const mt5Client = new MT5Client(account.apiKeyEncrypted);
const balance = await mt5Client.getBalance();
// Create proof record
await prisma.proofOfReserve.create({
data: {
snapshotDate: new Date(),
brokerAccountId: account.id,
assetType: "broker_account",
reportedBalance: balance.balance,
verifiedBalance: balance.balance,
currency: account.currency,
apiResponse: balance,
verificationMethod: "MT5 API",
}
});
// Update broker account current balance
await prisma.brokerAccount.update({
where: { id: brokerAccountId },
data: {
currentBalance: balance.balance,
lastSyncedAt: new Date(),
}
});
}
For Blockchain Wallets
Use the existing blockchain integration:
import { getWalletBalance } from '@/lib/blockchain';
async function verifyWalletBalance(
opportunityId: number,
traderId: number,
walletAddress: string,
blockchain: 'BSC' | 'Ethereum' | 'Cardano'
) {
// Query blockchain
const balance = await getWalletBalance(walletAddress, blockchain);
// Create proof record
await prisma.proofOfReserve.create({
data: {
snapshotDate: new Date(),
opportunityId,
traderId,
assetType: "blockchain_wallet",
walletAddress,
blockchain,
reportedBalance: balance.usdValue,
verifiedBalance: balance.usdValue,
currency: balance.currency,
apiResponse: balance,
verificationMethod: `${blockchain} Blockchain Explorer API`,
}
});
}
Admin API Endpoints (Recommended)
POST /api/admin/opportunities/[id]/reserve-accounts
Register a new reserve account for an opportunity.
GET /api/admin/opportunities/[id]/reserves
Get all reserve accounts and latest proofs for an opportunity.
POST /api/admin/reserve-accounts/[id]/verify
Trigger automated verification for a reserve account.
POST /api/admin/proof-of-reserves
Manually record a proof of reserves snapshot.
GET /api/opportunities/[id]/proof-of-reserves (Public)
Public endpoint showing latest verified reserves for transparency.
Best Practices
- Regular Snapshots: Take proof of reserves snapshots at least weekly, ideally daily
- Automated Verification: Use API integration where possible to reduce manual work
- Screenshot Evidence: Always store screenshots as backup evidence
- Multiple Asset Types: Track both MT5 accounts and crypto wallets comprehensively
- Transparency: Display latest reserves on opportunity pages for investor confidence
- Audit Trail: Keep all historical snapshots, never delete old records
- Verification: Require admin verification for all proofs before displaying publicly
Security Considerations
- API Keys: Store MT5 API keys encrypted in
broker_accounts.api_key_encrypted - Sensitive Data: Proof of reserves data should only be visible to admins and opportunity participants
- Rate Limiting: Implement rate limiting on public proof endpoints
- Screenshot Storage: Store screenshots in secure location with access controls
Migration Notes
The migration 20251015163819_add_proof_of_reserves_enhancements adds:
- New enum
ReserveAssetType - New fields to
proof_of_reservestable - New
opportunity_reserve_accountslinking table - Indexes for performance on
opportunityId,traderId, andsnapshotDate
All existing ProofOfReserve records are preserved and default to assetType = 'broker_account'.