Skip to main content

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

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

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

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

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

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

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:
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

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

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

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

  1. Handle Reorgs: Use finalized blocks for critical operations
  2. Batch Queries: Query multiple blocks at once for historical data
  3. Error Handling: Implement retry logic for RPC failures
  4. Rate Limiting: Respect RPC endpoint rate limits
  5. Persistence: Store events in a database for historical analysis

Example: Robust Event Handler

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);
}

Next Steps