Making Donations
Send USDC or other tokens to entities onchain.
Overview
Direct USDC : Approve and donate
Token Swap : Any ERC20 → USDC via swap wrapper
Native ETH : Wrapped to WETH → USDC
Donation fees apply, EntityDonationReceived event emitted
Permissionless - anyone can donate
Direct USDC Donations
Single call: invoke donate(amount) on the entity contract.
TypeScript
const provider = new ethers . providers . JsonRpcProvider ( RPC_URL );
const signer = new ethers . Wallet ( PRIVATE_KEY , provider );
const usdc = new ethers . Contract ( USDC_ADDRESS , USDC_ABI , signer );
const entity = new ethers . Contract ( ENTITY_ADDRESS , ENTITY_ABI , signer );
const amount = ethers . utils . parseUnits ( '100' , 6 );
// Approve entity to spend USDC (required)
await usdc . approve ( ENTITY_ADDRESS , amount );
await entity . donate ( amount );
Python
w3 = Web3(Web3.HTTPProvider( RPC_URL ))
account = Account.from_key( PRIVATE_KEY )
usdc = w3.eth.contract( address = USDC_ADDRESS , abi = USDC_ABI )
entity = w3.eth.contract( address = ENTITY_ADDRESS , abi = ENTITY_ABI )
amount = 100 * ( 10 ** 6 ) # 100 USDC
# Approve entity to spend USDC (required)
approve_tx = usdc.functions.approve( ENTITY_ADDRESS , amount).build_transaction({
'from' : account.address,
'nonce' : w3.eth.get_transaction_count(account.address)
})
signed = account.sign_transaction(approve_tx)
w3.eth.send_raw_transaction(signed.rawTransaction)
# Donate
tx = entity.functions.donate(amount).build_transaction({
'from' : account.address,
'nonce' : w3.eth.get_transaction_count(account.address)
})
signed_tx = account.sign_transaction(tx)
w3.eth.send_raw_transaction(signed_tx.rawTransaction)
Token Swap + Donation
Swap any ERC20 to USDC and donate:
const WETH_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' ;
const USDC_ADDRESS = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' ;
const ENTITY_ADDRESS = '0x...' ; // Your entity
const SWAP_WRAPPER_ADDRESS = '0x60303F43028AD7198bc4bDF96ccBf734A87444df' ;
const weth = new ethers . Contract ( WETH_ADDRESS , WETH_ABI , signer );
const entity = new ethers . Contract ( ENTITY_ADDRESS , ENTITY_ABI , signer );
const amount = ethers . utils . parseEther ( '1' );
// Hardcoded for example - in practice, this should be fetched from
// smart order router / Endaoment's backend
const path = ethers . utils . solidityPack (
[ 'address' , 'uint24' , 'address' ],
[ WETH_ADDRESS , 3000 , USDC_ADDRESS ]
);
const minOutput = ethers . utils . parseUnits ( '1800' , 6 ); // Min USDC with slippage
const deadline = Math . floor ( Date . now () / 1000 ) + 600 ; // 10 min
const swapData = ethers . utils . defaultAbiCoder . encode (
[ 'bytes' , 'uint256' , 'uint256' ],
[ path , minOutput , deadline ]
);
await weth . approve ( ENTITY_ADDRESS , amount );
await entity . swapAndDonate ( SWAP_WRAPPER_ADDRESS , WETH_ADDRESS , amount , swapData );
ETH Donation
const ETH_PLACEHOLDER = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' ;
const amount = ethers . utils . parseEther ( '0.5' );
// Encode swap path (WETH -> USDC)
const path = ethers . utils . solidityPack (
[ 'address' , 'uint24' , 'address' ],
[ WETH_ADDRESS , 3000 , USDC_ADDRESS ]
);
const minOutput = ethers . utils . parseUnits ( '900' , 6 );
const deadline = Math . floor ( Date . now () / 1000 ) + 600 ;
const swapData = ethers . utils . defaultAbiCoder . encode (
[ 'bytes' , 'uint256' , 'uint256' ],
[ path , minOutput , deadline ]
);
await entity . swapAndDonate (
SWAP_WRAPPER_ADDRESS ,
ETH_PLACEHOLDER ,
amount ,
swapData ,
{ value: amount }
);
Reconcile Balance
Process USDC sent directly to entity:
await entity . reconcileBalance ();
Donation Fees
Entity Type Inbound Fee Note Org 1.5% (150 bp) 100 USDC → 1.5 USDC fee, 98.5 USDC net Fund 0.05-0.50% Tiered: 0.50% on first 250 k , d e c l i n i n g t o 0.05 250k, declining to 0.05% over 250 k , d ec l inin g t o 0.05 100M
Funds also have 1.0% outbound fee on grants. Check exact fee: registry.getDonationFee(entityAddress)
Fee timing and compounding: inbound donation fees apply when the donation is processed (e.g., donate() or swapAndDonate()), reducing the entity’s USDC balance by the fee immediately. Outbound fees apply when funds are transferred via grants. If you donate and then later transfer, each step applies its respective fee (they do not “compound” at one step, but both fees apply across the two actions). Always verify exact fees via the Registry before large transactions.
Common Errors
Error Fix EntityInactiveEntity not active. Check: registry.isActiveEntity(entityAddress) ERC20: insufficient allowanceIncrease approval: usdc.approve(entity, amount) InvalidActionSwap wrapper not approved. Check: registry.isSwapperSupported(wrapperAddress) before using the wrapper
Advanced Patterns
Calculate Fees
const registry = new ethers . Contract ( REGISTRY_ADDRESS , REGISTRY_ABI , provider );
const feeInBp = await registry . getDonationFee ( entityAddress );
const feeAmount = amount . mul ( feeInBp ). div ( 10000 );
const netAmount = amount . sub ( feeAmount );
Batch Donations
for ( const { address , amount } of recipients ) {
const entity = new ethers . Contract ( address , ENTITY_ABI , signer );
await entity . donate ( ethers . utils . parseUnits ( amount , 6 ));
}
Contract Integration
contract DonationHelper {
function donateToEntity ( address entity , uint256 amount ) external {
IERC20 (usdc). transferFrom ( msg.sender , address ( this ), amount);
IERC20 (usdc). approve (entity, amount);
IEntity (entity). donate (amount);
}
}
Next Steps
Entity State Query entity balances and info
Entity Deployment Deploy Orgs and Funds
Entity Best Practices Implement production safeguards
Entity Events Monitor donation activity