Skip to main content

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 opportunity
  • traderId - Links reserve to the trader managing the opportunity
  • assetType - Type of asset (broker_account, blockchain_wallet, bank_account)
  • brokerAccountId - For MT5/broker accounts
  • walletAddress - For blockchain wallets
  • blockchain - Blockchain type (BSC, Ethereum, Cardano)
  • reportedBalance - Balance claimed by trader
  • verifiedBalance - Balance verified by system/admin
  • currency - Currency/token symbol
  • screenshotUrl - Evidence screenshot
  • apiResponse - API response from balance query (JSON)
  • verificationMethod - How balance was verified
  • notes - 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:

  1. Display which accounts back each opportunity
  2. Query all accounts for a specific opportunity
  3. Track which trader manages which accounts

Key Fields:

  • opportunityId - The opportunity this account backs
  • traderId - The trader who controls this account
  • assetType - Type of asset
  • brokerAccountId - 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`,
}
});
}

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

  1. Regular Snapshots: Take proof of reserves snapshots at least weekly, ideally daily
  2. Automated Verification: Use API integration where possible to reduce manual work
  3. Screenshot Evidence: Always store screenshots as backup evidence
  4. Multiple Asset Types: Track both MT5 accounts and crypto wallets comprehensively
  5. Transparency: Display latest reserves on opportunity pages for investor confidence
  6. Audit Trail: Keep all historical snapshots, never delete old records
  7. 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_reserves table
  • New opportunity_reserve_accounts linking table
  • Indexes for performance on opportunityId, traderId, and snapshotDate

All existing ProofOfReserve records are preserved and default to assetType = 'broker_account'.