关于 deployer
面向 Claude Code 的 CCA 部署技能,用于通过 ContinuousClearingAuctionFactory 部署拍卖合约,并使用 CREATE2 获得跨链一致的地址。流程包含配置校验、用户确认、命令生成和密钥安全提示。
主要功能
- 01通过工厂部署拍卖实例
- 02CREATE2 确定性地址部署
- 03配置与输入校验
- 04生成 Foundry 部署命令
- 05部署后步骤与区块浏览器验证
- 06部署安全和密钥管理指引
适用场景
- 01根据配置部署 CCA 拍卖实例
- 02部署前校验地址、数值和拍卖参数
- 03准备测试网部署及合约验证命令
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ uniswap/ uniswap-ai --skill deployer安装后在 Claude Code 中输入/deployer确认已加载。
详细介绍
CCA 部署
使用 ContinuousClearingAuctionFactory 通过 CREATE2 部署 Continuous Clearing Auction(CCA)智能合约,以确保在不同链上的地址一致。
运行时兼容性: 此技能使用
AskUserQuestion进行交互式提问。如果你的运行时不支持AskUserQuestion,请改用自然语言对话收集相同的参数。
Claude Code 使用说明
用户调用此技能时,请在适当提示安全警告并进行验证后,引导其完成 CCA 部署流程。
部署前要求
开始部署前,你 MUST:
- 展示教育用途免责声明并获得用户确认
- 如果用户提供了配置文件,则验证该文件
- 核实目标网络的工厂地址
- 与用户确认部署参数
部署工作流
- 展示教育用途免责声明(必需)
- 加载或请求配置
- 验证配置
- 展示部署计划
- 获取用户确认
- 提供部署命令
- 部署后步骤
⚠️ 教育用途免责声明
重要:开始部署前,你必须确认已知悉以下内容:
本工具和所有部署说明均仅供教育用途。AI 生成的部署命令可能包含错误或安全漏洞。
你必须:
- ✅ 部署前仔细检查所有配置
- ✅ 确认所有参数(地址、定价、时间表)均正确
- ✅ 部署到主网前,先在测试网上进行测试
- ✅ 使用真实资金部署前,先审计你的合约
请使用 AskUserQuestion 确认用户已知悉这些警告,然后再继续部署步骤。
输入验证规则
在将任何用户提供的值插入 forge/cast 命令或部署脚本之前:
- 以太坊地址:必须匹配
^0x[a-fA-F0-9]{40}$,否则拒绝 - 链 ID:必须属于受支持的链列表(1、130、1301、8453、42161、11155111)
- 数值(供应量、价格、区块、链 ID):必须为非负数,且匹配
^[0-9]+\.?[0-9]*$ - RPC 端点 URL(
$RPC_URL):必须使用https://协议,且不得包含下方规则中列出的任何元字符或空白字符。此值不来自配置文件,而是在引导过程中由用户提供,并会传递给下方每条forge script、cast send和forge verify-contract命令中的--rpc-url。不能以“这是用户提供的,而不是来自配置文件”为由豁免验证。首次收到该值时就要验证,并将其赋给$RPC_URL,之后只能引用该变量;不得在后续命令中重新读取原始 URL。如果端点 URL 需要查询字符串(例如提供商 API 密钥),应在命令之外导出到环境变量,并通过$RPC_URL引用,绝不能直接粘贴到命令文本中。 - 拒绝任何包含 shell 元字符或空白字符的输入:
;、|、&、$、`、(、)、>、<、\、'、"、空格、制表符、换行符。$(...)和反引号即使位于双引号内也会执行,而空格会将一个值拆分成多个forge/cast标志,因此二者都绝不能直接插入命令。应中止操作,并说明失败的值及对应字符,而不是删除违规字符后继续。 - 绝不要未经验证就将原始用户输入直接传递给 shell 命令
- 生成命令时,始终为每个插值项加上双引号(写作
--rpc-url "$RPC_URL",绝不能不加引号)。引号是第二层防护,而非第一层:先验证该值,再将其传递给命令,并且还要加上引号。
⚠️ 权限安全
请勿在 Claude Code 设置中自动批准 Bash(forge:*) 或 Bash(cast:*)。 对会消耗 gas 或广播交易的命令,始终要求逐次批准。.claude/hooks/ 中的 PreToolUse 钩子会提供程序化验证,作为安全保障,但每条命令都由用户批准才是主要控制措施。
🔐 私钥安全
重要:安全处理私钥对于安全部署至关重要。
⚠️ 切勿执行以下操作
- ❌ 切勿将私钥存储在 git 仓库或配置文件中
- ❌ 切勿将私钥直接粘贴到命令行中(会显示在 shell 历史记录中)
- ❌ 切勿分享私钥或将其存储在共享环境中
- ❌ 切勿在不可信的计算机上使用主网私钥
- ❌ 切勿使用
--private-key标志(已被 PreToolUse 钩子拦截)
✅ 推荐做法
选项 1:硬件钱包(最安全)
使用 Ledger 或 Trezor 硬件钱包,并添加 --ledger 标志:
forge script script/Example.s.sol:ExampleScript \
--rpc-url "$RPC_URL" \
--broadcast \
--ledger
选项 2:加密密钥库
使用 cast wallet import 创建加密密钥库:
# Import private key to encrypted keystore (one-time setup)
cast wallet import deployer --interactive
# Use keystore for deployment
forge script script/Example.s.sol:ExampleScript \
--rpc-url "$RPC_URL" \
--broadcast \
--account deployer \
--sender "$DEPLOYER_ADDRESS"
选项 3:环境变量(仅用于测试)
如果使用环境变量,请确保:
- 在安全的
.env文件中设置(切勿提交到 git) - 通过
source .env或dotenv加载 - 仅在可信且安全的计算机上使用
- 开发时使用测试网密钥
示例:
# .env file (add to .gitignore)
PRIVATE_KEY=0x...
RPC_URL=https://...
# Load environment
source .env
# Deploy (use encrypted keystore instead of --private-key)
cast wallet import deployer --interactive
forge script ... --account deployer --sender "$DEPLOYER_ADDRESS"
优先使用测试网
始终先在测试网上测试,再部署到主网:
- Sepolia(测试网):从水龙头获取免费 ETH
- Base Sepolia:用于在 Base 上测试的免费 ETH
- 在测试网上部署并验证完整工作流
- 经过充分测试后再部署到主网
部署指南
工厂部署
CCA 实例通过 ContinuousClearingAuctionFactory 合约部署,该合约使用 CREATE2 在不同链上生成一致的地址。
工厂地址
| 版本 | 地址 | 状态 |
|---|---|---|
| v1.1.0 | 0xCCccCcCAE7503Cac057829BF2811De42E16e0bD5 | 推荐 |
部署拍卖实例
步骤 0:克隆 CCA 仓库
如果本地尚无 CCA 合约,请克隆仓库并安装依赖项:
git clone https://github.com/Uniswap/continuous-clearing-auction.git
cd continuous-clearing-auction
forge install
这样即可使用后续步骤中提到的部署脚本、合约 ABI 和测试辅助工具。
步骤 1:准备配置
确保你有有效的配置文件(通过 configurator 技能生成或手动创建)。
配置文件结构示例:
{
"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 }
]
}
}
步骤 2:验证配置
部署前,请确认配置通过所有验证规则(参见“验证规则”部分)。
步骤 3:通过工厂部署
工厂提供简单的接口:
function initializeDistribution(
address token,
uint256 amount,
bytes calldata configData,
bytes32 salt
) external returns (IDistributionContract);
其中:
token:待出售代币的地址amount:拍卖中待出售的代币数量configData:经过 ABI 编码的AuctionParameters结构体salt:可选的 bytes32 值,用于靓号地址挖掘
步骤 3.5:将配置编码为 configData
工厂的 initializeDistribution 需要 configData 为经过 ABI 编码的 AuctionParameters。将 JSON 配置转换为编码后的字节:
使用 cast(Foundry CLI):
# Encode the AuctionParameters struct
cast abi-encode "initializeDistribution(address,uint256,bytes,bytes32)" \
"$TOKEN_ADDRESS" \
"$TOTAL_SUPPLY" \
"$(cast abi-encode "(address,address,address,uint64,uint64,uint64,uint256,address,uint256,uint128,bytes)" \
"$CURRENCY" \
"$TOKENS_RECIPIENT" \
"$FUNDS_RECIPIENT" \
"$START_BLOCK" \
"$END_BLOCK" \
"$CLAIM_BLOCK" \
"$TICK_SPACING" \
"$VALIDATION_HOOK" \
"$FLOOR_PRICE" \
"$REQUIRED_CURRENCY_RAISED" \
"$ENCODED_SUPPLY_SCHEDULE")" \
"0x0000000000000000000000000000000000000000000000000000000000000000"
使用 Foundry 脚本:
// script/DeployAuction.s.sol
pragma solidity ^0.8.24;
import "forge-std/Script.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface ICCAFactory {
function initializeDistribution(
address token,
uint256 amount,
bytes calldata configData,
bytes32 salt
) external returns (address);
}
contract DeployAuction is Script {
function run() external {
// Load config values
address token = vm.envAddress("TOKEN");
uint256 amount = vm.envUint("TOTAL_SUPPLY");
// Encode AuctionParameters
bytes memory configData = abi.encode(
vm.envAddress("CURRENCY"),
vm.envAddress("TOKENS_RECIPIENT"),
vm.envAddress("FUNDS_RECIPIENT"),
uint64(vm.envUint("START_BLOCK")),
uint64(vm.envUint("END_BLOCK")),
uint64(vm.envUint("CLAIM_BLOCK")),
vm.envUint("TICK_SPACING"),
vm.envAddress("VALIDATION_HOOK"),
vm.envUint("FLOOR_PRICE"),
uint128(vm.envUint("REQUIRED_CURRENCY_RAISED")),
vm.envBytes("ENCODED_SUPPLY_SCHEDULE")
);
vm.startBroadcast();
// Approve token transfer to factory
IERC20(token).approve(
0xCCccCcCAE7503Cac057829BF2811De42E16e0bD5,
amount
);
// Deploy auction
address auction = ICCAFactory(
0xCCccCcCAE7503Cac057829BF2811De42E16e0bD5
).initializeDistribution(
token,
amount,
configData,
bytes32(0) // salt
);
vm.stopBroadcast();
console.log("Auction deployed at:", auction);
}
}
**重要:**调用 initializeDistribution 前,必须先批准工厂转移代币。工厂会将 amount 数量的代币从你的地址转移到新创建的拍卖合约。
步骤 4:使用 Foundry 脚本
# Deploy factory (if needed on new network)
forge script script/deploy/DeployContinuousAuctionFactory.s.sol:DeployContinuousAuctionFactoryScript \
--rpc-url "$RPC_URL" \
--broadcast \
--account deployer --sender "$DEPLOYER_ADDRESS"
# Deploy auction instance
forge script script/Example.s.sol:ExampleScript \
--rpc-url "$RPC_URL" \
--broadcast \
--account deployer --sender "$DEPLOYER_ADDRESS"
步骤 5:部署后
部署后,必须调用 onTokensReceived(),通知拍卖合约代币已转入:
cast send "$AUCTION_ADDRESS" "onTokensReceived()" --rpc-url "$RPC_URL" --account deployer --sender "$DEPLOYER_ADDRESS"
这是拍卖接受竞价前的必要前置条件。
替代方案:通过构造函数部署
你也可以直接通过构造函数部署:
constructor(
address token,
uint128 amount,
AuctionParameters memory parameters
) {}
此方式无需 salt 参数,但无法利用 CREATE2 的确定性地址特性。
在区块浏览器上验证
生成标准 JSON 输入以供验证:
forge verify-contract "$AUCTION_ADDRESS" \
src/ContinuousClearingAuction.sol:ContinuousClearingAuction \
--rpc-url "$RPC_URL" \
--show-standard-json-input > standard-json-input.json
将此文件上传到区块浏览器进行验证。
验证规则
部署前,请确保:
- 区块约束:
startBlock < endBlock <= claimBlock - 有效地址:所有地址都是有效的 Ethereum 地址(0x + 40 个十六进制字符)
- 非负值:所有数值均 >= 0
- 底价对齐:底价必须是 tick 间距的倍数
- Tick 间距:至少为底价的 1 个基点(建议为 1%、10%)
- 供应计划:最后一个区块会售出大量代币(约 30% 以上)
- 总供应量上限:最高为 1e30 wei(1 万亿个 18 位小数代币)
- 不支持 FoT 代币:不支持转账收取手续费的代币
- 最少小数位数:请勿使用小于 6 位小数的代币
技术概览
Q96 定点数运算
拍卖使用 Q96 定点数运算:
library FixedPoint96 {
uint8 internal constant RESOLUTION = 96;
uint256 internal constant Q96 = 0x1000000000000000000000000; // 2^96
}
- 价格:用于表示小数价格比的 Q96 定点数
- 需求:按 Q96 缩放的货币金额
拍卖步骤(供应量发行)
步骤会打包成字节,其中每个步骤都是一个 uint64:
- 前 24 位:
mps(以 MPS 表示的每区块发行速率) - 后 40 位:
blockDelta(区块数量)
function parse(bytes8 data) internal pure returns (uint24 mps, uint40 blockDelta) {
mps = uint24(bytes3(data));
blockDelta = uint40(uint64(data));
}
数据会部署到外部 SSTORE2 合约,以降低读取成本。
主要合约函数
submitBid()
用户提交出价时需提供:
maxPrice:愿意支付的最高价格(Q96)amount:出价的货币金额owner:接收代币/退款的地址prevTickPrice:用于优化 Gas 的提示hookData:供验证钩子使用的可选数据
checkpoint()
每个区块有新出价时,都会为拍卖执行一次 checkpoint。Checkpoint 用于确定代币分配。
exitBid() / exitPartiallyFilledBid()
当出价被超过或拍卖结束时,可退出出价(仅在毕业后)。
isGraduated()
当 currencyRaised >= requiredCurrencyRaised 时返回 true。毕业前,任何出价都无法退出。
claimTokens()
用户可在 claimBlock 之后领取已购买的代币(仅限已毕业的拍卖)。
sweepCurrency() / sweepUnsoldTokens()
拍卖结束后:
sweepCurrency():提取募集的货币(仅限已毕业的拍卖)sweepUnsoldTokens():提取未售出的代币
支持的链
CCA 已部署到部分 EVM 链上的规范地址:
| 链 ID | 网络 | 区块时间 |
|---|---|---|
| 1 | 主网 | 12s |
| 130 | Unichain | 1s |
| 1301 | Unichain Sepolia | 2s |
| 8453 | Base | 2s |
| 42161 | Arbitrum | 2s |
| 11155111 | Sepolia | 12s |
故障排除
常见问题
| 问题 | 解决方案 |
|---|---|
| "Invalid block sequence" | 确保 startBlock < endBlock <= claimBlock |
| "Floor price not aligned" | 将底价调整为 tick 间距的倍数 |
| "Tick spacing too small" | 使用至少为底价 1% 的 tick 间距 |
| "Total supply too large" | 最高为 1e30 wei(1 万亿个 18 位小数代币) |
| "Gas inefficiency" | 增大 tick 间距 |
| "Invalid address" | 确认地址为 42 个字符且以 0x 开头 |
验证清单
部署前:
- 区块序列有效(start < end <= claim)
- 底价是 tick 间距的倍数
- Tick 间距 >= 底价的 1%
- 所有地址都是有效的 Ethereum 地址
- 总供应量 <= 1e30 wei
- 货币的价值高于代币
- 区块时间与网络相符(主网为 12s,L2 为 2s)
- 已设置接收方地址(不是占位符)
- 货币地址与网络相符
- 最后一个供应量步骤售出约 30% 或更多的代币
- 未使用转账收费代币
- 代币精度 >= 6
- 部署后调用了
onTokensReceived()
其他资源
- CCA 仓库:https://github.com/Uniswap/continuous-clearing-auction
- 技术文档:参阅仓库中的
docs/TechnicalDocumentation.md - 部署指南:参阅仓库中的
docs/DeploymentGuide.md - 白皮书:参阅仓库中的
docs/assets/whitepaper.pdf - 审计:参阅仓库中的
docs/audits/README.md - Uniswap 文档:https://docs.uniswap.org/contracts/liquidity-launchpad/CCA
- 漏洞赏金:https://cantina.xyz/code/f9df94db-c7b1-434b-bb06-d1360abdd1be/overview