ZkPadFactory
Source: contracts/src/ZkPadFactory.sol · Interface: IZkPadFactory · Port of Clanker
v4 Clanker to PancakeSwap Infinity. Ownable2Step, ReentrancyGuardTransient.
Purpose
One call, deployToken, launches a coin:
- deploys a
ZkPadTokenwith CREATE2, saltkeccak256(abi.encode(msg.sender, tokenConfig.salt)); - has an enabled hook create the dynamic-fee CL pool against an allow-listed quote token;
- has an enabled locker place 100% of the supply in 1..7 single-sided positions;
- optionally performs an atomic dev buy;
- optionally arms the anti-sniper MEV module.
It also receives the hook's protocol fees (25% of F) and the locker's protocol top-ups, and
forwards them to teamFeeRecipient. The FeeVault reads tokenDeploymentInfo to authorize
creator-triggered consolidations and to record launch credit.
Launch configuration
struct DeploymentConfig {
TokenConfig tokenConfig;
PoolConfig poolConfig;
LockerConfig lockerConfig;
MevModuleConfig mevModuleConfig;
DevBuyConfig devBuyConfig;
bytes beneficiaryHint; // emitted in TokenLaunched, never stored
}
| Struct | Field | Meaning |
|---|---|---|
TokenConfig | tokenAdmin | Cosmetic admin and the only address that may lower fees; address(0) renounces both |
name, symbol | ERC20 name and symbol | |
salt | User salt; mixed with msg.sender for CREATE2 (stops address squatting, not mempool observation) | |
image, metadata, context | Cosmetic data; context is immutable (e.g. client identifier) | |
PoolConfig | hook | Must be enabled via setHook |
pairedToken | Quote token; must be enabled via setQuoteToken | |
tickIfToken0IsToken | Starting tick in token0 orientation; multiple of 200, inside the quote's bounds | |
tickSpacing | Must equal TICK_SPACING (200) | |
poolData | abi.encode(IZkPadHook.PoolStaticConfigVars{tokenFee, pairedFee}) | |
LockerConfig | locker | Must be enabled for the hook via setLocker |
beneficiaryId | Non-zero FeeVault id that receives 100% of LP fees | |
tickLower[], tickUpper[], positionBps[] | 1..7 positions (token0 orientation), bps sum to 10,000, each > 0 | |
lockerData | abi.encode(ILpLocker.LpFeeConversionInfo); empty = FeeIn.Paired (convert token-side fees to the paired token). Both / Token credit the beneficiary in the launched token, which relayers cannot price and consolidation cannot route (self-funded claims only); the SDK requires allowSelfFundedTokenFees for them | |
MevModuleConfig | mevModule, mevModuleData | address(0) disables; data = abi.encode(FeeConfig) for the descending-fee module |
DevBuyConfig | amountIn | Paired-token amount; for WBNB must equal msg.value; 0 = none |
minAmountOut | Slippage guard | |
recipient | Receives the bought tokens (non-zero if amountIn > 0) |
Functions
| Function | Access | Description |
|---|---|---|
deployToken(DeploymentConfig) payable returns (address) | anyone | Launch. Reverts if deprecated. |
claimTeamFees(address token) | anyone | Forward the factory's balance of token (hook protocol fees) to teamFeeRecipient |
predictTokenAddress(address deployer, TokenConfig) view | CREATE2 address a launch would get | |
tokenDeploymentInfo(address token) view | DeploymentInfo{token, hook, locker, mevModule, pairedToken, poolId, beneficiaryId, deployer, launchedAt} | |
quoteTokenConfig(address) view | QuoteTokenConfig{enabled, minStartingTick, maxStartingTick} | |
launchOf(address token) view returns (address creator, bytes32 beneficiaryId) | Creator (msg.sender of deployToken) and beneficiary id; used to authorize creator-triggered consolidation | |
launchCount() view, launchAt(uint256 index) view | Bounded global launch index, in launch order | |
enabledHooks, enabledLockers(locker, hook), enabledMevModules | view | Allow-lists |
admins(address) view | Whether an address may manage quote tokens | |
TOKEN_SUPPLY, TICK_SPACING, vault, poolManager, wbnb, deprecated, teamFeeRecipient | view | Configuration |
setDeprecated(bool) | owner | Stop (or resume) new launches; never affects existing ones |
setHook, setLocker, setMevModule | owner | Enable or disable components for new launches |
setAdmin(address admin, bool enabled) | owner | Grant or revoke the quote-token admin role (e.g. the QuoteRegistry, which admits Tier 2 quotes). Clanker v4 OwnerAdmins. |
setQuoteToken(address, QuoteTokenConfig) | owner or admin | Allow-list a quote with tick bounds; disabling stops only new launches |
setTeamFeeRecipient(address) | owner | Treasury for protocol fees |
Events
| Event | Emitted when |
|---|---|
TokenLaunched(msgSender, tokenAddress, tokenAdmin, pairedToken, poolId, beneficiaryId, beneficiaryHint, tokenName, tokenSymbol, tokenImage, tokenMetadata, tokenContext, startingTick, poolHook, locker, mevModule, devBuyAmountIn, devBuyAmountOut) | A launch succeeds. Indexed: tokenAddress, tokenAdmin, pairedToken. |
DevBuy(token, recipient, amountIn, amountOut) | A dev buy executes |
ClaimTeamFees(token, recipient, amount) | Protocol fees are forwarded |
SetDeprecated, SetHook, SetLocker, SetMevModule, SetAdmin, SetQuoteToken, SetTeamFeeRecipient | Admin changes |
Errors
Deprecated, ZeroAddress, InvalidHook, InvalidLocker, InvalidMevModule,
HookNotEnabled, LockerNotEnabled, MevModuleNotEnabled, QuoteTokenNotAllowed(quote),
InvalidTickBounds, InvalidStartingTick(tick), InvalidTickSpacing(tickSpacing),
InvalidMsgValue, InvalidDevBuy, DevBuySlippage(amountOut, minAmountOut),
TeamFeeRecipientNotSet, OnlyVault, SupplyNotPlaced, Unauthorized,
IndexOutOfBounds(index, count).
Notes
msg.valueis accepted only as the dev buy of a WBNB-paired launch, and must equaldevBuyConfig.amountIn.- Pool pre-initialization is impossible: only the hook can initialize zk-pad pools, and it does so only for the factory.
- Use the SDK's
buildLaunchParamsto assemble and validate aDeploymentConfig.