mcpField
D
deployer

deployer

github.com/uniswap开发2361.1k 次安装2 天前更新
代码仓库

关于 deployer

面向 Claude Code 的 CCA 部署技能,用于通过 ContinuousClearingAuctionFactory 部署拍卖合约,并使用 CREATE2 获得跨链一致的地址。流程包含配置校验、用户确认、命令生成和密钥安全提示。

主要功能

  1. 01通过工厂部署拍卖实例
  2. 02CREATE2 确定性地址部署
  3. 03配置与输入校验
  4. 04生成 Foundry 部署命令
  5. 05部署后步骤与区块浏览器验证
  6. 06部署安全和密钥管理指引

适用场景

  1. 01根据配置部署 CCA 拍卖实例
  2. 02部署前校验地址、数值和拍卖参数
  3. 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:

  1. 展示教育用途免责声明并获得用户确认
  2. 如果用户提供了配置文件,则验证该文件
  3. 核实目标网络的工厂地址
  4. 与用户确认部署参数
部署工作流
  1. 展示教育用途免责声明(必需)
  2. 加载或请求配置
  3. 验证配置
  4. 展示部署计划
  5. 获取用户确认
  6. 提供部署命令
  7. 部署后步骤

⚠️ 教育用途免责声明

重要:开始部署前,你必须确认已知悉以下内容:

本工具和所有部署说明均仅供教育用途。AI 生成的部署命令可能包含错误或安全漏洞。

你必须:

  1. ✅ 部署前仔细检查所有配置
  2. ✅ 确认所有参数(地址、定价、时间表)均正确
  3. ✅ 部署到主网前,先在测试网上进行测试
  4. ✅ 使用真实资金部署前,先审计你的合约

请使用 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.00xCCccCcCAE7503Cac057829BF2811De42E16e0bD5推荐
部署拍卖实例
步骤 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

将此文件上传到区块浏览器进行验证。


验证规则

部署前,请确保:

  1. 区块约束:startBlock < endBlock <= claimBlock
  2. 有效地址:所有地址都是有效的 Ethereum 地址(0x + 40 个十六进制字符)
  3. 非负值:所有数值均 >= 0
  4. 底价对齐:底价必须是 tick 间距的倍数
  5. Tick 间距:至少为底价的 1 个基点(建议为 1%、10%)
  6. 供应计划:最后一个区块会售出大量代币(约 30% 以上)
  7. 总供应量上限:最高为 1e30 wei(1 万亿个 18 位小数代币)
  8. 不支持 FoT 代币:不支持转账收取手续费的代币
  9. 最少小数位数:请勿使用小于 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
130Unichain1s
1301Unichain Sepolia2s
8453Base2s
42161Arbitrum2s
11155111Sepolia12s

故障排除

常见问题
问题解决方案
"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()

其他资源

评论

支持 Markdown