Skip to main content

Deploying Entities

Deploy new Org and Fund entities using OrgFundFactory.

Overview

Entity Types:
  • Org: 501(c)(3) nonprofit organizations
  • Fund: Donor-advised funds (DAFs) or Community Funds
Key Features:
  • Minimal proxy pattern (EIP-1167) for low gas costs
  • Deterministic cross-chain addresses
  • Atomic deploy-and-donate operations
  • Token swapping during deployment

Factory Addresses

NetworkOrgFundFactory Address
Ethereum Mainnet0x10fD9348136dCea154F752fe0B6dB45Fc298A589
Base Mainnet0x10fD9348136dCea154F752fe0B6dB45Fc298A589
Optimism0x10fD9348136dCea154F752fe0B6dB45Fc298A589
The factory uses the same address across all supported EVM chains, enabling deterministic entity addresses and syncing org addresses across chains. Please note that this is subject to change in future deployments of the Endaoment protocol.

Deploying an Organization

Basic Org Deployment

import { ethers } from 'ethers';

const FACTORY_ADDRESS = '0x10fD9348136dCea154F752fe0B6dB45Fc298A589';

const FACTORY_ABI = [
  'function deployOrg(bytes32 orgId) external returns (address)',
  'event EntityDeployed(address indexed entity, uint8 indexed entityType, address indexed entityManager)'
];

async function deployOrg() {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const signer = new ethers.Wallet(PRIVATE_KEY, provider);
  
  const factory = new ethers.Contract(FACTORY_ADDRESS, FACTORY_ABI, signer);
  
  // Org ID is typically the EIN (tax ID) for US nonprofits
  // Must be converted to bytes32
  const orgIdString = '12-3456789';  // Format: XX-XXXXXXX
  const orgId = ethers.utils.formatBytes32String(orgIdString);
  
  console.log(`Deploying Org with ID: ${orgIdString}...`);
  
  const tx = await factory.deployOrg(orgId, { gasLimit: 500000 });
  const receipt = await tx.wait();
  
  // Extract org address from event
  const entityDeployedEvent = receipt.events?.find(
    (e: any) => e.event === 'EntityDeployed'
  );
  
  const orgAddress = entityDeployedEvent?.args?.entity;
  console.log(`Org deployed at: ${orgAddress}`);
  console.log(`Transaction: ${tx.hash}`);
  
  return orgAddress;
}

deployOrg().catch(console.error);

Python/web3.py

from web3 import Web3
from eth_account import Account
import os

RPC_URL = 'https://eth-mainnet.g.alchemy.com/v2/YOUR-API-KEY'
PRIVATE_KEY = os.environ['PRIVATE_KEY']
FACTORY_ADDRESS = '0x10fD9348136dCea154F752fe0B6dB45Fc298A589'

w3 = Web3(Web3.HTTPProvider(RPC_URL))
account = Account.from_key(PRIVATE_KEY)

FACTORY_ABI = [{
    "inputs": [{"name": "orgId", "type": "bytes32"}],
    "name": "deployOrg",
    "outputs": [{"name": "", "type": "address"}],
    "type": "function"
}, {
    "anonymous": False,
    "inputs": [
        {"indexed": True, "name": "entity", "type": "address"},
        {"indexed": True, "name": "entityType", "type": "uint8"},
        {"indexed": True, "name": "entityManager", "type": "address"}
    ],
    "name": "EntityDeployed",
    "type": "event"
}]

def deploy_org():
    factory = w3.eth.contract(address=FACTORY_ADDRESS, abi=FACTORY_ABI)
    
    org_id_string = '12-3456789'  # Tax ID
    # Convert to bytes32
    org_id_bytes = org_id_string.encode('utf-8')
    if len(org_id_bytes) > 31:
        raise ValueError("Org ID too long")
    # Match ethers.utils.formatBytes32String: utf-8 + NUL terminator + right pad
    org_id = org_id_bytes + b'\x00' + (b'\x00' * (31 - len(org_id_bytes)))
    
    print(f"Deploying Org with ID: {org_id_string}...")
    
    tx = factory.functions.deployOrg(org_id).build_transaction({
        'from': account.address,
        'nonce': w3.eth.get_transaction_count(account.address),
        'gas': 500000,
        'gasPrice': w3.eth.gas_price
    })
    
    signed_tx = account.sign_transaction(tx)
    tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
    receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
    
    # Parse event logs to get org address
    entity_deployed_event = factory.events.EntityDeployed().process_receipt(receipt)[0]
    org_address = entity_deployed_event['args']['entity']
    
    print(f"Org deployed at: {org_address}")
    print(f"Transaction: {tx_hash.hex()}")
    
    return org_address

if __name__ == '__main__':
    deploy_org()

Deploy and Donate (Atomic)

Deploy an Org and make an initial donation in a single transaction:
const USDC_ADDRESS = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';  // Mainnet USDC

const FACTORY_ABI = [
  'function deployOrgAndDonate(bytes32 orgId, uint256 amount) external returns (address)'
];

async function deployOrgAndDonate() {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const signer = new ethers.Wallet(PRIVATE_KEY, provider);
  
  const factory = new ethers.Contract(FACTORY_ADDRESS, FACTORY_ABI, signer);
  const usdc = new ethers.Contract(
    USDC_ADDRESS,
    ['function approve(address spender, uint256 amount) external returns (bool)'],
    signer
  );
  
  const orgIdString = '12-3456789';
  const orgId = ethers.utils.formatBytes32String(orgIdString);
  const donationAmount = ethers.utils.parseUnits('1000', 6);  // 1000 USDC
  
  // Step 1: Approve factory to spend USDC
  console.log('Approving USDC...');
  const approveTx = await usdc.approve(FACTORY_ADDRESS, donationAmount);
  await approveTx.wait();
  
  // Step 2: Deploy and donate in one transaction
  console.log('Deploying Org and donating...');
  const tx = await factory.deployOrgAndDonate(orgId, donationAmount, {
    gasLimit: 800000
  });
  
  const receipt = await tx.wait();
  
  const entityDeployedEvent = receipt.events?.find(
    (e: any) => e.event === 'EntityDeployed'
  );
  
  const orgAddress = entityDeployedEvent?.args?.entity;
  console.log(`Org deployed and funded at: ${orgAddress}`);
  
  return orgAddress;
}

Deploying a Fund

Funds work similarly to Orgs but don’t require a tax ID:
const FACTORY_ABI = [
  'function deployFund(address manager, bytes32 salt) external returns (address)',
  'event EntityDeployed(address indexed entity, uint8 indexed entityType, address indexed manager)'
];

async function deployFund() {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const signer = new ethers.Wallet(PRIVATE_KEY, provider);
  
  const factory = new ethers.Contract(FACTORY_ADDRESS, FACTORY_ABI, signer);
  
  // Manager will be the deployer (msg.sender)
  const manager = signer.address;
  
  // Generate a unique salt (or use a specific one for deterministic address)
  const salt = ethers.utils.formatBytes32String('my-fund-v1');
  
  console.log('Deploying Fund...');
  
  const tx = await factory.deployFund(manager, salt, { gasLimit: 500000 });
  const receipt = await tx.wait();
  
  const entityDeployedEvent = receipt.events?.find(
    (e: any) => e.event === 'EntityDeployed'
  );
  
  const fundAddress = entityDeployedEvent?.args?.entity;
  const entityType = entityDeployedEvent?.args?.entityType;
  const managerAddress = entityDeployedEvent?.args?.manager;
  
  console.log(`Fund deployed at: ${fundAddress}`);
  console.log(`Entity type: ${entityType}`);  // 1 for Fund
  console.log(`Manager set to: ${managerAddress}`);
  console.log(`Transaction: ${tx.hash}`);
  
  return fundAddress;
}

Deploy Fund with Initial Deposit

const FACTORY_ABI = [
  'function deployFundAndDonate(address manager, bytes32 salt, uint256 amount) external returns (address)'
];

async function deployFundAndDonate() {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const signer = new ethers.Wallet(PRIVATE_KEY, provider);
  
  const factory = new ethers.Contract(FACTORY_ADDRESS, FACTORY_ABI, signer);
  const usdc = new ethers.Contract(
    USDC_ADDRESS,
    ['function approve(address spender, uint256 amount) external returns (bool)'],
    signer
  );
  
  const manager = signer.address;
  const salt = ethers.utils.formatBytes32String('my-fund-v2');
  const initialDeposit = ethers.utils.parseUnits('5000', 6);  // 5000 USDC
  
  // Approve
  const approveTx = await usdc.approve(FACTORY_ADDRESS, initialDeposit);
  await approveTx.wait();
  
  // Deploy and deposit
  const tx = await factory.deployFundAndDonate(manager, salt, initialDeposit, {
    gasLimit: 800000
  });
  
  const receipt = await tx.wait();
  
  const entityDeployedEvent = receipt.events?.find(
    (e: any) => e.event === 'EntityDeployed'
  );
  
  const fundAddress = entityDeployedEvent?.args?.entity;
  
  console.log(`Fund deployed and funded at: ${fundAddress}`);
  console.log(`Initial balance: ${ethers.utils.formatUnits(initialDeposit, 6)} USDC`);
  
  return fundAddress;
}

Deploy with Token Swap

Deploy an entity and donate any ERC20 token (swapped to USDC).
This function is available on all networks where OrgFundFactory is deployed. Update RPC endpoint, token addresses, and swap wrapper addresses for your target network. Example shows Ethereum Mainnet configuration.
const FACTORY_ABI = [
  'function deployOrgSwapAndDonate(bytes32 orgId, address swapWrapper, address tokenIn, uint256 amountIn, bytes calldata data) external payable returns (address)'
];

async function deployOrgWithTokenSwap() {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const signer = new ethers.Wallet(PRIVATE_KEY, provider);
  
  const factory = new ethers.Contract(FACTORY_ADDRESS, FACTORY_ABI, signer);
  
  const orgIdString = '12-3456789';
  const orgId = ethers.utils.formatBytes32String(orgIdString);
  
  // Ethereum Mainnet addresses - update for other chains
  const wethAddress = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
  const swapWrapperAddress = '0x60303F43028AD7198bc4bDF96ccBf734A87444df';
  const donationAmount = ethers.utils.parseEther('1');  // 1 WETH
  
  // Approve WETH
  const weth = new ethers.Contract(
    wethAddress,
    ['function approve(address spender, uint256 amount) external returns (bool)'],
    signer
  );
  
  await (await weth.approve(FACTORY_ADDRESS, donationAmount)).wait();
  
  // Encode swap data (Uniswap V3 path)
  const path = ethers.utils.solidityPack(
    ['address', 'uint24', 'address'],
    [wethAddress, 3000, USDC_ADDRESS]
  );
  
  const deadline = Math.floor(Date.now() / 1000) + 600;
  const minOutput = ethers.utils.parseUnits('1800', 6);
  
  // Hardcoded for this example - in practice, this should be fetched from
  // Endaoment's backend or Uniswap's smart order router
  // for optimal route/output
  const swapData = ethers.utils.defaultAbiCoder.encode(
    ['bytes', 'uint256', 'uint256'],
    [path, minOutput, deadline]
  );
  
  // Deploy, swap, and donate
  const tx = await factory.deployOrgSwapAndDonate(
    orgId,
    swapWrapperAddress,
    wethAddress,
    donationAmount,
    swapData,
    { gasLimit: 1200000 }
  );
  
  const receipt = await tx.wait();
  
  const entityDeployedEvent = receipt.events?.find(
    (e: any) => e.event === 'EntityDeployed'
  );
  
  const orgAddress = entityDeployedEvent?.args?.entity;
  
  console.log(`Org deployed at: ${orgAddress}`);
  console.log(`Funded with WETH swapped to USDC`);
  
  return orgAddress;
}

Predict Addresses

// Use factory's compute functions
const orgAddress = await factory.computeOrgAddress(orgId);
const fundAddress = await factory.computeFundAddress(manager, salt);

Verify Deployment

const entity = new ethers.Contract(entityAddress, ENTITY_ABI, provider);

const balance = await entity.balance();        // USDC balance
const manager = await entity.manager();        // Manager address
const isActive = await registry.isActiveEntity(entityAddress);  // Active status

Common Errors

”Org already exists”

Since entity contract addresses are deterministic, using a same tax id will error because of CREATE2 collision.
try {
  await factory.deployOrg(orgId);
} catch (error) {
  if (error.message.includes('already exists')) {
    console.log('Org already deployed. Querying existing address...');
    const address = await queryOrgAddress(orgId);
    return address;
  }
  throw error;
}

”ERC20: insufficient allowance”

Increase USDC approval:
// Approve more than needed to account for fees
const approvalAmount = donationAmount.mul(110).div(100);  // 10% buffer
await usdc.approve(FACTORY_ADDRESS, approvalAmount);

Best Practices

  • Use atomic operations (deployAndDonate)
  • Deploy on L2s (Base) for lower costs
  • Verify entity status post-deployment

Next Steps