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

506 lines
31 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`Alchemy `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 base`https://aggregator-api.kyberswap.com`chain 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 `0x85Fb9f9BC8a44663A234AdB4CfE4E1459C096651``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 |
> ⚠️ 池里很多是新发/未验证 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() == 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 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() == 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(动态,勿硬编码)**
1. `Voter.length()` → N
2. 对 i∈[0,N)`Voter.pools(i)` → pool`Voter.gauges(pool)` → gauge`Voter.isAlive(gauge)``Voter.weights(pool)`
3. 每个 pool`token0/token1`CL 再读 `tickSpacing/fee/slot0/liquidity/stakedLiquidity`v2 读 `stable/getReserves`
4. 每个 gauge`rewardRate/periodFinish/left``Voter.claimable(gauge)``Voter.gaugeToFees/gaugeToBribe`
5. 未列入 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**
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
**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()`CL `PositionManager.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 直连比价
1. **候选**Uniswap v2、Uniswap v3 四个官方 fee tier 与 UP33 CL;每条 route
都用链上 Router/Quoter 报完整输入,protocol 内先取最优,再按 9 bps 后净输出
选择全局最优。UP33 Solidly v2 因无原子 fee-sweep 路径,不进入 swap 候选。
2. **完整性**factory/pool 不存在是 `absent`discovery 或完整报价失败是
`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. **原子 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% 变成目标 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. **"动态费率"当前实为静态 override**SwapFeeModule 只读 per-pool `customFee`,不看波动率/成交量。以 `pool.fee()` 实际值为准(例:WETH/UP CL 曾是 2% 静态,而非文档表的 0.3%)。
3. **CL 未质押 LP 有 10% fee levy**:文档说"未质押保留全部手续费",实际默认扣 10% 给 gauge。算未质押 CL 真实收益要先扣。
4. **rebase 当前为 0**team share 5%。
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`)→ 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 轮询能跟上)。
**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×(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 仅用于美元估值。
- **枚举 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 CL15s 自动刷新,
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 USDCORS 开放;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 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。