IdeaFactory
IdeaFactory.sol deploys graduated ideas as Clanker v4 ERC-20 tokens. 302 LOC, Solidity 0.8.26.
The factory orchestrates four contracts atomically: Clanker, ChamberRegistry, FeeRouter, and
the deployed token itself.
Surface
function deployIdea(
uint64 chamberId,
uint32 ideaSlot,
bytes32 salt,
bytes calldata poolData,
bytes calldata lockerData,
bytes calldata extensionData
) external payable onlyDeployer returns (address tokenAddress, uint256 lpTokenId);
// Owner-gated
function setDeployer(address deployer) external onlyOwner;
function setChamberRegistry(address registry) external onlyOwner;
function setFeeRouter(address router) external onlyOwner;
function setClanker(address clanker) external onlyOwner;
function setClankerHook(address hook) external onlyOwner;deployer is the relayer EOA that drives the factory. clanker is the Clanker v4 factory
address (0xE85A...83a9 on mainnet, MockClanker on Sepolia).
Deploy flow
function deployIdea(...) external payable onlyDeployer returns (address tokenAddress, uint256 lpTokenId) {
// 1. Read snapshot params (creator, BPS splits) from forum-api-supplied calldata.
DeployParams memory p = _decodeParams(extensionData);
// 2. Encode the Clanker TokenConfig with FeeRouter as the first reward recipient.
IClanker.TokenConfig memory cfg = _buildConfig(p, salt, poolData, lockerData);
// 3. Atomically call Clanker.
(tokenAddress, lpTokenId) = IClanker(clanker).deployToken{value: msg.value}(cfg);
// 4. Register with ChamberRegistry.
IChamberRegistry(chamberRegistry).registerIdea(
p.chamberId, p.ideaSlot, tokenAddress, p.ticker, p.creator
);
// 5. Snapshot per-idea fee config on FeeRouter (immutable from this point).
IFeeRouter(feeRouter).configureIdea(
tokenAddress,
p.protocol, p.creator, p.winnersSplitter,
p.forPool, p.againstPool, p.executorPool,
p.protocolBps, p.creatorBps, p.winnersBps,
p.forBps, p.againstBps, p.executorBps
);
emit IdeaDeployed(p.chamberId, p.ideaSlot, tokenAddress, p.ticker);
}TokenConfig shape
The Clanker TokenConfig is the heaviest payload in the system:
struct TokenConfig {
address tokenAdmin; // who can admin the deployed token
string tokenName; // human-readable name
string tokenSymbol; // ticker (8 chars)
bytes32 salt; // CREATE2 salt
bytes32 image; // ipfs hash of image
string metadata; // ipfs hash of metadata JSON
string context; // app-specific context string
bytes poolData; // hook + V4 pool init params
bytes lockerData; // LP locker config
bytes extensionData; // reserved
address[] rewardRecipients; // who receives reward shares
uint16[] rewardBps; // bps per recipient (sums to 8000)
uint8 vault; // reserved vault config
uint8 mevBlockDelay; // blocks to delay MEV-block hook
uint8 extension; // reserved
}Quorum’s _buildConfig sets:
tokenAdmin: address(this)— see audit M-02 below.tokenSymbol: p.ticker— derived fromkeccak256(chamberId, slot)truncated to 8 chars.rewardRecipients[0]: feeRouter— all fees flow through Quorum’s router.rewardBps[0]: 8000— Clanker keeps 2000 (20%), router gets 8000 (80% of pool).mevBlockDelay: 1— MEV-block delay enabled (1 block).
Audit notes
- M-02 —
tokenAdmin = address(this)strands Clanker admin rights forever. The factory has nocallTokenAdminproxy, so admin powers (metadata updates, locker reconfig) are unreachable. Pre-mainnet fix:tokenAdmin: owner()so the protocol multisig retains admin. - L-06 —
deployIdeaispayableand forwardsmsg.valueto Clanker. There is noreceive()/fallback()so any ETH that ends up inIdeaFactory(e.g. a Clanker refund) is stuck. Pre-mainnet fix: addreceive() external payable {}plus an owner-gatedsweepETH(address to).
salt and CREATE2 determinism
The salt passed to Clanker.deployToken is the CREATE2 salt for the new ERC-20. This means
the token address is deterministic given:
- The Clanker factory address.
- The salt.
- The token bytecode (Clanker v4’s standard ERC-20 implementation).
The external audit scope includes verifying this determinism on Base mainnet. If a salt
collides with a previously deployed token, Clanker.deployToken reverts and the factory call
fails atomically — no partial state.
Wiring
IdeaFactory.deployer → relayer EOA (set by setDeployer)
IdeaFactory.clanker → Clanker v4 factory (mainnet 0xE85A...83a9, Sepolia MockClanker)
IdeaFactory.chamberRegistry → ChamberRegistry
IdeaFactory.feeRouter → FeeRouter
ChamberRegistry.factory → IdeaFactory (set by setFactory)
FeeRouter.factory → IdeaFactory (set by setFactory)All five wiring calls are made in Deploy.s.sol after the constructor pass. The Sepolia E2E run
confirms every pointer via cast call.