Configure CCA (Continuous Clearing Auction) smart contract parameters through an interactive bulk form flow. Use when user says "configure auction", "cca auction", "setup token auction", "auction configuration", "continuous auction", or mentions CCA contracts.
Configure Continuous Clearing Auction (CCA) smart contract parameters for fair and transparent token distribution.
Runtime Compatibility: This skill uses
AskUserQuestionfor interactive prompts. IfAskUserQuestionis not available in your runtime, collect the same parameters through natural language conversation instead.
When the user invokes this skill, guide them through a bulk interactive form configuration flow using AskUserQuestion. Collect parameters in batches to minimize user interaction rounds.
Collect parameters in these batches:
Question 1: Task Type
After collection: If not "Configure auction parameters", skip to appropriate section.
Question 1: Network
chainId, blockTime, rpcUrl, currencyDecimals (for selected currency)Question 2: Token Address
tokenQuestion 3: Total Supply
totalSupplyQuestion 4: Currency
currencyAfter collection: Validate all inputs, show summary of basic configuration.
Question 1: Auction Duration
auctionBlocksQuestion 2: Prebid Period
prebidBlocksQuestion 3: Floor Price
Q96 * ratio / 10^(tokenDecimals - currencyDecimals)Q96 * ratio / 10^12Q96 * ratio / 10^0 = Q96 * ratiofloorPriceRatio, floorPrice (Q96), tokenDecimals, currencyDecimalsQuestion 4: Tick Spacing
tickSpacing = int(floorPrice * percentage)roundedFloorPrice = (floorPrice // tickSpacing) * tickSpacingroundedFloorPrice % tickSpacing == 0 must be truetickSpacingPercentage, tickSpacing (Q96), roundedFloorPriceAfter collection: Validate inputs, verify floor price divisibility, calculate and display Q96 values, show timing summary.
Question 1: Tokens Recipient
tokensRecipientQuestion 2: Funds Recipient
fundsRecipientQuestion 3: Start Time
startBlockendBlock = startBlock + prebidBlocks + auctionBlocks, claimBlock = endBlockQuestion 4: Minimum Funds Required
requiredCurrencyRaisedAfter collection: Validate addresses, fetch current block from RPC, calculate full block timeline.
Question 1: Validation Hook
validationHookAfter collection: Validate hook address if provided.
If MCP server is not running, provide instructions to start it:
# Navigate to MCP server directory
cd packages/plugins/uniswap-cca/mcp-server/supply-schedule
# Run setup script (first time only)
chmod +x setup.sh
./setup.sh
# Start the MCP server
python3 server.py
Once the MCP server is running, call the cca-supply-schedule__generate_supply_schedule MCP tool with the collected parameters. The tool expects a JSON object:
{
"auction_blocks": 86400,
"prebid_blocks": 0
}
Replace the values with the actual auctionBlocks and prebidBlocks collected from the user.
If the MCP tool is unavailable, use the fallback Python algorithm directly (see Supply Schedule Configuration section).
Store: supplySchedule
After collecting all parameters and generating the supply schedule, display the complete JSON configuration in the CLI output:
{
"[chainId]": {
"token": "...",
"totalSupply": ...,
"currency": "...",
"tokensRecipient": "...",
"fundsRecipient": "...",
"startBlock": ...,
"endBlock": ...,
"claimBlock": ...,
"tickSpacing": ...,
"validationHook": "...",
"floorPrice": ...,
"requiredCurrencyRaised": ...,
"supplySchedule": [...]
}
}
Do NOT automatically create a file. Let the user copy the JSON or specify a filepath to save it.
Show the user a comprehensive formatted summary including:
Ask the user what they want to do:
script/auction-config.json)Q96 * ratio / 10^(tokenDecimals - currencyDecimals)roundedFloorPrice = (floorPrice // tickSpacing) * tickSpacingroundedFloorPrice % tickSpacing == 0Store these for quick reference:
const NETWORKS = {
1: {
name: 'Mainnet',
blockTime: 12,
rpc: 'https://ethereum-rpc.publicnode.com',
usdc: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
},
130: {
name: 'Unichain',
blockTime: 1,
rpc: 'https://mainnet.unichain.org',
usdc: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
},
1301: {
name: 'Unichain Sepolia (Testnet)',
blockTime: 2,
rpc: 'https://sepolia.unichain.org',
usdc: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
},
8453: {
name: 'Base',
blockTime: 2,
rpc: 'https://mainnet.base.org',
usdc: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
},
42161: {
name: 'Arbitrum',
blockTime: 2,
rpc: 'https://arb1.arbitrum.io/rpc',
usdc: '0xaf88d065e77c8cc2239327c5edb3a432268e5831',
},
11155111: {
name: 'Sepolia',
blockTime: 12,
rpc: 'https://ethereum-sepolia-rpc.publicnode.com',
usdc: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
},
};
// Q96 = 2^96 (JavaScript BigInt notation)
const Q96 = 79228162514264337593543950336n;
Python equivalent:
# Q96 = 2**96
Q96 = 79228162514264337593543950336
CCA (Continuous Clearing Auction) is a novel auction mechanism that generalizes the uniform-price auction into continuous time. It provides fair price discovery for bootstrapping initial liquidity while eliminating timing games and encouraging early participation.
Key features:
| Task... | Use This Section |
|---|---|
| Configure auction parameters | Configuration Guide |
| Generate supply schedule | Supply Schedule Configuration |
| Understand auction mechanics | Technical Overview |
CCA auctions are configured through the AuctionParameters struct:
struct AuctionParameters {
address currency; // Token to raise funds in (address(0) for ETH)
address tokensRecipient; // Address to receive leftover tokens
address fundsRecipient; // Address to receive all raised funds
uint64 startBlock; // Block when auction starts
uint64 endBlock; // Block when auction ends
uint64 claimBlock; // Block when tokens can be claimed
uint256 tickSpacing; // Fixed granularity for prices (Q96)
address validationHook; // Optional hook (use 0x0 if none)
uint256 floorPrice; // Starting floor price (Q96)
uint128 requiredCurrencyRaised; // Minimum funds to graduate
bytes auctionStepsData; // Packed supply issuance schedule
}
Create a JSON configuration file (e.g., script/auction-config.json):
{
"1": {
"token": "0x...",
"totalSupply": 1e29,
"currency": "0x0000000000000000000000000000000000000000",
"tokensRecipient": "0x...",
"fundsRecipient": "0x...",
"startBlock": 24321000,
"endBlock": 24327001,
"claimBlock": 24327001,
"tickSpacing": 79228162514264337593543950,
"validationHook": "0x0000000000000000000000000000000000000000",
"floorPrice": 7922816251426433759354395000,
"requiredCurrencyRaised": 0,
"supplySchedule": [
{ "mps": 1000, "blockDelta": 6000 },
{ "mps": 4000000, "blockDelta": 1 }
]
}
}
| Parameter | Type | Description |
|---|---|---|
token | address | Token being auctioned |
totalSupply | number | Total tokens to auction (wei/smallest unit) |
currency | address | Purchase token (USDC, etc.) or address(0) for ETH |
tokensRecipient | address | Where unsold tokens go |
fundsRecipient | address | Where raised funds go |
| Parameter | Type | Description | Constraint |
|---|---|---|---|
startBlock | number | When auction starts | startBlock < endBlock |
endBlock | number | When auction ends | endBlock <= claimBlock |
claimBlock | number | When tokens can be claimed | claimBlock >= endBlock |
Block times by network:
| Parameter | Type | Description |
|---|---|---|
floorPrice | number | Minimum price (Q96 format) |
tickSpacing | number | Price tick increment (Q96 format) |
validationHook | address | Optional validation contract (use 0x0 if none) |
requiredCurrencyRaised | number | Minimum funds needed (0 if no minimum) |
| Parameter | Type | Description |
|---|---|---|
supplySchedule | array | Array of {mps, blockDelta} objects |
CCA uses Q96 fixed-point format for precise pricing. The base value 2^96 (79228162514264337593543950336) represents a 1:1 price ratio.
CRITICAL: Account for decimal differences between token and currency.
# Base value for 1:1 ratio
Q96 = 79228162514264337593543950336
# Formula: Q96 * (human price ratio) / 10^(token_decimals - currency_decimals)
# Example 1: USDC (6 decimals) per 18-decimal token at $0.10 ratio
token_decimals = 18
currency_decimals = 6 # USDC has 6 decimals
decimal_adjustment = 10 ** (token_decimals - currency_decimals) # 10^12
floorPrice = Q96 * 0.1 / decimal_adjustment
# Result: 7922816251426433759354395 (approximately)
# Example 2: Native ETH (18 decimals) per 18-decimal token at 0.1 ratio
token_decimals = 18
currency_decimals = 18 # Native ETH has 18 decimals
decimal_adjustment = 10 ** (18 - 18) # 10^0 = 1
floorPrice = Q96 * 0.1 / 1
# Result: 7922816251426433759354395034
Key Point: USDC has 6 decimals on all networks, so you must divide by 10^12 when using USDC with 18-decimal tokens.
Tick spacing governs where bids can be placed. Choose AT LEAST 1 basis point of the floor price. 1% or 10% is also reasonable.
# Example: 1% of floor price
tickSpacing = int(floorPrice * 0.01)
# For floorPrice = 7922816251426433759354395000
# Result: 79228162514264337593543950
Floor price MUST be evenly divisible by tick spacing. Round DOWN to ensure exact divisibility:
# Calculate tick spacing first
tickSpacing = int(floorPrice * 0.01) # 1% of floor price
# Round floor price DOWN to be evenly divisible
roundedFloorPrice = (floorPrice // tickSpacing) * tickSpacing
# VERIFY divisibility (must be True)
assert roundedFloorPrice % tickSpacing == 0, "Floor price must be divisible by tick spacing!"
Example:
Q96 = 79228162514264337593543950336
raw_floor_price = int(Q96 * 0.0001) # 0.0001 ETH per token
# Result: 7922816251426434139029504
tick_spacing = int(raw_floor_price * 0.01) # 1%
# Result: 79228162514264350785536
rounded_floor_price = (raw_floor_price // tick_spacing) * tick_spacing
# Result: 7843588088912170727768064
# Verify: 7843588088912170727768064 / 79228162514264350785536 = 99 (exact)
# Remainder: 0 ✓
Warning: Setting too small of a tick spacing will make the auction extremely gas inefficient and can result in DoS attacks.
Supply schedules use MPS = 1e7 (10 million), where each unit represents one thousandth of a basis point.
The supply schedule defines the token issuance rate over time. Each step contains:
mps: Tokens released per block (in mps units)blockDelta: Number of blocks this rate appliesThe plugin includes an MCP server that generates supply schedules using a normalized convex curve with the following properties:
Use the MCP tool generate_supply_schedule to generate this standard distribution:
MCP Tool Call:
{
"auction_blocks": 86400,
"prebid_blocks": 0
}
The algorithm automatically calculates:
Base uses 2s blocks, so 2 days = 86400 blocks.
Call generate_supply_schedule with:
{
"auction_blocks": 86400,
"prebid_blocks": 0
}
Output (normalized convex distribution):
{
"schedule": [
{ "mps": 54, "blockDelta": 10894 },
{ "mps": 68, "blockDelta": 8517 },
{ "mps": 75, "blockDelta": 7803 },
{ "mps": 79, "blockDelta": 7373 },
{ "mps": 83, "blockDelta": 7068 },
{ "mps": 85, "blockDelta": 6835 },
{ "mps": 88, "blockDelta": 6647 },
{ "mps": 90, "blockDelta": 6490 },
{ "mps": 92, "blockDelta": 6356 },
{ "mps": 94, "blockDelta": 6238 },
{ "mps": 95, "blockDelta": 6136 },
{ "mps": 97, "blockDelta": 6043 },
{ "mps": 2988006, "blockDelta": 1 }
],
"auction_blocks": 86400,
"prebid_blocks": 0,
"total_phases": 13,
"summary": {
"total_mps": 10000000,
"target_mps": 10000000,
"final_block_mps": 2988006,
"final_block_percentage": 29.88,
"num_steps": 12,
"alpha": 1.2,
"main_supply_pct": 70.0,
"step_tokens_pct": 5.8333
}
}
Notice:
Add a prebid period where no tokens are released (mps=0). The prebid is prepended to the schedule.
Call generate_supply_schedule with:
{
"auction_blocks": 86400,
"prebid_blocks": 43200
}
Output:
{
"schedule": [
{ "mps": 0, "blockDelta": 43200 },
{ "mps": 54, "blockDelta": 10894 },
{ "mps": 68, "blockDelta": 8517 },
...
{ "mps": 2988006, "blockDelta": 1 }
],
"auction_blocks": 86400,
"prebid_blocks": 43200,
"total_phases": 14,
"summary": {
"total_mps": 10000000,
"target_mps": 10000000,
"final_block_mps": 2988006,
"final_block_percentage": 29.88,
"num_steps": 12,
"alpha": 1.2,
"main_supply_pct": 70.0,
"step_tokens_pct": 5.8333
}
}
Notice: The prebid phase is simply prepended with mps: 0. The auction portion still uses the same normalized convex distribution.
For custom distribution, manually define the schedule:
{
"supplySchedule": [
{ "mps": 100, "blockDelta": 5000 },
{ "mps": 200, "blockDelta": 5000 },
{ "mps": 500, "blockDelta": 4400 }
]
}
Important: The last block should sell a significant amount of tokens (typically 30%+) to prevent price manipulation.
After generating a supply schedule, it must be encoded into a bytes format for the onchain AuctionParameters struct. The encoding packs each {mps, blockDelta} element into a uint64.
For each element in the supply schedule:
mps value (left padded)blockDelta value (left padded)encodePacked (concatenate bytes)0x prefix// Solidity equivalent
uint64 packed = (uint64(mps) << 40) | uint64(blockDelta);
bytes memory auctionStepsData = abi.encodePacked(packed1, packed2, ...);
Use the encode_supply_schedule MCP tool to encode a supply schedule:
Input:
{
"schedule": [
{ "mps": 0, "blockDelta": 43200 },
{ "mps": 54, "blockDelta": 10894 },
{ "mps": 68, "blockDelta": 8517 }
]
}
Output:
{
"encoded": "0x0000000000a8c00000003600002aa60000004400002145...",
"length_bytes": 112,
"num_elements": 14
}
def encode_supply_schedule(schedule):
"""Encode supply schedule to bytes."""
encoded_bytes = b''
for item in schedule:
mps = item['mps']
block_delta = item['blockDelta']
# Validate bounds
assert mps < 2**24, f"mps {mps} exceeds 24-bit max"
assert block_delta < 2**40, f"blockDelta {block_delta} exceeds 40-bit max"
# Pack into uint64: mps (24 bits) << 40 | blockDelta (40 bits)
packed = (mps << 40) | block_delta
# Convert to 8 bytes (big-endian)
encoded_bytes += packed.to_bytes(8, byteorder='big')
return '0x' + encoded_bytes.hex()
# Example
schedule = [
{"mps": 0, "blockDelta": 43200},
{"mps": 54, "blockDelta": 10894}
]
encoded = encode_supply_schedule(schedule)
print(encoded) # 0x0000000000a8c00000003600002aa6
When using the configurator skill:
generate_supply_schedule MCP toolencode_supply_schedule MCP toolauctionStepsData parameterThe encoded bytes string is what gets passed to the Factory's initializeDistribution function as part of the configData parameter.
Use public RPCs to fetch current block for startBlock configuration:
| Network | RPC URL |
|---|---|
| Mainnet | https://ethereum-rpc.publicnode.com |
| Unichain | https://unichain-rpc.publicnode.com |
| Base | https://mainnet.base.org |
| Arbitrum | https://arb1.arbitrum.io/rpc |
| Sepolia | https://ethereum-sepolia-rpc.publicnode.com |
curl -X POST "https://mainnet.base.org" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_blockNumber",
"params": [],
"id": 1
}'
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x123abc"
}
Convert hex to decimal for block number.
Before generating configuration, ensure:
startBlock < endBlock <= claimBlockdocs/TechnicalDocumentation.md in repodocs/DeploymentGuide.md in repodocs/assets/whitepaper.pdf in repodocs/audits/README.md in repo