BasketFactory
contracts/src/BasketFactory.sol · Solidity 0.8.26
Deploys Basket clones (EIP-1167 minimal proxies), funds them with seed tokens, and initializes them atomically in one transaction, then registers each. It enforces creation gating (an open-creation flag or a curator allowlist) and exposes owner controls.
- Inherits:
Ownable2Step. Uses OpenZeppelinClones.
Why atomic: a funded-but-uninitialized clone would be hijackable by a third party, so the seed transfer and
initialize()must never be split across transactions.
Storage
address public immutable basketImplementation; // the clone target
Whitelist public immutable whitelist; // injected into every basket
address public treasury; // protocol treasury injected into every basket
bool public openCreation; // if true, anyone may create; else curators only
mapping(address => bool) public isCurator; // creation allowlist
mapping(address => bool) public isBasket; // registry of created baskets
address[] internal baskets;
createBasket
function createBasket(
address[] calldata tokens,
uint256[] calldata seedUnits,
uint256 seedShares,
uint16 entryFeeBps,
uint16 exitFeeBps,
uint16 mgmtFeeBpsPerYear,
string calldata name,
string calldata symbol
) external returns (address basket)
The main entry point. msg.sender becomes the new basket's creator and its fee recipient.
Reverts NotCurator() if !openCreation && !isCurator[msg.sender]. Checks for duplicate tokens (DuplicateToken()). Then:
basket = Clones.clone(basketImplementation)- Seed: for each token, transfers
amt = ceil(seedUnits[i] × totalSeed / 1e18)from the caller into the clone, wheretotalSeed = seedShares + DEAD_SHARES(rounds up so the clone always holds at least whatinitializeverifies). - Init: calls
Basket.initialize(...)withcreator = msg.sender,treasuryandwhitelistfrom the factory. - Registers
isBasket[basket] = true, appends tobaskets, emitsBasketCreated.
Any revert from the seed transfers or Basket.initialize (e.g. FeeAboveCap, BadParams) bubbles up.
The caller must hold each constituent and have approved the factory for the seed amount before calling. See the Create a basket guide.
Owner functions
constructor(address gov, address impl, address wl, address treasury_) Ownable(gov)
function setCurator(address who, bool ok) external onlyOwner // emits CuratorSet
function setOpenCreation(bool open) external onlyOwner // emits OpenCreationSet
function setMintPaused(address basket, bool p) external onlyOwner // pass-through to Basket.setMintPaused
setOpenCreation(true) is what makes basket creation permissionless for any wallet. setMintPaused lets the owner pause a specific basket's mint (redemption is never pausable).
View functions
function allBaskets() external view returns (address[] memory)
Plus auto-getters: basketImplementation(), whitelist(), treasury(), openCreation(), isCurator(address), isBasket(address), and the inherited Ownable2Step surface.
Events & errors
event BasketCreated(address indexed basket, address indexed creator, string symbol);
event CuratorSet(address indexed who, bool allowed);
event OpenCreationSet(bool open);
error NotCurator(); // createBasket while gated and caller isn't a curator
error DuplicateToken(); // tokens array contains a duplicate
BasketCreated is the event the backend indexer watches to discover new baskets. isBasket is what the HoodZapRouter checks before routing a zap.