Monitoring Entity Activity
Track donations and transfers in real-time using entity events.Overview
Entity contracts emit events for all major operations:- EntityDonationReceived: When donations are processed
- EntityValueTransferred: When grants are made
- EntityValuePaidOut: When payouts occur (Orgs only)
- EntityManagerSet: When the manager changes
- EntityBalanceReconciled: When balance reconciliation occurs
Event Schemas
EntityDonationReceived
Copy
Ask AI
event EntityDonationReceived(
address indexed from, // Donor address
address indexed to, // Entity receiving donation
address indexed tokenIn, // Token donated (USDC or other)
uint256 amountIn, // Amount donated
uint256 amountReceived, // Amount entity received (after fees)
uint256 amountFee // Fee amount sent to treasury
);
EntityValueTransferred
Copy
Ask AI
event EntityValueTransferred(
address indexed from, // Source entity (Fund)
address indexed to, // Destination entity (Org or Fund)
uint256 amountReceived, // Amount received by destination
uint256 amountFee // Transfer fee
);
Listening for Events
Monitor Donations
Copy
Ask AI
import { ethers } from 'ethers';
const ENTITY_ABI = [
'event EntityDonationReceived(address indexed from, address indexed to, address indexed tokenIn, uint256 amountIn, uint256 amountReceived, uint256 amountFee)'
];
async function watchDonations(entityAddress: string) {
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const entity = new ethers.Contract(entityAddress, ENTITY_ABI, provider);
console.log(`👀 Watching donations to ${entityAddress}...\n`);
entity.on('EntityDonationReceived', (from, to, tokenIn, amountIn, amountReceived, amountFee, event) => {
console.log(`\n💰 Donation Received:`);
console.log(` From: ${from}`);
console.log(` Token: ${tokenIn}`);
console.log(` Amount In: ${ethers.utils.formatUnits(amountIn, 6)} USDC`);
console.log(` Received: ${ethers.utils.formatUnits(amountReceived, 6)} USDC`);
console.log(` Fee: ${ethers.utils.formatUnits(amountFee, 6)} USDC`);
console.log(` Block: ${event.blockNumber}`);
console.log(` Tx: ${event.transactionHash}`);
});
}
// Start monitoring
await watchDonations('0x...');
Monitor Grants
Copy
Ask AI
async function watchGrants(fundAddress: string) {
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const fund = new ethers.Contract(fundAddress, ENTITY_ABI, provider);
console.log(`👀 Watching grants from ${fundAddress}...\n`);
fund.on('EntityValueTransferred', (from, to, amountReceived, amountFee, event) => {
console.log(`\n📤 Grant Made:`);
console.log(` From Fund: ${from}`);
console.log(` To Entity: ${to}`);
console.log(` Amount: ${ethers.utils.formatUnits(amountReceived, 6)} USDC`);
console.log(` Fee: ${ethers.utils.formatUnits(amountFee, 6)} USDC`);
console.log(` Block: ${event.blockNumber}`);
console.log(` Tx: ${event.transactionHash}`);
});
}
Historical Event Queries
Query Past Donations
Copy
Ask AI
async function getPastDonations(
entityAddress: string,
fromBlock: number,
toBlock: number
) {
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const entity = new ethers.Contract(entityAddress, ENTITY_ABI, provider);
console.log(`Fetching donations from block ${fromBlock} to ${toBlock}...`);
const filter = entity.filters.EntityDonationReceived(null, entityAddress, null);
const events = await entity.queryFilter(filter, fromBlock, toBlock);
console.log(`\n📊 Found ${events.length} donations:\n`);
let totalReceived = ethers.BigNumber.from(0);
let totalFees = ethers.BigNumber.from(0);
for (const event of events) {
const { from, amountIn, amountReceived, amountFee } = event.args;
const block = await provider.getBlock(event.blockNumber);
const date = new Date(block.timestamp * 1000);
console.log(`${date.toLocaleDateString()}:`);
console.log(` From: ${from}`);
console.log(` Amount: ${ethers.utils.formatUnits(amountReceived, 6)} USDC`);
console.log(` Fee: ${ethers.utils.formatUnits(amountFee, 6)} USDC`);
console.log(` Tx: ${event.transactionHash}\n`);
totalReceived = totalReceived.add(amountReceived);
totalFees = totalFees.add(amountFee);
}
console.log(`Summary:`);
console.log(` Total Received: ${ethers.utils.formatUnits(totalReceived, 6)} USDC`);
console.log(` Total Fees: ${ethers.utils.formatUnits(totalFees, 6)} USDC`);
return events;
}
// Usage: Get last 1000 blocks of donations
const currentBlock = await provider.getBlockNumber();
await getPastDonations('0x...', currentBlock - 1000, currentBlock);
Query Grant History
Copy
Ask AI
interface GrantEvent {
from: string;
to: string;
amount: string;
fee: string;
timestamp: number;
txHash: string;
blockNumber: number;
}
async function getGrantHistory(
fundAddress: string,
startBlock: number,
endBlock: number
): Promise<GrantEvent[]> {
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const fund = new ethers.Contract(fundAddress, ENTITY_ABI, provider);
const filter = fund.filters.EntityValueTransferred(fundAddress, null);
const events = await fund.queryFilter(filter, startBlock, endBlock);
const grants: GrantEvent[] = [];
for (const event of events) {
const { from, to, amountReceived, amountFee } = event.args;
const block = await provider.getBlock(event.blockNumber);
grants.push({
from,
to,
amount: ethers.utils.formatUnits(amountReceived, 6),
fee: ethers.utils.formatUnits(amountFee, 6),
timestamp: block.timestamp,
txHash: event.transactionHash,
blockNumber: event.blockNumber
});
}
return grants;
}
Real-Time Monitoring Dashboard
Complete Entity Monitor
Build a comprehensive monitoring solution:Copy
Ask AI
class EntityMonitor {
private entity: ethers.Contract;
private provider: ethers.providers.Provider;
private listeners: Map<string, ethers.providers.Listener> = new Map();
constructor(entityAddress: string, provider: ethers.providers.Provider) {
this.provider = provider;
this.entity = new ethers.Contract(entityAddress, ENTITY_ABI, provider);
}
async startMonitoring() {
console.log(`🚀 Starting entity monitor for ${this.entity.address}\n`);
// Get initial state
const balance = await this.entity.balance();
const manager = await this.entity.manager();
const entityType = await this.entity.entityType();
console.log(`📊 Initial State:`);
console.log(` Type: ${entityType === 0 ? 'Org' : 'Fund'}`);
console.log(` Balance: ${ethers.utils.formatUnits(balance, 6)} USDC`);
console.log(` Manager: ${manager}\n`);
// Monitor donations
const donationListener = (from, to, tokenIn, amountIn, amountReceived, amountFee, event) => {
console.log(`\n[${new Date().toLocaleTimeString()}] 💰 DONATION`);
console.log(` +${ethers.utils.formatUnits(amountReceived, 6)} USDC from ${from}`);
};
this.entity.on('EntityDonationReceived', donationListener);
this.listeners.set('donation', donationListener);
// Monitor transfers
const transferListener = (from, to, amountReceived, amountFee, event) => {
console.log(`\n[${new Date().toLocaleTimeString()}] 📤 GRANT`);
console.log(` -${ethers.utils.formatUnits(amountReceived, 6)} USDC to ${to}`);
};
this.entity.on('EntityValueTransferred', transferListener);
this.listeners.set('transfer', transferListener);
// Monitor manager changes
const managerListener = (oldManager, newManager, event) => {
console.log(`\n[${new Date().toLocaleTimeString()}] 🔐 MANAGER CHANGED`);
console.log(` From: ${oldManager}`);
console.log(` To: ${newManager}`);
};
this.entity.on('EntityManagerSet', managerListener);
this.listeners.set('manager', managerListener);
console.log(`✅ Monitoring active. Press Ctrl+C to stop.\n`);
}
stopMonitoring() {
console.log(`\n🛑 Stopping monitor...`);
this.entity.removeAllListeners();
this.listeners.clear();
}
async getCurrentState() {
const balance = await this.entity.balance();
const manager = await this.entity.manager();
return {
balance: ethers.utils.formatUnits(balance, 6),
manager
};
}
}
// Usage
const monitor = new EntityMonitor('0x...', provider);
await monitor.startMonitoring();
// Stop monitoring after 1 hour
setTimeout(() => monitor.stopMonitoring(), 3600000);
Webhook Integration
Send Webhooks on Events
Copy
Ask AI
import axios from 'axios';
interface WebhookPayload {
eventType: string;
entityAddress: string;
data: any;
timestamp: number;
txHash: string;
}
async function setupWebhooks(entityAddress: string, webhookUrl: string) {
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const entity = new ethers.Contract(entityAddress, ENTITY_ABI, provider);
const sendWebhook = async (payload: WebhookPayload) => {
try {
await axios.post(webhookUrl, payload, {
headers: { 'Content-Type': 'application/json' }
});
console.log(`✓ Webhook sent: ${payload.eventType}`);
} catch (error) {
console.error(`✗ Webhook failed: ${error.message}`);
}
};
// Donation webhook
entity.on('EntityDonationReceived', async (from, to, tokenIn, amountIn, amountReceived, amountFee, event) => {
await sendWebhook({
eventType: 'donation',
entityAddress,
data: {
from,
to,
tokenIn,
amountIn: ethers.utils.formatUnits(amountIn, 6),
amountReceived: ethers.utils.formatUnits(amountReceived, 6),
amountFee: ethers.utils.formatUnits(amountFee, 6)
},
timestamp: Date.now(),
txHash: event.transactionHash
});
});
// Grant webhook
entity.on('EntityValueTransferred', async (from, to, amountReceived, amountFee, event) => {
await sendWebhook({
eventType: 'grant',
entityAddress,
data: {
from,
to,
amountReceived: ethers.utils.formatUnits(amountReceived, 6),
amountFee: ethers.utils.formatUnits(amountFee, 6)
},
timestamp: Date.now(),
txHash: event.transactionHash
});
});
console.log(`📡 Webhooks configured for ${entityAddress}`);
}
Alerting System
Set Up Alerts
Copy
Ask AI
interface AlertConfig {
minDonation?: number; // Alert on donations above this amount
maxBalance?: number; // Alert when balance exceeds this
managerChange?: boolean; // Alert on manager changes
}
class EntityAlerter {
private entity: ethers.Contract;
private config: AlertConfig;
constructor(entityAddress: string, provider: ethers.providers.Provider, config: AlertConfig) {
this.entity = new ethers.Contract(entityAddress, ENTITY_ABI, provider);
this.config = config;
}
private async sendAlert(message: string, severity: 'info' | 'warning' | 'critical') {
console.log(`\n🚨 [${severity.toUpperCase()}] ${message}`);
// Integrate with your alerting system (email, Slack, PagerDuty, etc.)
// Example: await sendSlackMessage(message);
}
async startAlerting() {
// Large donation alert
if (this.config.minDonation) {
this.entity.on('EntityDonationReceived', async (from, to, tokenIn, amountIn, amountReceived, amountFee) => {
const amount = parseFloat(ethers.utils.formatUnits(amountReceived, 6));
if (amount >= this.config.minDonation!) {
await this.sendAlert(
`Large donation received: ${amount.toFixed(2)} USDC from ${from}`,
'info'
);
}
});
}
// Balance threshold alert
if (this.config.maxBalance) {
this.entity.on('EntityDonationReceived', async () => {
const balance = await this.entity.balance();
const balanceNum = parseFloat(ethers.utils.formatUnits(balance, 6));
if (balanceNum >= this.config.maxBalance!) {
await this.sendAlert(
`Entity balance (${balanceNum.toFixed(2)} USDC) exceeds threshold (${this.config.maxBalance} USDC)`,
'warning'
);
}
});
}
// Manager change alert
if (this.config.managerChange) {
this.entity.on('EntityManagerSet', async (oldManager, newManager) => {
await this.sendAlert(
`Manager changed from ${oldManager} to ${newManager}`,
'critical'
);
});
}
console.log('🔔 Alerting system active');
}
}
// Usage
const alerter = new EntityAlerter('0x...', provider, {
minDonation: 10000, // Alert on donations > $10k
maxBalance: 1000000, // Alert when balance > $1M
managerChange: true // Alert on any manager change
});
await alerter.startAlerting();
Python Event Monitoring
Copy
Ask AI
from web3 import Web3
import time
from datetime import datetime
RPC_URL = 'https://eth-mainnet.g.alchemy.com/v2/YOUR-API-KEY'
w3 = Web3(Web3.HTTPProvider(RPC_URL))
ENTITY_ABI = [{
"anonymous": False,
"inputs": [
{"indexed": True, "name": "from", "type": "address"},
{"indexed": True, "name": "to", "type": "address"},
{"indexed": True, "name": "tokenIn", "type": "address"},
{"name": "amountIn", "type": "uint256"},
{"name": "amountReceived", "type": "uint256"},
{"name": "amountFee", "type": "uint256"}
],
"name": "EntityDonationReceived",
"type": "event"
}]
def watch_donations(entity_address: str):
entity = w3.eth.contract(address=entity_address, abi=ENTITY_ABI)
# Create event filter
event_filter = entity.events.EntityDonationReceived.create_filter(fromBlock='latest')
print(f"👀 Watching donations to {entity_address}...\n")
while True:
for event in event_filter.get_new_entries():
args = event['args']
print(f"\n💰 Donation Received at {datetime.now().strftime('%H:%M:%S')}:")
print(f" From: {args['from']}")
print(f" Amount: {args['amountReceived'] / 10**6:.2f} USDC")
print(f" Fee: {args['amountFee'] / 10**6:.2f} USDC")
print(f" Tx: {event['transactionHash'].hex()}")
time.sleep(2) # Poll every 2 seconds
if __name__ == '__main__':
watch_donations('0x...')
Best Practices
Event Monitoring Tips
- Handle Reorgs: Use finalized blocks for critical operations
- Batch Queries: Query multiple blocks at once for historical data
- Error Handling: Implement retry logic for RPC failures
- Rate Limiting: Respect RPC endpoint rate limits
- Persistence: Store events in a database for historical analysis
Example: Robust Event Handler
Copy
Ask AI
async function robustEventMonitoring(entityAddress: string) {
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const entity = new ethers.Contract(entityAddress, ENTITY_ABI, provider);
let lastProcessedBlock = await provider.getBlockNumber();
const processEvents = async () => {
try {
const currentBlock = await provider.getBlockNumber();
const safeBlock = currentBlock - 12; // Wait for 12 confirmations
if (safeBlock <= lastProcessedBlock) {
return; // No new blocks to process
}
const filter = entity.filters.EntityDonationReceived();
const events = await entity.queryFilter(filter, lastProcessedBlock + 1, safeBlock);
for (const event of events) {
// Process event
console.log(`Processing event from block ${event.blockNumber}`);
// Store in database, send notifications, etc.
}
lastProcessedBlock = safeBlock;
} catch (error) {
console.error(`Error processing events: ${error.message}`);
// Don't update lastProcessedBlock on error - will retry
}
};
// Run every 30 seconds
setInterval(processEvents, 30000);
}