Covers SHEEP CHOICE, BRIDGE and any-token ZAP, the fee split by venue, and the self-hosting path for anyone running their own build. The contract map is the reference the source comments already cite by path — every address in it is Blockscout-verified, so it belongs next to the code that calls them rather than in a private tree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
31 KiB
UP33 合约地图 & 用户全流程(前端构建基准)
目标:把 UP33(
up.,Robinhood Chain 上的 Velodrome V2 + Slipstream ve(3,3) DEX)的每个用户动作 精确映射到链上合约调用,作为自建前端的工程参考。 所有函数签名均已通过 Robinhood Chain Blockscout 的已验证源码核对(编译器 0.8.19)。
- Chain: Robinhood Chain,
chainId = 4663,gas token = ETH - RPC:
.env里的RPC(Alchemyrobinhood-mainnet);官方公开 RPChttps://rpc.mainnet.chain.robinhood.com - Explorer: https://robinhoodchain.blockscout.com
- 官方子图 (Goldsky):
- CL/v3:
https://api.goldsky.com/api/public/project_cmhef02640198x7p2cz2w70u8/subgraphs/up-robinhood-v3-mainnet/0.1.1/gn - Gauges:
https://api.goldsky.com/api/public/project_cmhef02640198x7p2cz2w70u8/subgraphs/up-robinhood-gauges-mainnet/0.1.0/gn
- CL/v3:
0. 实时状态(2026-07-16 04:03 UTC,block 10954835)—— 协议已全面上线
关键:今天 00:00 UTC 发生了第一次 epoch flip,世界与 07-13 研究时完全不同:
| 指标 | 07-13(上线前) | 07-16(现在) | 含义 |
|---|---|---|---|
Minter.epochCount |
0 | 1 | 第一期排放已发生 |
Minter.weekly |
10,000,000 | 10,300,000 UP | +3% 增长期已启动 |
UP.totalSupply |
500,000,000 | 510,526,315 | +10M 排放 + 526k team share |
Voter.capMode |
Disabled | Enforced (2) | gauge cap 现在真的在生效 |
Voter.totalWeight |
~7,828 | ~100,183,267 veUP | 创世大锁已投票 |
| 池 / gauge | 3 v2 + 15 CL / 15 | 6 v2 + 21 CL / 22(全 alive) | 新增 UPHOOD/DOWN/WISHBONE/ARROW/VEX/PONS/RUGD 等 |
| gauge streaming | rewardRate=0 | rewardRate>0,periodFinish=2026-07-23 00:00 UTC | UP 正在按秒流向质押 LP |
epoch_next = 2026-07-23T00:00:00Z(下一次 flip / 投票截止)。- 池和 gauge 列表会持续变化,前端必须动态枚举(见 §5),不要硬编码。
1. 合约清单
核心合约(Blockscout 已验证)
| 角色 | 地址 | 说明 |
|---|---|---|
| UP token | 0x57C0E45cB534413D1C20A4240955d6bB250BB4F1 |
排放/治理代币,ERC-20,18 dec |
| veUP (VotingEscrow) | 0x5d321dE36F0bf98D92b291280514F3878582B7B6 |
锁仓 ERC-721 NFT,投票权 + fee 权 |
| Voter | 0x7F749fDD351C1Ceed82d76d7699CB631Eb8332a7 |
投票、gauge 注册、claim 分发、cap |
| Minter | 0x912EC7A90e8C9829eE0e0f6a4Db5270776Fc3Da5 |
每周排放、team share、rebase |
| RewardsDistributor | 0xC75C2cC04f65d7C1e541474C8ce927b544Fff849 |
rebase 分发(当前 rate=0) |
| v2 PoolFactory | 0xFA5429AEBa338BEa2BFcc1b9a889862Ee395bc28 |
v2 stable/volatile 池 |
| v2 Router | 0xf5198743240fAC98db71868F34c70139b1eb0474 |
v2 加/减流动性、swap、zap |
| v2 GaugeFactory | 0x6df2E93cc56E96330dce2B60e34D88AF3EF23580 |
部署 v2 gauge |
| CLFactory | 0x1ac9dB4a2608ba45D6127B1737949b51Bb54B7F3 |
Slipstream CL 池 |
| CL PositionManager (NFT) | 0x07F44c47743A2f36414A82b9F558ECFCf0EEdCEf |
CL 仓位 NFT(mint/increase/decrease/collect) |
| CL SwapRouter | 0xC062b870E813fcA720f1e002c234369Ab3aB9415 |
CL swap |
| CL Quoter | 0x03983AB2C057a2eac211ff01738a1e49ff325B49 |
CL 报价(revert 式,eth_call) |
| WETH | 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73 |
标准 WETH9,18 dec |
| Multicall3 | 0xcA11bde05977b3631167028862bE2a173976CA11 |
标准地址,批量读 |
第三方:KyberSwap(只读美元估值)
- Aggregator API base:
https://aggregator-api.kyberswap.com,chain slugrobinhood(对应 chainId 4663)。 - 前端只读取 fee-free routes 的
amountOutUsd,用于 UP 奖励、APR、仓位与 Add-LP 模拟的美元展示。 - Kyber 价格不进入 MARKET/ZAP route、Zap sizing、slippage、minimum 或交易执行; 代码不暴露 Kyber transaction builder。
- 相关参数只有
KYBERSWAP_AGGREGATOR_API_BASE_URL/KYBERSWAP_CHAIN。
Fee / cap 模块(治理可替换)
| 角色 | 地址 | 现状 |
|---|---|---|
| CL SwapFeeModule | 0x0F19e5EcA92523F728A43F5C5BfdFb1561B34272 |
静态 per-pool override(非文档宣传的动态费率) |
| CL UnstakedFeeModule | 0xbb7A7015fD9744dF83d688D5C71C9efE613b976d |
默认对未质押 CL LP 的 fee 抽 10%(可 per-pool 设 0–50%) |
| GaugeCapController | 0x5739B0FcF359DF7a386C0fDD27464Dd15eE1687D |
源码未验证;capMode 现已 Enforced |
治理
- Genesis 2/3 Safe
0x0eEA30aBa3f07abFA20E4b544F55e0f917d9DFd8:持有 4 个创世 permanent veNFT(#1–#4,约 437.5M 票权)、v2/CL fee manager、pauser、governor、emergency council、epoch governor、cap controller owner。高度集中。 - Minter
team()仍是单 EOA0x85Fb9f9BC8a44663A234AdB4CfE4E1459C096651(pendingTeam已设为 Safe,尚未acceptTeam())。
2. 代币清单(实时,含小数位)
USDG 是 6 位小数,其余均 18 位 —— 前端所有金额换算必须按各自 decimals,切勿假设 18。
| Symbol | Address | dec |
|---|---|---|
| UP | 0x57c0e45cb534413d1c20a4240955d6bb250bb4f1 |
18 |
| WETH | 0x0bd7d308f8e1639fab988df18a8011f41eacad73 |
18 |
| USDG | 0x5fc5360d0400a0fd4f2af552add042d716f1d168 |
6 |
| VIRTUAL | 0xc6911796042b15d7fa4f6cde69e245ddcd3d9c31 |
18 |
| CASHCAT | 0x020bfc650a365f8bb26819deaabf3e21291018b4 |
18 |
| DIH | 0x17bb0c898254406b1ea2e8e99b0c263e26c9e4a4 |
18 |
| JUGGERNAUT | 0xd7321801caae694090694ff55a9323139f043b88 |
18 |
| TENDIES | 0x45242320dbb855eea8fd36804c6487e10e97fcf9 |
18 |
| UPHOOD | 0x62f1…30b63 / 0xa153…59ee(两个) |
18 |
| DOWN | 0x4cd75000eb7a5951abfd57fc7ea2ac8aa1622935 |
18 |
| WISHBONE | 0x77581054581b9c525e7dd7a0155de43867532d03 |
18 |
| VEX | 0x8ff92566f2e81bdd68edfaa8cde73942a723796b |
18 |
| ARROW | 0xf2915d1e3c1b0c769d0c756ec43f1c1f6c99cd03 |
18 |
| PONS | 0x39dbed3a2bd333467115de45665cc57f813c4571 |
18 |
| RUGD | 0xa07a181570a59d00fc5923552b04e5467dc87250 |
18 |
⚠️ 池里很多是新发/未验证 token(DIH、CASHCAT、RUGD…)。前端应把"资产风险"与"排放 APR"分开展示,不要把高票诱饵池当低风险机会。
3. ve(3,3) 价值流(一句话模型)
lock UP → veUP 投票给 gauge → 下一 epoch UP 排放按票重流向各 gauge
→ LP 质押到 gauge 赚 UP(放弃自己那份手续费)
→ 质押流动性产生的手续费 + 外部 incentives 归投票者
→ 领取 → 续锁/再投票
- 每 epoch = 7 天,周四 00:00 UTC flip。
- 本期票决定下期排放;本期 fee/incentive 在 flip 后可领。
- 质押 = 赚 UP、手续费给 voters;未质押 = 保留手续费、不赚 UP(二选一)。
- CL 只有价格在区间内才赚有效排放和手续费。
4. 合约接口速查(已验证签名)
4.1 ERC-20 / WETH(标准)
approve(address spender, uint256 amount)
allowance(address owner, address spender) -> uint256
balanceOf(address) -> uint256
WETH.deposit() payable // ETH -> WETH
WETH.withdraw(uint256 wad) // WETH -> ETH
4.2 veUP VotingEscrow — 锁仓
// 写
createLock(uint256 _value, uint256 _lockDuration) -> uint256 tokenId // duration 单位秒,最长 4y
createLockFor(uint256 _value, uint256 _lockDuration, address _to) -> uint256
increaseAmount(uint256 _tokenId, uint256 _value) // 加仓(需先 approve UP)
increaseUnlockTime(uint256 _tokenId, uint256 _lockDuration) // 延长
merge(uint256 _from, uint256 _to)
split(uint256 _from, uint256 _amount)
lockPermanent(uint256 _tokenId)
unlockPermanent(uint256 _tokenId)
withdraw(uint256 _tokenId) // 仅到期后
delegate(uint256 delegator, uint256 delegatee)
// 读
locked(uint256 _tokenId) -> (int128 amount, uint256 end, bool isPermanent)
balanceOfNFT(uint256 _tokenId) -> uint256 // 当前投票权
balanceOfNFTAt(uint256 _tokenId, uint256 _t) -> uint256
ownerOf(uint256 _tokenId) -> address
balanceOf(address _owner) -> uint256 // 用户持有的 veNFT 数量
tokenOfOwnerByIndex(address, uint256) -> uint256 // 枚举用户的 veNFT(ERC721Enumerable)
supply() -> uint256 // 锁定的 UP 总量
4.3 Voter — 投票、claim、gauge
// 写
vote(uint256 _tokenId, address[] _poolVote, uint256[] _weights) // 每 epoch 一次,最多 30 池
reset(uint256 _tokenId)
poke(uint256 _tokenId) // 按当前衰减刷新已投票权
claimRewards(address[] _gauges) // LP:领 UP 排放(等价 gauge.getReward)
claimFees(address[] _fees, address[][] _tokens, uint256 _tokenId) // voter:领手续费
claimBribes(address[] _bribes, address[][] _tokens, uint256 _tokenId) // voter:领 incentives
createGauge(address _poolFactory, address _pool) // 白名单 token 可 permissionless 建 gauge
distribute(address[] _gauges) // permissionless,把本期 UP 推进 gauge
// 读
length() -> uint256 // gauge/pool 数量
pools(uint256) -> address // 枚举 pool
gauges(address pool) -> address // pool -> gauge
poolForGauge(address gauge) -> address
isAlive(address gauge) -> bool
isGauge(address) -> bool
weights(address pool) -> uint256 // 池累计票重
totalWeight() -> uint256
usedWeights(uint256 tokenId) -> uint256 // 某 NFT 已用票权
votes(uint256 tokenId, address pool) -> uint256 // 某 NFT 投给某池的票
poolVote(uint256 tokenId, uint256 i) -> address // 某 NFT 投过的池列表
lastVoted(uint256 tokenId) -> uint256
gaugeToFees(address gauge) -> address // -> FeesVotingReward 地址
gaugeToBribe(address gauge) -> address // -> IncentiveVotingReward 地址
claimable(address gauge) -> uint256 // gauge 待分发 UP
epochStart/epochNext/epochVoteStart/epochVoteEnd(uint256 ts) -> uint256
capMode() -> uint8 // 0 Disabled / 1 ObserveOnly / 2 Enforced
4.4 Minter — 排放
updatePeriod() // permissionless;7 天到期后触发本期 mint + flip
nudge() // epoch governor 微调 tail rate ±1bp
weekly() / activePeriod() / epochCount() / tailEmissionRate() / teamRate() / rebaseRateBps()
4.5 v2 Router — v2 流动性 & swap
// 加/减流动性
addLiquidity(tokenA, tokenB, bool stable, amountADesired, amountBDesired, amountAMin, amountBMin, to, deadline)
addLiquidityETH(token, bool stable, amountTokenDesired, amountTokenMin, amountETHMin, to, deadline) payable
removeLiquidity(tokenA, tokenB, bool stable, liquidity, amountAMin, amountBMin, to, deadline)
removeLiquidityETH(token, bool stable, liquidity, amountTokenMin, amountETHMin, to, deadline)
// 报价(view)
quoteAddLiquidity(tokenA, tokenB, stable, _factory, amountADesired, amountBDesired) -> (amountA, amountB, liquidity)
quoteRemoveLiquidity(tokenA, tokenB, stable, _factory, liquidity) -> (amountA, amountB)
getReserves(tokenA, tokenB, stable, _factory) -> (reserveA, reserveB)
getAmountsOut(amountIn, Route[] routes) -> uint256[] // Route = (from,to,stable,factory)
poolFor(tokenA, tokenB, stable, _factory) -> address
defaultFactory() -> address // 传给上面 _factory 的默认值
// swap(Route[] = (address from, address to, bool stable, address factory))
swapExactTokensForTokens(amountIn, amountOutMin, Route[], to, deadline)
swapExactETHForTokens(amountOutMin, Route[], to, deadline) payable
swapExactTokensForETH(amountIn, amountOutMin, Route[], to, deadline)
zapIn(...) / zapOut(...) // 可选:一步 zap + 可选 stake
4.6 v2 PoolFactory / v2 Pool
Factory.getPool(tokenA, tokenB, bool stable) -> address // 找池
Factory.allPools(uint256) / allPoolsLength() // 枚举
Factory.getFee(address pool, bool stable) -> uint256 // 分母 10_000
Pool.getReserves() -> (r0, r1, blockTimestampLast)
Pool.token0()/token1()/stable()/totalSupply()
Pool.balanceOf(user) -> uint256 // 未质押 LP token 余额
Pool.claimable0(user)/claimable1(user) -> uint256 // 未质押 LP 待领手续费
Pool.claimFees() // 未质押 LP 领手续费
4.7 v2 Gauge(标准 Velodrome V2 Gauge;实例未单独验证,构建时用 eth_call 确认 selector)
deposit(uint256 _amount) // 质押 LP token(先 approve pool->gauge)
deposit(uint256 _amount, address _recipient)
withdraw(uint256 _amount) // 取回 LP token
getReward(address _account) // 领 UP 排放
earned(address _account) -> uint256 // 待领 UP
balanceOf(address) -> uint256 // 已质押 LP token
stakingToken() -> address // = pool 地址
rewardToken() -> address // = UP
rewardRate()/periodFinish()/left()/rewardRateByEpoch(uint256)
4.8 CLFactory / CL PositionManager / CL Pool / CL Gauge(Slipstream)
CLFactory
getPool(tokenA, tokenB, int24 tickSpacing) -> address
allPools(uint256) / allPoolsLength() / isPool(address)
CL Pool(读为主;写由 gauge/PM 调)
slot0() -> (uint160 sqrtPriceX96, int24 tick, uint16 obsIndex, uint16 obsCard, uint16 obsCardNext, bool unlocked)
token0()/token1()/tickSpacing()/fee()/unstakedFee()/liquidity()/stakedLiquidity()
gaugeFees() -> (uint128, uint128)
positions(bytes32 key) -> (uint128 liquidity, ...) // key = keccak(owner,tickLower,tickUpper)
CL PositionManager (NFT, ERC721Enumerable)
// 写
mint((address token0, address token1, int24 tickSpacing, int24 tickLower, int24 tickUpper,
uint256 amount0Desired, uint256 amount1Desired, uint256 amount0Min, uint256 amount1Min,
address recipient, uint256 deadline, uint160 sqrtPriceX96)) payable -> tokenId // sqrtPriceX96=0 表示池已存在
increaseLiquidity((uint256 tokenId, uint256 amount0Desired, uint256 amount1Desired, uint256 amount0Min, uint256 amount1Min, uint256 deadline)) payable
decreaseLiquidity((uint256 tokenId, uint128 liquidity, uint256 amount0Min, uint256 amount1Min, uint256 deadline)) payable
collect((uint256 tokenId, address recipient, uint128 amount0Max, uint128 amount1Max)) payable // 领未质押 CL 手续费
burn(uint256 tokenId) payable
// 读
balanceOf(user) / tokenOfOwnerByIndex(user, i) / ownerOf(tokenId)
positions(uint256 tokenId) -> (uint96 nonce, address operator, address token0, address token1,
int24 tickSpacing, int24 tickLower, int24 tickUpper, uint128 liquidity,
uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, uint128 tokensOwed0, uint128 tokensOwed1)
CL Gauge(质押 = 把 NFT 交给 gauge)
// 写
deposit(uint256 tokenId) // 质押 CL NFT(先 PositionManager.approve(gauge, tokenId))
withdraw(uint256 tokenId) // 取回 NFT
getReward(uint256 tokenId) // 领该仓位 UP
getReward(address account) // 领该地址全部质押仓位 UP
// 读
earned(address account, uint256 tokenId) -> uint256
stakedValues(address depositor) -> uint256[] // ★ 该地址已质押的 tokenId 列表(枚举质押 CL 仓位的关键)
stakedContains(address, uint256 tokenId) -> bool
stakedLength(address depositor) -> uint256
rewardRate()/rewardRateByEpoch(uint256)/periodFinish()/left()
rewardToken() -> address
4.9 CL Quoter / CL SwapRouter
Quoter.quoteExactInputSingle((address tokenIn, address tokenOut, uint256 amountIn, int24 tickSpacing, uint160 sqrtPriceLimitX96))
-> (uint256 amountOut, uint160 sqrtPriceX96After, uint32 ticksCrossed, uint256 gasEstimate) // nonpayable,用 eth_call 模拟
SwapRouter.exactInputSingle((address tokenIn, address tokenOut, int24 tickSpacing, address recipient,
uint256 deadline, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) payable -> amountOut
4.10 KyberSwap valuation(HTTP API,只读)
GET {BASE}/robinhood/api/v1/routes?tokenIn=&tokenOut=&amountIn=&gasInclude=false
-> data.routeSummary.amountOutUsd
- 不发送 fee 参数,不调用
route/build,不读取 routerAddress,不产生 approve 或交易 calldata。 - 当前
useUpPrice每 60 秒取 1 UP→USDG 的amountOutUsd;失败时估值保持不可用, 不回退为交易报价。
4.11 Uniswap v3(官方部署,2026-07-02 上线 — "primary public AMM")
官方地址(developers.uniswap.org deployments;配对已于 2026-07-16 链上验证:
NPM.factory() == FACTORY、NPM.WETH9() == WETH、minted 156k+。⚠️ Blockscout
上还有多个同名野生 fork —— 认准下面这两个):
UniswapV3Factory 0x1f7d7550B1b028f7571E69A784071F0205FD2EfA owner 0x2bad…46cd
NonfungiblePositionManager 0x73991a25C818Bf1f1128dEAaB1492D45638DE0D3
QuoterV2 0x33e885ed0ec9bf04ecfb19341582aadcb4c8a9e7 (直连报价)
SwapRouter02 0xcaf681a66d020601342297493863e78c959e5cb2 (直连 swap + 原子 9 bps)
UniversalRouter 0x8876789976decbfcbbbe364623c63652db8c0904 (未接入)
Permit2 0x000000000022D473030F116dDEE9F6B43aC78BA3 (canonical)
与 Slipstream 的差异(前端已适配):
positions(tokenId)第 4 槽是 uint24 fee(Slipstream 是 int24 tickSpacing)slot0()是 7 字段(多一个 uint8 feeProtocol;Slipstream 6 字段)——fetchSqrtPriceX96用只解码(sqrtPriceX96, tick)前缀的最小 ABI 两者通吃- fee → tickSpacing:100→1 / 500→10 / 3000→60 / 10000→200(工厂已启用全部四档)
- 池按
getPool(token0, token1, fee)发现;无 gauge / 质押 / unstakedFee 概念 - 写接口 increase/decrease/collect/burn 与 Slipstream 签名完全一致 —— 前端复用 clPmAbi 片段、只换 NPM 地址(实测 collect 模拟通过)
4.12 Uniswap v2(官方部署;POOLS + 直连 swap)
官方地址(developers.uniswap.org v2 deployments;配对已于 2026-07-16 链上验证:
Router02.factory() == FACTORY、Router02.WETH() == WETH):
UniswapV2Factory 0x8bcEaA40B9AcdfAedF85AdF4FF01F5Ad6517937f
UniswapV2Router02 0x89e5DB8B5aA49aA85AC63f691524311AEB649eba
- 香草 v2:固定 0.30% 费率,无 stable 档,LP 是同质化 ERC-20(非 NFT),
手续费滚入储备自动复利;与 UP33 的 Solidly 式 v2 Router 签名不同
(
addLiquidity无stable参数、无quoteAddLiquidity)—— 前端用uniV2RouterAbi走UNI.V2_ROUTER,mins = desired×(1−滑点) - 目录可直接枚举:
allPairsLength()/allPairs(i)(2026-07-16 实测 11,825 对, 日增 ~百对);池状态getReserves()+totalSupply() - 头寸管理(LP 余额 / removeLiquidity)暂未接入 —— 只有浏览 + 加流动性
Uniswap v4 同日上线但未接入:PoolManager 0x8366a39CC670B4001A1121B8F6A443A643e40951、
PositionManager 0x58dAeC3116AaE6D93017bAaEA7749052e8a04FA7、StateView
0xf3334192d15450cdd385c8b70e03f9a6bd9e673b、Quoter 0x8dc178efb8111bb0973dd9d722ebeff267c98f94。
5. 用户全流程 → 精确调用序列
A. 查看(只读,全部走 Multicall3 批量)
枚举所有池/gauge(动态,勿硬编码)
Voter.length()→ N- 对 i∈[0,N):
Voter.pools(i)→ pool;Voter.gauges(pool)→ gauge;Voter.isAlive(gauge)、Voter.weights(pool) - 每个 pool:
token0/token1;CL 再读tickSpacing/fee/slot0/liquidity/stakedLiquidity,v2 读stable/getReserves - 每个 gauge:
rewardRate/periodFinish/left、Voter.claimable(gauge)、Voter.gaugeToFees/gaugeToBribe - 未列入 Voter 的新池(还没 gauge):
CLFactory.allPools/v2Factory.allPools补全
APR 估算:池周排放 ≈ weekly × weights(pool)/totalWeight(capMode=Enforced 时还要按 cap 打折);LP 收益 ∝ 自己有效流动性 / gauge 总有效流动性。UP 计价需 Quoter 报一个 UP→WETH。
查看"我的仓位"
- 钱包余额:各 token
balanceOf(user) - 未质押 v2 LP:对每个 v2 pool
pool.balanceOf(user)+claimable0/1(user) - 已质押 v2 LP:对每个 v2 gauge
gauge.balanceOf(user)+gauge.earned(user) - 未质押 CL NFT:
PM.balanceOf(user)→tokenOfOwnerByIndex→positions(tokenId);用 (token0,token1,tickSpacing) →CLFactory.getPool定位池 - 已质押 CL NFT:对每个 CL gauge
gauge.stakedValues(user)→ tokenId[],再gauge.earned(user, tokenId)、PM.positions(tokenId) - 我的锁:
veUP.balanceOf(user)→tokenOfOwnerByIndex→locked(tokenId)+balanceOfNFT(tokenId)+Voter.usedWeights/lastVoted/votes - 可领:LP 用上面的
earned;voter fee/incentive 用gaugeToFees/gaugeToBribe指向的 reward 合约的earned(token, tokenId)
B. 提供流动性(Provide liquidity)
v2
UP/WETH/... approve(v2Router, amount)(两种 token)quoteAddLiquidity(...)预估 → 设amountAMin/BMin(滑点)v2Router.addLiquidity(tokenA, tokenB, stable, aDesired, bDesired, aMin, bMin, user, deadline)(含 ETH 用addLiquidityETH{value:...}) → 得到 v2 LP token(ERC-20,在 pool 合约里)
CL
- 选 tickSpacing(决定 base fee 档)与 tickLower/tickUpper(须对齐 tickSpacing)
token0/token1 approve(PositionManager, amount)PositionManager.mint({token0, token1, tickSpacing, tickLower, tickUpper, amount0Desired, amount1Desired, amount0Min, amount1Min, recipient:user, deadline, sqrtPriceX96:0})→ 得到 CL 仓位 NFT
C. 质押 / 取消质押(Stake / Unstake)
v2:pool.approve(gauge, lpAmount) → gauge.deposit(lpAmount);取消 gauge.withdraw(lpAmount)
CL:PositionManager.approve(gauge, tokenId)(或 setApprovalForAll)→ gauge.deposit(tokenId);取消 gauge.withdraw(tokenId)
质押时 gauge 会先把质押前累计的手续费付给你,之后手续费归 voters、你改赚 UP。无锁定、无罚金。
D. 领取(Claim)
- LP 排放(UP):
gauge.getReward(user)(CL 也可getReward(tokenId));或批量Voter.claimRewards([gauges]) - 未质押手续费:v2
pool.claimFees();CLPositionManager.collect({tokenId, recipient:user, amount0Max:max, amount1Max:max}) - voter 手续费:
Voter.claimFees([feesRewards], [[tokens]], tokenId) - voter incentives/bribes:
Voter.claimBribes([bribeRewards], [[tokens]], tokenId)(feesRewards/bribeRewards 地址来自gaugeToFees/gaugeToBribe)
E. 锁仓(Lock)
- 新建:
UP.approve(veUP, value)→veUP.createLock(value, durationSeconds) - 加仓:
veUP.increaseAmount(tokenId, value);延长:increaseUnlockTime(tokenId, durationSeconds) - 永久:
lockPermanent(tokenId)/unlockPermanent(tokenId);合并merge(from,to);到期取回withdraw(tokenId)
F. 投票(Vote)
Voter.vote(tokenId, [pool1,pool2,...], [w1,w2,...])(权重比例即可,合约按和归一;最多 30 池)- 改主意:
reset(tokenId)(同 epoch 不能再投);poke(tokenId)按衰减刷新 - 窗口:flip 后第 1 小时不能投;下次 flip 前最后 1 小时仅 whitelisted NFT 可投
G. 激励一个池(Incentivize)
token.approve(bribeReward, amount)→IncentiveVotingReward(gaugeToBribe(gauge)).notifyRewardAmount(token, amount)- token 须在白名单;存入不可撤回,给本 epoch voters
H. 交易(Swap)—— Uniswap + UP33 直连比价
- 候选:Uniswap v2、Uniswap v3 四个官方 fee tier 与 UP33 CL;每条 route 都用链上 Router/Quoter 报完整输入,protocol 内先取最优,再按 9 bps 后净输出 选择全局最优。UP33 Solidly v2 因无原子 fee-sweep 路径,不进入 swap 候选。
- 完整性:factory/pool 不存在是
absent;discovery 或完整报价失败是failed。一个已发现 route 失败会使该 protocol 整体不参与排名,不用较差 route 静默兜底。ZAP 只有在 Uniswap/UP33 比较完整时才规划。 - 滑点:同 route 用约 1% 小额 probe 与完整报价的单位价格差估算 impact, 自动选择 0.5%(绿)/ 1%(黄)/ 3%(红)。raw amount 小于 100 或 probe 不可用时 AUTO 保持 neutral,必须手选 0.5% / 1% / 3%。
- 执行:route 在用户确认后固定;发送前同 route fresh quote 必须覆盖冻结的 net minimum。ERC20 只做 exact approval,真实 approve 后重新 prepare。
- 原子 fee:Uniswap SwapRouter02 或 UP33 CL Router 把 gross output 留在 router,
同一 multicall 调
sweepTokenWithFee/unwrapWETH9WithFee收取固定 9 bps。 用户 minimum 是 fee 后净额;不做 aggregator 拆单、provider fallback 或 Kyber 执行。
H2. 区间限价单(LIMIT,"部署 LP 卖币")
单边 CL 仓位当限价单:把要卖的 token 单边 mint 进一个严格在现价之外的区间(卖 token0 → 区间在上方;卖 token1 → 区间在下方,均指 token1/token0 价格空间),价格穿越区间的过程即逐步成交,完全穿越后仓位 100% 变成目标 token,withdraw 落袋。
- 完全成交均价 = 区间两端价格的几何平均
√(pa·pb)(闭式,已对 BigInt 路径验证到 1e-12) - 成交进度:卖 token1 时
fill = (√B−√P)/(√B−√A),卖 token0 镜像 - 调用序列 =
approve(PM)→PM.mint(单边 desired,另一侧 0;卖侧 min≈100% 兜底"价格已进区间")→ 出场decreaseLiquidity(100%)+collect - 非挂单语义:价格回撤会"反向成交";无自动执行;区间内赚池费(unstaked 扣 10% levy)。CL gauge 不适用(出区间质押零排放,且质押把手续费让给 voters)
- 前端在
#limit;订单意图存 localStorage(tokenId→sell/buy),POSITIONS 显示 fill%
6. 文档 vs 链上:必须知道的差异(已随上线更新)
- Gauge cap 现在是 Enforced(≠ 07-13 的 Disabled)。排放会按"池手续费 WETH 价值 × cap multiple"封顶,超出被 burn。前端做 APR 时若 capMode=2 不能只用
weekly × 票重占比,需考虑 cap 打折(controller 源码未验证,精确值最好读Voter.claimable(gauge)实际到账,而非纯理论推算)。 - "动态费率"当前实为静态 override:SwapFeeModule 只读 per-pool
customFee,不看波动率/成交量。以pool.fee()实际值为准(例:WETH/UP CL 曾是 2% 静态,而非文档表的 0.3%)。 - CL 未质押 LP 有 10% fee levy:文档说"未质押保留全部手续费",实际默认扣 10% 给 gauge。算未质押 CL 真实收益要先扣。
- rebase 当前为 0;team share 5%。
- 治理高度集中:单一 2/3 Safe 可改 fee/module、pause、kill/revive gauge、切 cap 模式。前端应把这些当"参数随时可变",读实时值而非缓存假设。
6.6 APR 公式 ↔ 已部署源码逐行核对(2026-07-16,全部取自 Blockscout 已验证合约)
CL 手续费(CLPool.calculateFees/splitFees/applyUnstakedFees,WETH/UP 池实例已验证):
- 每步 swap:
feeAmount = amountIn × fee()/1e6(fee 实时读 factory SwapFeeModule) - staked 份额
feeAmount × stakedLiq/liq→gaugeFees→(CLGauge._claimFees→pool.collectFees)→ FeesVotingReward(voters) - unstaked 份额再抽
unstakedFee()/1e6(现 10%)→ 也进gaugeFees - 余下记
feeGrowthGlobal += net/(liq−stakedLiq)⇒ 未质押仓位实得 = 总fee × (1−levy) × L/活跃总流动性 stake()把 NFT 仓位流动性移入 gauge 虚拟仓位,Position.update(..., staked=true)跳过 tokensOwed 累积 ⇒ 质押仓位手续费严格为 0
CL 排放(CLPool._updateRewardsGrowthGlobal + CLGauge._earned/_notifyRewardAmount):
rewardGrowthGlobal += rewardRate×dt/stakedLiquidity(分母=活跃质押流动性;为 0 时该段 UP 进rollover,滚入下期 notify)- 仓位收益 =
L × ΔrewardGrowthInside(区间),出区间不增长 - notify 仅 Voter 可调:
rewardRate = (amount+rollover+leftover)/到下周四的秒数,periodFinish= 下次 flip
Voter cap(_distributeEnforced):gauge 只收到 release = min(nominal, cap 允许累计 − 已放);withheld 期末结算时 burnFrom 烧掉 ⇒ gauge.rewardRate() 就是 cap 后真值;同 epoch 内后续 distribute() 可能增量 release、把 rate 上调(前端 ~20s 轮询能跟上)。
v2(Pool._update0/_update1 + Gauge.rewardPerToken,Gauge 源码取自已验证 GaugeFactory):
- fee =
amountIn × getFee/10000→ 转出到 PoolFees,index0 += fee×1e18/totalSupply⇒ 每枚 LP token 均匀累积(gauge 持有部分由 gauge 领走给 voters ⇒ 质押者 0 手续费) - 排放
rewardPerToken = rewardRate×dt/totalSupply(gauge 内质押 LP token)
⇒ 前端 src/lib/apr.ts 与上述一致:池列(vol×fee×(1−levy)×365/TVL、rewardRate×年秒×UP价/质押TVL)是文档化的"代表性仓位"近似;per-position 模拟的份额公式 L/(activeL+L)(fee)与 L/(stakedL+L)(emit)为合约级精确。年化假设当前 rate 持续(合约只承诺到 periodFinish)。
7. 前端读数策略
- 批量读:所有 view 走
Multicall3.aggregate3,一次 RPC 拿全量池/gauge/仓位。 - 交易报价:Uniswap v2/v3 + UP33 CL 直接用官方 Router/Quoter;Kyber 仅用于美元估值。
- 枚举 tokenId:veUP 与 CL PM 都是 ERC721Enumerable,用
balanceOf + tokenOfOwnerByIndex;CL 质押仓位用CLGauge.stakedValues。 - 子图(可选,加速历史/TVL/volume):Goldsky CL + gauges 子图;但写前必须以 RPC 实时读为准。
- epoch 计时:
Voter.epochNext(now)得下次 flip;UI 显示倒计时与投票窗口开关。 - 小数位:USDG=6,其余 18;所有格式化按各 token decimals。
- swap 报价:输入去抖后并行读取 Uniswap v2/v3 与 UP33 CL;15s 自动刷新, pool absent、quote failed、impact unavailable 分别展示。
- 24h volume/TVL(实测可用,2026-07-16):CL 池用 DexScreener 批量端点(
api.dexscreener.com/latest/dex/pairs/robinhood/<addr,addr,…>,一次 ≤30 地址,rolling 24h USD,CORS 开放;v2 池不收录);v2 池用官方 Goldsky v2 子图project_cmhef02640198x7p2cz2w70u8/subgraphs/up-robinhood-v2-mainnet/0.1.0/gn(标准 uniswap-v2 schema:pairs/pairDayDatas/pairHourDatas,同步在链头;小池 trackedUSD 可能为 0,用 USDG 侧≈$ 或 WETH 侧×WETH 价兜底)。GeckoTerminal 对robinhood的覆盖已完整(2026-07-16 晚实测:14 个 DEX 含 uniswap v2/v3/v4 + pancake,各榜单 top 10 页×20,池对象带 vol m5–h24/储备/买卖笔数/ 代币 USD 价,免费档 30 req/min;无 up33 条目 —— UP33 统计仍走 DexScreener + Goldsky)。Uniswap 池的 TVL/vol 由自建 indexer 供给(up33-terminal/indexer/)。 fees 24h = vol × 池费率(gross)。 - token list / USD 价:token list 合并 UP33 factory registry 与 Uniswap pool
index(index 不可用时显式标记 fallback);Kyber routes 的
amountOutUsd只给 UP 奖励/APR/仓位与模拟面板做只读美元估值。
8. 安全 / 体验注意
- 每次交易只按精确额度 approve(不授权 max);写前校验
chainId==4663。 - CL tick 必须对齐 tickSpacing;mint 前用 slot0 当前 tick 辅助选区间。
- 出区间的 CL 仓位不赚费/排放,UI 要显式标"out of range"。
- claim/领取分三类(LP 排放 / 未质押手续费 / voter fee+incentive),入口分开,避免误解。
- 展示"资产风险"独立于"排放 APR":很多池是新发未验证 token。