Skip to main content

Reading Entity State

Query entity balances, positions, and metadata onchain.

Overview

Every entity (Org or Fund) maintains:
  • Balance: USDC holdings in the entity
  • Manager: Address with permission to manage the entity
  • Entity Type: Org (0) or Fund (1)
  • Active Status: Whether the entity can receive/send funds

Basic Entity Information

Check Entity Balance

import { ethers } from 'ethers';

const ENTITY_ABI = [
  'function balance() external view returns (uint256)',
  'function manager() external view returns (address)',
  'function entityType() external pure returns (uint8)',
  'function baseToken() external view returns (address)'
];

async function getEntityInfo(entityAddress: string) {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const entity = new ethers.Contract(entityAddress, ENTITY_ABI, provider);
  
  const balance = await entity.balance();
  const manager = await entity.manager();
  const entityType = await entity.entityType();
  const baseToken = await entity.baseToken();
  
  console.log(`Balance: ${ethers.utils.formatUnits(balance, 6)} USDC`);
  console.log(`Manager: ${manager}`);
  console.log(`Entity Type: ${entityType === 0 ? 'Org' : 'Fund'}`);
  console.log(`Base Token: ${baseToken}`);
  
  return { balance, manager, entityType, baseToken };
}

Python/web3.py

from web3 import Web3
from typing import Dict

RPC_URL = 'https://eth-mainnet.g.alchemy.com/v2/YOUR-API-KEY'
w3 = Web3(Web3.HTTPProvider(RPC_URL))

ENTITY_ABI = [
    {"inputs": [], "name": "balance", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"},
    {"inputs": [], "name": "manager", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"},
    {"inputs": [], "name": "entityType", "outputs": [{"name": "", "type": "uint8"}], "stateMutability": "pure", "type": "function"}
]

def get_entity_info(entity_address: str) -> Dict:
    entity = w3.eth.contract(address=entity_address, abi=ENTITY_ABI)
    
    balance = entity.functions.balance().call()
    manager = entity.functions.manager().call()
    entity_type = entity.functions.entityType().call()
    
    print(f"Balance: {balance / 10**6:.2f} USDC")
    print(f"Manager: {manager}")
    print(f"Entity Type: {'Org' if entity_type == 0 else 'Fund'}")
    
    return {
        'balance': balance,
        'manager': manager,
        'entity_type': 'org' if entity_type == 0 else 'fund'
    }

# Usage
entity_info = get_entity_info('0x...')

Verify Entity Status

Check if an entity is active and can transact:
const REGISTRY_ABI = [
  'function isActiveEntity(address entity) external view returns (bool)'
];

async function checkEntityStatus(entityAddress: string) {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const registry = new ethers.Contract(REGISTRY_ADDRESS, REGISTRY_ABI, provider);
  
  const isActive = await registry.isActiveEntity(entityAddress);
  
  if (!isActive) {
    console.log('⚠️  Entity is inactive - cannot receive donations or make transfers');
    return false;
  }
  
  console.log('✓ Entity is active');
  return true;
}
Registry Addresses:
  • Ethereum: 0x94106ca9c7e567109a1d39413052887d1f412183
  • Base: 0x237b53bcfbd3a114b549dfec96a9856808f45c94
  • Optimism: 0x5d216bb28852f98ceaa29180670397ab01774ea6

Check Entity Manager

Verify if an address is the manager of an entity:
async function canManageEntity(
  entityAddress: string,
  walletAddress: string
): Promise<boolean> {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const entity = new ethers.Contract(entityAddress, ENTITY_ABI, provider);
  
  // Check if wallet is the manager
  const manager = await entity.manager();
  if (manager.toLowerCase() === walletAddress.toLowerCase()) {
    console.log('✓ Address is the entity manager');
    return true;
  }
  
  console.log('✗ Address is not the entity manager');
  return false;
}

Get Org Tax ID

For Org entities, retrieve the tax identifier:
const ORG_ABI = [
  'function taxId() external view returns (bytes32)'
];

async function getOrgTaxId(orgAddress: string): Promise<string> {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const org = new ethers.Contract(orgAddress, ORG_ABI, provider);
  
  const taxIdBytes = await org.taxId();
  const taxId = ethers.utils.parseBytes32String(taxIdBytes);
  
  console.log(`Tax ID (EIN): ${taxId}`);
  return taxId;
}

Compute Entity Address (Before Deployment)

Predict entity address before deployment:
const FACTORY_ABI = [
  'function computeOrgAddress(bytes32 orgId) external view returns (address)',
  'function computeFundAddress(address manager, bytes32 salt) external view returns (address)'
];

async function predictOrgAddress(taxId: string) {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const factory = new ethers.Contract(FACTORY_ADDRESS, FACTORY_ABI, provider);
  
  const orgId = ethers.utils.formatBytes32String(taxId);
  const predictedAddress = await factory.computeOrgAddress(orgId);
  
  console.log(`Org will be deployed at: ${predictedAddress}`);
  return predictedAddress;
}

async function predictFundAddress(manager: string, salt: string) {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const factory = new ethers.Contract(FACTORY_ADDRESS, FACTORY_ABI, provider);
  
  const saltBytes = ethers.utils.formatBytes32String(salt);
  const predictedAddress = await factory.computeFundAddress(manager, saltBytes);
  
  console.log(`Fund will be deployed at: ${predictedAddress}`);
  return predictedAddress;
}

Check Fees

Query donation and transfer fees for an entity:
const REGISTRY_ABI = [
  'function getDonationFee(address entity) external view returns (uint32)',
  'function getDonationFeeWithOverrides(address entity) external view returns (uint32)',
  'function getTransferFee(address from, address to) external view returns (uint32)'
];

async function getEntityFees(entityAddress: string) {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const registry = new ethers.Contract(REGISTRY_ADDRESS, REGISTRY_ABI, provider);
  
  const donationFee = await registry.getDonationFee(entityAddress);
  const donationFeeWithOverride = await registry.getDonationFeeWithOverrides(entityAddress);
  
  console.log(`Donation Fee: ${donationFee / 100}%`);
  console.log(`With Override: ${donationFeeWithOverride / 100}%`);
  
  return {
    donationFee: donationFee / 100,
    donationFeeWithOverride: donationFeeWithOverride / 100
  };
}

async function getTransferFee(fromEntity: string, toEntity: string) {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const registry = new ethers.Contract(REGISTRY_ADDRESS, REGISTRY_ABI, provider);
  
  const transferFee = await registry.getTransferFee(fromEntity, toEntity);
  
  console.log(`Transfer Fee: ${transferFee / 100}%`);
  return transferFee / 100;
}

Batch Query Multiple Entities

Efficiently query multiple entities:
import { ethers } from 'ethers';

interface EntitySummary {
  address: string;
  balance: string;
  manager: string;
  entityType: 'org' | 'fund';
  isActive: boolean;
}

async function batchGetEntities(
  entityAddresses: string[]
): Promise<EntitySummary[]> {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  
  // Create multicall contract (optional, for efficiency)
  const results: EntitySummary[] = [];
  
  for (const address of entityAddresses) {
    const entity = new ethers.Contract(address, ENTITY_ABI, provider);
    const registry = new ethers.Contract(REGISTRY_ADDRESS, REGISTRY_ABI, provider);
    
    const [balance, manager, entityType, isActive] = await Promise.all([
      entity.balance(),
      entity.manager(),
      entity.entityType(),
      registry.isActiveEntity(address)
    ]);
    
    results.push({
      address,
      balance: ethers.utils.formatUnits(balance, 6),
      manager,
      entityType: entityType === 0 ? 'org' : 'fund',
      isActive
    });
  }
  
  return results;
}

// Usage
const entities = await batchGetEntities([
  '0x...',  // Org 1
  '0x...',  // Fund 1
  '0x...'   // Org 2
]);

entities.forEach(e => {
  console.log(`\n${e.entityType.toUpperCase()}: ${e.address}`);
  console.log(`  Balance: ${e.balance} USDC`);
  console.log(`  Manager: ${e.manager}`);
  console.log(`  Active: ${e.isActive ? '✓' : '✗'}`);
});

Common Query Patterns

Check if Entity Exists

async function entityExists(address: string): Promise<boolean> {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const code = await provider.getCode(address);
  
  // If code is '0x', no contract exists
  if (code === '0x') {
    console.log('No contract at this address');
    return false;
  }
  
  // Verify it's an Endaoment entity
  const registry = new ethers.Contract(REGISTRY_ADDRESS, REGISTRY_ABI, provider);
  const isEntity = await registry.isActiveEntity(address);
  
  if (!isEntity) {
    console.log('Contract exists but is not an active Endaoment entity');
  }
  
  return isEntity;
}

Monitor Entity Balance Changes

async function watchEntityBalance(entityAddress: string) {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const entity = new ethers.Contract(entityAddress, ENTITY_ABI, provider);
  
  // Listen for donation events
  entity.on('EntityDonationReceived', (from, to, tokenIn, amountIn, amountReceived, amountFee) => {
    console.log('\n💰 Donation Received:');
    console.log(`  From: ${from}`);
    console.log(`  Amount: ${ethers.utils.formatUnits(amountReceived, 6)} USDC`);
    console.log(`  Fee: ${ethers.utils.formatUnits(amountFee, 6)} USDC`);
  });
  
  // Listen for transfer events
  entity.on('EntityValueTransferred', (from, to, amountReceived, amountFee) => {
    console.log('\n📤 Transfer Out:');
    console.log(`  To: ${to}`);
    console.log(`  Amount: ${ethers.utils.formatUnits(amountReceived, 6)} USDC`);
    console.log(`  Fee: ${ethers.utils.formatUnits(amountFee, 6)} USDC`);
  });
  
  console.log(`👀 Watching entity: ${entityAddress}`);
}

Next Steps