Files
labrinyang 6a0972db1f docs: rewrite the README around what shipped, add the contract map
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>
2026-07-23 00:02:14 +08:00

31 KiB
Raw Permalink Blame History

UP33 合约地图 & 用户全流程(前端构建基准)

目标:把 UP33up.Robinhood Chain 上的 Velodrome V2 + Slipstream ve(3,3) DEX)的每个用户动作 精确映射到链上合约调用,作为自建前端的工程参考。 所有函数签名均已通过 Robinhood Chain Blockscout 的已验证源码核对(编译器 0.8.19)。

  • Chain: Robinhood ChainchainId = 4663gas token = ETH
  • RPC: .env 里的 RPCAlchemy robinhood-mainnet);官方公开 RPC https://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

0. 实时状态(2026-07-16 04:03 UTCblock 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>0periodFinish=2026-07-23 00:00 UTC UP 正在按秒流向质押 LP
  • epoch_next = 2026-07-23T00:00:00Z(下一次 flip / 投票截止)。
  • 池和 gauge 列表会持续变化,前端必须动态枚举(见 §5),不要硬编码。

1. 合约清单

核心合约(Blockscout 已验证)

角色 地址 说明
UP token 0x57C0E45cB534413D1C20A4240955d6bB250BB4F1 排放/治理代币,ERC-2018 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 仓位 NFTmint/increase/decrease/collect
CL SwapRouter 0xC062b870E813fcA720f1e002c234369Ab3aB9415 CL swap
CL Quoter 0x03983AB2C057a2eac211ff01738a1e49ff325B49 CL 报价(revert 式,eth_call
WETH 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73 标准 WETH918 dec
Multicall3 0xcA11bde05977b3631167028862bE2a173976CA11 标准地址,批量读

第三方:KyberSwap(只读美元估值)

  • Aggregator API basehttps://aggregator-api.kyberswap.comchain slug robinhood(对应 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 设 050%
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() 仍是单 EOA 0x85Fb9f9BC8a44663A234AdB4CfE4E1459C096651pendingTeam 已设为 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

⚠️ 池里很多是新发/未验证 tokenDIH、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    // 枚举用户的 veNFTERC721Enumerable
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()          // permissionless7 天到期后触发本期 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 的默认值
// swapRoute[] = (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 GaugeSlipstream

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 valuationHTTP 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() == FACTORYNPM.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 feeSlipstream 是 int24 tickSpacing
  • slot0()7 字段(多一个 uint8 feeProtocolSlipstream 6 字段)—— fetchSqrtPriceX96 用只解码 (sqrtPriceX96, tick) 前缀的最小 ABI 两者通吃
  • fee → tickSpacing100→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() == FACTORYRouter02.WETH() == WETH):

UniswapV2Factory   0x8bcEaA40B9AcdfAedF85AdF4FF01F5Ad6517937f
UniswapV2Router02  0x89e5DB8B5aA49aA85AC63f691524311AEB649eba
  • 香草 v2:固定 0.30% 费率,无 stable 档,LP 是同质化 ERC-20(非 NFT), 手续费滚入储备自动复利;与 UP33 的 Solidly 式 v2 Router 签名不同 addLiquiditystable 参数、无 quoteAddLiquidity)—— 前端用 uniV2RouterAbiUNI.V2_ROUTERmins = 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(动态,勿硬编码)

  1. Voter.length() → N
  2. 对 i∈[0,N)Voter.pools(i) → poolVoter.gauges(pool) → gaugeVoter.isAlive(gauge)Voter.weights(pool)
  3. 每个 pooltoken0/token1CL 再读 tickSpacing/fee/slot0/liquidity/stakedLiquidityv2 读 stable/getReserves
  4. 每个 gaugerewardRate/periodFinish/leftVoter.claimable(gauge)Voter.gaugeToFees/gaugeToBribe
  5. 未列入 Voter 的新池(还没 gauge):CLFactory.allPools/v2Factory.allPools 补全

APR 估算池周排放 ≈ weekly × weights(pool)/totalWeightcapMode=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 NFTPM.balanceOf(user)tokenOfOwnerByIndexpositions(tokenId);用 (token0,token1,tickSpacing) → CLFactory.getPool 定位池
  • 已质押 CL NFT:对每个 CL gauge gauge.stakedValues(user) → tokenId[],再 gauge.earned(user, tokenId)PM.positions(tokenId)
  • 我的锁:veUP.balanceOf(user)tokenOfOwnerByIndexlocked(tokenId) + balanceOfNFT(tokenId) + Voter.usedWeights/lastVoted/votes
  • 可领:LP 用上面的 earnedvoter fee/incentive 用 gaugeToFees/gaugeToBribe 指向的 reward 合约的 earned(token, tokenId)

B. 提供流动性(Provide liquidity

v2

  1. UP/WETH/... approve(v2Router, amount)(两种 token
  2. quoteAddLiquidity(...) 预估 → 设 amountAMin/BMin(滑点)
  3. v2Router.addLiquidity(tokenA, tokenB, stable, aDesired, bDesired, aMin, bMin, user, deadline) (含 ETH 用 addLiquidityETH{value:...} → 得到 v2 LP tokenERC-20,在 pool 合约里)

CL

  1. 选 tickSpacing(决定 base fee 档)与 tickLower/tickUpper(须对齐 tickSpacing
  2. token0/token1 approve(PositionManager, amount)
  3. PositionManager.mint({token0, token1, tickSpacing, tickLower, tickUpper, amount0Desired, amount1Desired, amount0Min, amount1Min, recipient:user, deadline, sqrtPriceX96:0}) → 得到 CL 仓位 NFT

C. 质押 / 取消质押(Stake / Unstake

v2pool.approve(gauge, lpAmount)gauge.deposit(lpAmount);取消 gauge.withdraw(lpAmount) CLPositionManager.approve(gauge, tokenId)(或 setApprovalForAll)→ gauge.deposit(tokenId);取消 gauge.withdraw(tokenId)

质押时 gauge 会先把质押前累计的手续费付给你,之后手续费归 voters、你改赚 UP。无锁定、无罚金。

D. 领取(Claim

  • LP 排放(UPgauge.getReward(user)CL 也可 getReward(tokenId));或批量 Voter.claimRewards([gauges])
  • 未质押手续费v2 pool.claimFees()CL PositionManager.collect({tokenId, recipient:user, amount0Max:max, amount1Max:max})
  • voter 手续费Voter.claimFees([feesRewards], [[tokens]], tokenId)
  • voter incentives/bribesVoter.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 直连比价

  1. 候选Uniswap v2、Uniswap v3 四个官方 fee tier 与 UP33 CL;每条 route 都用链上 Router/Quoter 报完整输入,protocol 内先取最优,再按 9 bps 后净输出 选择全局最优。UP33 Solidly v2 因无原子 fee-sweep 路径,不进入 swap 候选。
  2. 完整性factory/pool 不存在是 absentdiscovery 或完整报价失败是 failed。一个已发现 route 失败会使该 protocol 整体不参与排名,不用较差 route 静默兜底。ZAP 只有在 Uniswap/UP33 比较完整时才规划。
  3. 滑点:同 route 用约 1% 小额 probe 与完整报价的单位价格差估算 impact, 自动选择 0.5%(绿)/ 1%(黄)/ 3%(红)。raw amount 小于 100 或 probe 不可用时 AUTO 保持 neutral,必须手选 0.5% / 1% / 3%。
  4. 执行:route 在用户确认后固定;发送前同 route fresh quote 必须覆盖冻结的 net minimum。ERC20 只做 exact approval,真实 approve 后重新 prepare。
  5. 原子 feeUniswap 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% 变成目标 tokenwithdraw 落袋。

  • 完全成交均价 = 区间两端价格的几何平均 √(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;订单意图存 localStoragetokenId→sell/buy),POSITIONS 显示 fill%

6. 文档 vs 链上:必须知道的差异(已随上线更新)

  1. Gauge cap 现在是 Enforced(≠ 07-13 的 Disabled。排放会按"池手续费 WETH 价值 × cap multiple"封顶,超出被 burn。前端做 APR 时若 capMode=2 不能只用 weekly × 票重占比,需考虑 cap 打折(controller 源码未验证,精确值最好读 Voter.claimable(gauge) 实际到账,而非纯理论推算)。
  2. "动态费率"当前实为静态 overrideSwapFeeModule 只读 per-pool customFee,不看波动率/成交量。以 pool.fee() 实际值为准(例:WETH/UP CL 曾是 2% 静态,而非文档表的 0.3%)。
  3. CL 未质押 LP 有 10% fee levy:文档说"未质押保留全部手续费",实际默认扣 10% 给 gauge。算未质押 CL 真实收益要先扣。
  4. rebase 当前为 0team share 5%。
  5. 治理高度集中:单一 2/3 Safe 可改 fee/module、pause、kill/revive gauge、切 cap 模式。前端应把这些当"参数随时可变",读实时值而非缓存假设。

6.6 APR 公式 ↔ 已部署源码逐行核对(2026-07-16,全部取自 Blockscout 已验证合约)

CL 手续费CLPool.calculateFees/splitFees/applyUnstakedFeesWETH/UP 池实例已验证):

  • 每步 swapfeeAmount = amountIn × fee()/1e6fee 实时读 factory SwapFeeModule
  • staked 份额 feeAmount × stakedLiq/liqgaugeFees →(CLGauge._claimFees→pool.collectFees)→ FeesVotingRewardvoters
  • unstaked 份额再抽 unstakedFee()/1e6(现 10%)→ 也进 gaugeFees
  • 余下记 feeGrowthGlobal += net/(liqstakedLiq)未质押仓位实得 = 总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 轮询能跟上)。

v2Pool._update0/_update1 + Gauge.rewardPerTokenGauge 源码取自已验证 GaugeFactory):

  • fee = amountIn × getFee/10000 → 转出到 PoolFeesindex0 += fee×1e18/totalSupply每枚 LP token 均匀累积gauge 持有部分由 gauge 领走给 voters ⇒ 质押者 0 手续费)
  • 排放 rewardPerToken = rewardRate×dt/totalSupplygauge 内质押 LP token

前端 src/lib/apr.ts 与上述一致:池列(vol×fee×(1levy)×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/QuoterKyber 仅用于美元估值。
  • 枚举 tokenIdveUP 与 CL PM 都是 ERC721Enumerable,用 balanceOf + tokenOfOwnerByIndexCL 质押仓位用 CLGauge.stakedValues
  • 子图(可选,加速历史/TVL/volume):Goldsky CL + gauges 子图;但写前必须以 RPC 实时读为准。
  • epoch 计时Voter.epochNext(now) 得下次 flip;UI 显示倒计时与投票窗口开关。
  • 小数位:USDG=6,其余 18;所有格式化按各 token decimals。
  • swap 报价:输入去抖后并行读取 Uniswap v2/v3 与 UP33 CL15s 自动刷新, pool absent、quote failed、impact unavailable 分别展示。
  • 24h volume/TVL(实测可用,2026-07-16CL 池用 DexScreener 批量端点(api.dexscreener.com/latest/dex/pairs/robinhood/<addr,addr,…>,一次 ≤30 地址,rolling 24h USDCORS 开放;v2 池不收录);v2 池用官方 Goldsky v2 子图 project_cmhef02640198x7p2cz2w70u8/subgraphs/up-robinhood-v2-mainnet/0.1.0/gn(标准 uniswap-v2 schemapairs/pairDayDatas/pairHourDatas,同步在链头;小池 trackedUSD 可能为 0,用 USDG 侧≈$ 或 WETH 侧×WETH 价兜底)。GeckoTerminal 对 robinhood 的覆盖已完整(2026-07-16 晚实测:14 个 DEX 含 uniswap v2/v3/v4 + pancake,各榜单 top 10 页×20,池对象带 vol m5h24/储备/买卖笔数/ 代币 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 indexindex 不可用时显式标记 fallback);Kyber routes 的 amountOutUsd 只给 UP 奖励/APR/仓位与模拟面板做只读美元估值。

8. 安全 / 体验注意

  • 每次交易只按精确额度 approve(不授权 max);写前校验 chainId==4663
  • CL tick 必须对齐 tickSpacingmint 前用 slot0 当前 tick 辅助选区间。
  • 出区间的 CL 仓位不赚费/排放,UI 要显式标"out of range"。
  • claim/领取分三类(LP 排放 / 未质押手续费 / voter fee+incentive),入口分开,避免误解。
  • 展示"资产风险"独立于"排放 APR":很多池是新发未验证 token。