Smart Contracts
CommunityPool.sol
Reference for every external function, event, and custom error on CommunityPool.sol. Source lives in src/CommunityPool.sol.
Constructor (V2)
constructor(
string memory name_,
string memory description_,
uint256 minimumUsd_,
address[] memory coOwners,
uint64 expiresAt_,
address ethUsdFeed,
uint32 ethUsdMaxPriceAge,
TokenConfig[] memory tokenConfigs,
address protocolConfig_
)Deploys a pool. The caller becomes the immutable deployer and is automatically added to the owner set. minimumUsd_ is denominated in 18-decimal USD wei. TokenConfig is { address token; address usdFeed; uint8 decimals; uint32 maxPriceAge }.
Every oracle value is fixed here and can never be changed: each feed, each feed's decimals (read from the feed at construction), and each maxPriceAge freshness window. So is protocolConfig_, which must be a contract with code. V1 took the same arguments without ethUsdMaxPriceAge, maxPriceAge and protocolConfig_.
Reverts: CommunityPool__ZeroAddress, CommunityPool__DuplicateOwner, CommunityPool__DuplicateToken, CommunityPool__ProtocolConfigNotContract, PriceConverter__InvalidMaxPriceAge, PriceConverter__UnsupportedFeedDecimals.
Protocol fee (V2 only)
Each contribution to a V2 pool pays floor(gross × feeBps / 10_000) to the treasury, taken out of the amount funded rather than added on top: the funder is debited exactly the amount they chose, and the pool keeps the remainder. The rate and the recipient are read from the shared ProtocolConfig on every contribution, so a rate change applies to existing pools without redeploying anything. The contract re-checks the 300 bps (3%) ceiling itself on every contribution and reverts above it, so no configuration can exceed the cap.
The protocol administrator's authority stops at that configuration. It confers no ability to withdraw or move pool assets, change pool ownership, or bypass a pool's withdrawal rules.
The USD minimum is checked against the gross amount, so a contribution that meets minimumUsd is never rejected because of the fee.
Funding
fund() — payable
Contribute ETH. The contract converts msg.value to USD via the ETH/USD feed and reverts if below minimumUsd. Emits Funded. Also triggered by the receive() and fallback() handlers, so plain ETH transfers to the contract fund the pool.
Reverts: CommunityPool__PoolExpired, CommunityPool__BelowMinimumUsd.
fundERC20(IERC20 token, uint256 amount)
Contribute a whitelisted ERC-20. Requires prior approve(). Uses the token's configured decimals and USD feed to enforce the minimum. Emits FundedERC20.
Reverts: CommunityPool__PoolExpired, CommunityPool__TokenNotWhitelisted, CommunityPool__BelowMinimumUsd.
Owner withdraws (before expiry)
withdraw(uint256 amount)
Partial ETH withdraw. Reverts if amount == 0 or if it exceeds the contract's ETH balance. Emits Withdrawn.
cheaperWithdraw()
Full ETH withdraw. Sweeps the contract's entire ETH balance tomsg.sender. Emits Withdrawn.
withdrawTokenAmount(IERC20 token, uint256 amount)
Partial ERC-20 withdraw of a whitelisted token. Reverts if amount == 0 or exceeds the contract's balance of that token. Emits WithdrawnToken.
withdrawToken(IERC20 token)
Full ERC-20 withdraw. Sweeps the contract's entire balance of the given token. Emits WithdrawnToken when the balance is non-zero.
All four owner withdraws revert with CommunityPool__NotOwner for non-owners and CommunityPool__WithdrawDisabledAfterExpiry after the expiry timestamp.
Release (after expiry)
releaseExpiredFundsToDeployer()
Callable by anyone once block.timestamp > expiresAt. Sweeps all ETH and every whitelisted ERC-20 balance to the original deployer. Emits Withdrawn and WithdrawnToken for each asset with a non-zero balance.
Reverts: CommunityPool__NotYetExpiredForRelease.
View functions
Pool name, description, and per-funder accounting are not stored on-chain. Read them off-chain from the PoolCreated, Funded, and FundedERC20 event logs.
minimumUsd,expiresAt,deployer— public immutables.isOwner(address)→boolgetOwner()→address— returns the deployer (kept for legacy UI callers).getWhitelistedTokens()→address[]getVersion()→uint256— returns the ETH/USD aggregator's version.
Events
event PoolCreated(
address indexed deployer,
string name,
string description,
uint256 minimumUsd,
uint64 expiresAt,
address[] coOwners,
address[] whitelistedTokens
);
// V2 (fee-bearing). feeRecipient is the zero address when feeAmount is 0.
event Funded(
address indexed funder,
address indexed feeRecipient,
uint256 grossAmount,
uint256 feeAmount,
uint256 netAmount
);
event FundedERC20(
address indexed token,
address indexed funder,
address indexed feeRecipient,
uint256 grossAmount,
uint256 feeAmount,
uint256 netAmount
);
// V1 (no fee) emitted the simpler form:
// event Funded(address indexed funder, uint256 amount);
// event FundedERC20(address indexed token, address indexed funder, uint256 amount);
event Withdrawn(address indexed owner, uint256 amount);
event WithdrawnToken(address indexed token, address indexed owner, uint256 amount);Custom errors
CommunityPool__NotOwnerCommunityPool__PoolExpiredCommunityPool__TokenNotWhitelistedCommunityPool__ZeroAddressCommunityPool__BelowMinimumUsdCommunityPool__DuplicateOwnerCommunityPool__DuplicateTokenCommunityPool__NotYetExpiredForReleaseCommunityPool__WithdrawDisabledAfterExpiryCommunityPool__InvalidWithdrawAmountCommunityPool__InsufficientBalanceCommunityPool__EthTransferFailedCommunityPool__ProtocolConfigNotContract(V2)CommunityPool__ProtocolFeeExceedsMaximum(V2)CommunityPool__InvalidFeeRecipient(V2)CommunityPool__ProtocolFeeTransferFailed(V2)CommunityPool__UnsupportedTokenBehavior(V2)PriceConverter__InvalidPrice(V2)PriceConverter__IncompleteRound(V2)PriceConverter__FutureTimestamp(V2)PriceConverter__StalePrice(V2)PriceConverter__UnsupportedFeedDecimals(V2)PriceConverter__InvalidMaxPriceAge(V2)
Deployed addresses
Pools are deployed by their creators, so each pool has its own address — the one shown after deployment and on the pool's page. The one shared contract is ProtocolConfig on Ethereum mainnet, at 0x2eD7F089a6C2971B24eA91121aD65f9242F622c0, which every V2 pool reads its fee rate and treasury from. Sepolia deployment artifacts live in communitypool/broadcast/.