Skip to Content
Quorum contracts are live on Base Sepolia. Mainnet ships after external audit. Do not send real funds.
ContractsIdeaFactory

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 from keccak256(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-02tokenAdmin = address(this) strands Clanker admin rights forever. The factory has no callTokenAdmin proxy, so admin powers (metadata updates, locker reconfig) are unreachable. Pre-mainnet fix: tokenAdmin: owner() so the protocol multisig retains admin.
  • L-06deployIdea is payable and forwards msg.value to Clanker. There is no receive() / fallback() so any ETH that ends up in IdeaFactory (e.g. a Clanker refund) is stuck. Pre-mainnet fix: add receive() external payable {} plus an owner-gated sweepETH(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.

Last updated on