@masterpeach/market-sdk 0.2.0-beta.20260930.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,153 @@
1
+ # 前端接入指南
2
+
3
+ Market SDK 面向 Arc(5042)和 BSC(56),负责市场读取、LP 仓位、存取流动性和手续费。使用前端已有的 viem publicClient / walletClient;不绑定 React、钱包连接框架或 UI 组件,也不会自动轮询。用户买卖的路由与执行属于 Swap SDK,测试脚本里的 Router 不作为前端生产交易入口。
4
+
5
+ ## 文档导航
6
+
7
+ | 前端需求 | 文档 |
8
+ | --- | --- |
9
+ | 池子列表、字段、分页和详情补充 | [池子列表](pools.md) |
10
+ | 我的仓位、收益和部分/全部提取 | [仓位](positions.md) |
11
+ | 授权、存入、滑点和原生币 | [流动性接口](liquidity.md) |
12
+ | 净收益、份额与代领 | [收益与权限](earnings.md) |
13
+ | 价格和活动流动性读取 | [池状态](pool-state.md) |
14
+ | 管理员/运营参数 | [管理接口](management.md) |
15
+ | 最新合约与测试池 | [最新合约对接](latest-contract.md) |
16
+
17
+ ## 安装与双链配置
18
+
19
+ 包版本以 `package.json` 为准;本次仅准备临时发布产物,尚未执行 npm 发布。同仓库消费使用 `@masterpeach/market-sdk: workspace:*` 和仓库 viem catalog;独立前端先安装本地打包产物:
20
+
21
+ ```sh
22
+ # 在 SDK 仓库执行
23
+ pnpm --filter @masterpeach/market-sdk build
24
+ pnpm -C packages/market pack
25
+
26
+ # 在前端项目执行;替换为实际打包文件路径
27
+ pnpm add /path/to/masterpeach-market-sdk-0.2.0-beta.20260930.1.tgz 'viem@^2.45.0'
28
+ ```
29
+
30
+ 公开入口为根、`/abi`、`/core`,不要导入 src 内部路径。当前 peer 范围为 viem `>=2.45.0 <3`。已验证 Bundler TypeScript 5.7.3;NodeNext 消费使用 TypeScript 5.9+,详见包 README。
31
+
32
+ | 配置 | Arc | BSC |
33
+ | --- | --- | --- |
34
+ | chainId | 5042 | 56 |
35
+ | 原生 gas 币 | USDC,18 位 | BNB,18 位 |
36
+ | 文档测试池 | tBTC/tUSDC,8/6 位 | tBNB/tUSDT、tQQQ/tUSDT,均 18/18 位 |
37
+ | 测试币形式 | 均为 ERC-20 | 均为 ERC-20;tBNB 不是原生 BNB |
38
+
39
+ 为两条链分别维护 `MarketChainConfig`,由用户选中的链选择配置和 RPC,不共用合约地址。配置包含:
40
+
41
+ - `chainId`、`confirmations`(至少 1)。
42
+ - `deployment.chainId`、`deploymentId`、PoolManager、Registry、authority,按需提供 Reader 和 poolStateReader。
43
+ - 每个合约的审核地址与 runtimeCodeHash;每 Hook 的 kind、implementation、独立 ClaimFeePolicy,RwaRamp 额外提供自己的 Calendar。
44
+ - 每 Hook 的 `liquidity / nativeLiquidity / claimOperator / management` 能力状态;写操作要求相关能力经过独立验收。不能因为 verifyDeployment 成功就自动将状态改为 verified。
45
+
46
+ SDK 不提供可直接开启主网写操作的可信内置配置。验收报告的 `observedChain` 供复现读取,不是生产信任预设;完整部署配置由接入方审核后提供。getPoolState、LP 预览和收益估算需要显式状态读取配置,见[池状态](pool-state.md)。
47
+
48
+ SDK 核心仅使用注入的 publicClient。仓库 Arc 只读脚本默认 RPC 仍为 `https://rpc.arc-scan.org`;双链验收时显式覆盖合约文档 Blockdaemon 节点。前端应注入可用的对应链节点并展示 RPC 失败,不自动跨链或默默换节点。
49
+
50
+ ## 客户端生命周期
51
+
52
+ ```ts
53
+ import { createMarketClient, type MarketChainConfig } from "@masterpeach/market-sdk";
54
+ import type { PublicClient, WalletClient } from "viem";
55
+
56
+ export async function initializeMarket(
57
+ chain: MarketChainConfig,
58
+ publicClient: PublicClient,
59
+ walletClient?: WalletClient,
60
+ ) {
61
+ const client = createMarketClient({ chain, publicClient, walletClient });
62
+ await client.verifyDeployment();
63
+ return client;
64
+ }
65
+ ```
66
+
67
+ 构造实例本身不发请求;示例主动核验代码和绑定。未连接钱包可浏览市场和指定 owner 的仓位,准备/发送交易才需要钱包。核验失败时展示配置/网络错误,不能继续把旧链数据当新链数据。
68
+
69
+ React 等框架中稳定保留实例,不要每次渲染都重新创建。切链、切钱包或更换部署后创建对应实例,丢弃旧 quote/prepared;已广播交易保留原实例等待回执,或者使用原链 publicClient 查询保存的 hash。不能把旧 prepared 或陌生 hash 交给新实例。
70
+
71
+ 查询缓存至少包含 chainId、deploymentId 和业务键(poolId,或 hook/rangeId/owner)。新账户不要复用旧 owner 的仓位缓存。批量查询限制并发,只在可见页面和必要状态变化时刷新;SDK 不会自动帮前端管理缓存。
72
+
73
+ ## 添加流动性:金额到预览
74
+
75
+ 输入使用字符串金额,通过 `parseUnits` 和真实 decimals 转成 bigint。PoolKey 的 currency0/1 顺序不一定等于界面的 base/quote,先映射币种再计算;ticks 必须与 key.tickSpacing 对齐。
76
+
77
+ ```ts
78
+ import { getMarketLiquidityForAmounts, type MarketClient, type MarketPoolKey } from "@masterpeach/market-sdk";
79
+ import type { Address } from "viem";
80
+
81
+ export async function previewAddition(
82
+ client: MarketClient,
83
+ params: {
84
+ key: MarketPoolKey; owner: Address; tickLower: number; tickUpper: number;
85
+ sqrtPriceX96: bigint; amount0: bigint; amount1: bigint;
86
+ slippageBps: number; deadline: bigint;
87
+ },
88
+ ) {
89
+ const liquidity = getMarketLiquidityForAmounts(params);
90
+ if (liquidity === 0n) throw new Error("Amounts are too small for this range");
91
+ const quote = await client.previewDeposit({
92
+ key: params.key, tickLower: params.tickLower, tickUpper: params.tickUpper,
93
+ liquidity, slippageBps: params.slippageBps, deadline: params.deadline,
94
+ });
95
+ const spend = await client.getSpendStatus({
96
+ key: params.key, owner: params.owner,
97
+ amount0: quote.data.limit0, amount1: quote.data.limit1,
98
+ ...quote.block,
99
+ });
100
+ return { quote, spend };
101
+ }
102
+ ```
103
+
104
+ params.sqrtPriceX96 来自该池的 `getPoolState().data.sqrtPriceX96`;deadline 使用最新区块 timestamp 加用户接受的有效秒数。SDK 会根据实际预览区块重新计算本金。数学 helper 的 amount0/1 是计算预算,**叠加滑点后的 limit0/1 可能超过输入金额**;若输入代表硬预算,前端必须检查并降低 liquidity 后重算,不能默认把超额授权给 Hook。
105
+
106
+ 展示 `quote.data.amount0/1`(估计本金)、`limit0/1`(最大支出)、deadline 及余额。对 `spend.data.tokens`:
107
+
108
+ 1. `sufficientBalance=false` 时提示余额不足。
109
+ 2. `kind="erc20" && needsApproval` 时调用 `prepareApproval({ key, currency: 0或1, amount: token.requiredAmount })`;授权金额为完整所需额度,不是差额。每笔经用户确认发送并等成功回执。需要先清零的 token 分两笔处理。
110
+ 3. 授权结束后重新预览和检查余额/allowance,再 `prepareDeposit(freshQuote)`,展示新金额并由用户确认发送。
111
+ 4. 原生币不 approve,allowance 为 null;RampETH 原生池另需 `nativeLiquidity="verified"`,余额覆盖本金和 gas。三组文档测试池均走 ERC-20 路径。
112
+
113
+ 完整接口限制见[流动性接口](liquidity.md);不要将预览序列化再恢复后传给 prepare,SDK 要求当前实例产生的原始对象。
114
+
115
+ ## 交易发送与界面状态
116
+
117
+ 以下函数会请求钱包发送交易,仅绑定用户确认事件调用,不放在初始化或自动 effect 中:
118
+
119
+ ```ts
120
+ import type { MarketClient, MarketPreparedTransaction } from "@masterpeach/market-sdk";
121
+ import type { Hex } from "viem";
122
+
123
+ export async function submitConfirmedAction(
124
+ client: MarketClient,
125
+ prepared: MarketPreparedTransaction,
126
+ savePendingHash: (hash: Hex) => void,
127
+ ) {
128
+ const hash = await client.send(prepared);
129
+ savePendingHash(hash);
130
+ const result = await client.waitForTransaction(hash);
131
+ if (result.status !== "confirmed") throw new Error("Transaction reverted");
132
+ // repriced 替换时以 result.hash / receipt 为最终依据。
133
+ return result;
134
+ }
135
+ ```
136
+
137
+ | 状态 | 页面行为 |
138
+ | --- | --- |
139
+ | 未连接钱包 / 钱包链错误 | 保留只读浏览,交易前提示连接或切链 |
140
+ | 查询/预览中 | 显示加载态,金额未就绪不允许确认 |
141
+ | 待签名 / 已发送等待回执 | 禁止重复提交;取得 hash 后立即保存并展示进度 |
142
+ | 已确认 | 刷新钱包余额、仓位、收益和授权;根据实际回执与事件显示结果 |
143
+ | 用户拒签 / 模拟回滚 | 显示原因,重新准备后允许重试 |
144
+ | 超时 / 连接中断 | 保留 hash,先查询或继续等待;不盲目重发本金交易 |
145
+ | 数据为空 | 分清没有候选仓位、没有份额、历史 owed 与 RPC 失败 |
146
+
147
+ 参数错误为 `InvalidMarketConfigError`;链/代码/绑定不匹配为 `MarketDeploymentVerificationError`;读取错误 `MarketReadError` 保留 cause;交易错误 `MarketTransactionError` 携带 stage;替换或取消为 `MarketTransactionReplacedError`。合约错误应保留可诊断信息,不能把所有失败都显示为“余额不足”。
148
+
149
+ 领取成功可能没有支付事件,不能把“没有 FeesClaimed”一概当失败。单独 claim 的准备有效期只限制发送时机,并不是合约链上 deadline。所有金额保持 bigint,到展示时用对应 decimals 格式化;JSON 传输显式编码为十进制字符串并按字段恢复,避免 Number 精度损失。
150
+
151
+ ## 验证范围
152
+
153
+ Arc tBTC/tUSDC 与 BSC tBNB/tUSDT、tQQQ/tUSDT 已通过线上只读和本地 fork 的存入、swap 产生手续费、领取、退出及余额对账。尚未进行前端钱包 UI 端到端验收或主网真实资金广播;观察配置未自动开放生产写能力。双链验收只使用 ERC-20 测试池,原生路径另有本地 EVM 验证。前端仍需测试连接/切链/拒签/重复点击/刷新恢复等交互。
@@ -0,0 +1,38 @@
1
+ # 最新合约接入:cf16bd9
2
+
3
+ 基线 `cf16bd9376c12ea50de04658b279176d8d56e4f5`。相对 `839c68e`,原有八份 ABI 和生产实现未变化,主要新增 BSC 部署及三组主网上的无价值测试 ERC-20 池。现有读取、LP 与管理 action 保持兼容。
4
+
5
+ ```ts
6
+ import { marketTestPools, createMarketClient } from "@masterpeach/market-sdk";
7
+
8
+ // publicClient 和 chain 为调用方提供的 RPC 与审核后的 MarketChainConfig。
9
+ const client = createMarketClient({ publicClient, chain });
10
+ const testPool = marketTestPools.find(
11
+ (pool) => pool.chainId === chain.chainId && pool.name === "tBTC/tUSDC",
12
+ );
13
+ if (!testPool) throw new Error("Requested test market is not recorded on this chain");
14
+ const verified = await client.verifyDeployment();
15
+ const block = { blockNumber: verified.blockNumber, blockHash: verified.blockHash };
16
+ const pool = await client.getPool(testPool.poolId, block);
17
+ const fees = await client.getCurrentFees(testPool.poolId, block);
18
+ ```
19
+
20
+ | 链 | 测试市场 | base / quote 精度 | 实现 |
21
+ | --- | --- | --- | --- |
22
+ | Arc 5042 | tBTC / tUSDC | 8 / 6 | Ramp |
23
+ | BSC 56 | tBNB / tUSDT | 18 / 18 | Ramp |
24
+ | BSC 56 | tQQQ / tUSDT | 18 / 18 | RwaRamp |
25
+
26
+ 目录来源为该提交的 `docs/test-pools/{arc,bsc}.json`,`source` 保留提交、文件、历史核验区块。目录不是实时状态或部署能力证明,也不自动合并进 Registry 结果。共用 Registry 可以包含真实与测试资产;列表未收录的池属于未知分类,不能据此推断有真实价值。
27
+
28
+ `tBNB` 是 ERC-20:授权给对应 Ramp,deposit value=0。真实 BSC BNB 池才使用 currency0=零地址及 RampETH。Arc 池内 tUSDC 是 6 位 ERC-20,gas 使用的原生 USDC 是另一种表示。RWA CLOSED 仅影响费率,不代表禁止交易。
29
+
30
+ 每个 Hook 必须配置自己的 `claimFeePolicy` 和审核后的 runtimeCodeHash;RwaRamp 另需自己的 `calendar`。不能把三个 BSC Hook 的 Policy 地址共用,也不能从某次 RPC 读取代码后自动把它认定为可信预期。目录没有这类信任配置,所有写入仍由已有 capability 和模拟流程控制。
31
+
32
+ 只读示例可精确指定池(Arc 默认 RPC 仍为 Launchpad 所用节点):
33
+
34
+ ```sh
35
+ node examples/consumer/scripts/read-market.mjs reviewed-chain.json https://rpc.arc-scan.org 0x1a325fe4671d6584778464cbe5ea68b111c2a72718f128a9427717298e85e2cd
36
+ ```
37
+
38
+ `reviewed-chain.json` 必须来自部署审核,不能直接使用合约测试池 JSON 替代。示例输出 `assetClassification`;RPC 失败会报告失败,不自动切换网络或发送交易。目录不提供真实资产兑换关系;公开 mint 是测试代币合约功能,不是生产资产的发行入口。
@@ -0,0 +1,87 @@
1
+ # LP 接入与 P5 原生币支持
2
+
3
+ 前端完整流程见[接入指南](frontend.md);“我的仓位”和按比例提取见[仓位说明](positions.md)。
4
+
5
+ P3 提供双 ERC-20 本金预览、余额 / allowance、精确授权、存入、撤出、本人领取和撤出并领取。必须使用已审核的部署配置;Hook 的 `capabilities.liquidity` 必须为 `verified` 才能准备和发送交易。构造客户端不访问网络,读与预览不要求钱包。P4 增加收益和代领,P5 增加 RampETH 原生币及变体资金验收。
6
+
7
+ ## 钱包与交易流程
8
+
9
+ ```ts
10
+ const market = createMarketClient({ publicClient, walletClient, chain });
11
+ const quote = await market.previewDeposit({
12
+ key, tickLower: -120, tickUpper: 120,
13
+ liquidity: 10n ** 18n,
14
+ slippageBps: 50,
15
+ deadline: (await publicClient.getBlock()).timestamp + 180n,
16
+ });
17
+ const spend = await market.getSpendStatus({
18
+ key, owner,
19
+ amount0: quote.data.limit0, amount1: quote.data.limit1,
20
+ });
21
+ // UI 展示每个 token 的余额、现有 allowance 和最大支出。
22
+ // 对需要授权的 token,amount 填其完整 requiredAmount,不能只填缺口。
23
+ const approval = await market.prepareApproval({
24
+ key, currency: 0, amount: quote.data.limit0,
25
+ });
26
+ // 展示 approval.request;用户确认后显式执行:
27
+ const approvalHash = await market.send(approval);
28
+ const approvalResult = await market.waitForTransaction(approvalHash);
29
+ if (approvalResult.status !== "confirmed") throw new Error("Approval reverted");
30
+ // currency1 若也缺授权,同样单独准备、确认、发送并等待回执。
31
+ // 授权后重新获取 quote 和 spend,确认新限额仍被余额和 allowance 覆盖。
32
+ const freshQuote = await market.previewDeposit({
33
+ ...quote.data,
34
+ deadline: (await publicClient.getBlock()).timestamp + 180n,
35
+ });
36
+ const deposit = await market.prepareDeposit(freshQuote);
37
+ // 再展示交易,显式发送并核对 receipt/events。
38
+ const hash = await market.send(deposit);
39
+ const result = await market.waitForTransaction(hash);
40
+ ```
41
+
42
+ 可编译的消费端示例:`examples/consumer/src/market.ts`。`marketLiquidityWorkflow` 返回同一个 client 以及显式广播 helper;不在构造或读取时签名。
43
+
44
+ - `previewDeposit(params)` / `previewWithdraw(params)`:参数包括完整 PoolKey、上下 tick、liquidity、slippageBps、deadline、可选 validForSeconds。返回不可变 `{block,data}`,含本金 amount0/1、limit0/1、rangeId、sqrtPriceX96、expiresAt。以 PoolManager 为数据源,不要求 Registry 仍列出该池。
45
+ - `getSpendStatus({key,owner,amount0,amount1,...block?})`:同时读取两币余额和对该 Hook 的 allowance,不隐式批准。
46
+ - `prepareApproval({key,currency,amount,validForSeconds?})`:currency 只允许 0 或 1;精确替换额度,限 uint128,与存入最大金额边界一致。amount=0 显式撤销 / 清零。需要先清零的 token 必须先单独发送并确认零授权,再准备新额度;不自动扩大授权、不自动发送第二笔。
47
+ - `prepareDeposit(quote)`:只接受当前客户端生成的 deposit quote,检查余额和 allowance 覆盖 maxima;份额归当前钱包,无存入 recipient 参数。
48
+ - `prepareWithdraw(quote,to)`:只接受当前客户端的 withdraw quote,limit 为最低本金;暂停不阻止此操作。手续费会在合约内结算到 owed,但不会自动支付。
49
+ - `prepareWithdrawAndClaim(quote,to,maxFeeBps)`:撤出并本人领取;暂停时拒绝,分账费上限在合约内验证。
50
+ - `prepareClaim({key,tickLower,tickUpper,to,maxFeeBps,validForSeconds?})`:本人领取,不依赖 Registry 或池状态 Reader;零份额但仍有 owed 也交由合约处理。不得把 settledOwed=0 当作没有可领手续费。合约可能成功且没有 FeesClaimed 事件。
51
+ - `send(prepared)`:只发送本客户端保存的不可变 canonical 请求。来源伪造、跨客户端、重复提交、链或账户变化均拒绝。发送前重新核对身份、有效期并模拟。
52
+ - `waitForTransaction(hash,{timeout?})`:仅接受该实例发送的 hash;使用 chain.confirmations,返回 confirmed / reverted 和实际 receipt。事件只接纳对应 Hook、owner、range 和 recipient。存入 / 撤出成功必须有匹配事件;领取成功可能没有支付。repriced 替换返回实际 hash,cancelled / replaced 抛出 `MarketTransactionReplacedError`;超时保留 hash 可再次等待。
53
+
54
+ 持有这个 client 实例直到交易完成。实例重建后可用 viem 根据已保存 hash 获取原始回执,但新的实例不会将陌生 hash 当作自身准备的动作。广播状态不明确时先查询已有 hash / 钱包交易历史,不要盲目重复存入。
55
+
56
+ ## 精度和保护边界
57
+
58
+ 所有金额均为 token 原始单位、liquidity 为 ERC-6909 LP 份额,不是 token 数量。`getMarketSqrtPriceAtTick` / `getMarketTickAtSqrtPrice` 使用 v4 tick 边界;`getMarketPriceRatio` 返回精确分数,表示已应用 decimals 的 currency1/currency0 价格。`getMarketAmountsForLiquidity` 和 `getMarketLiquidityForAmounts` 保留整数精度,无浮点运算。
59
+
60
+ 存入本金向上取整,最大支出再按 `ceil(amount * (10000 + bps) / 10000)` 计算。撤出本金向下取整,最低回收按 `floor(amount * (10000 - bps) / 10000)` 计算。bps 允许 0..9999;小额或单边仓位的一侧 min 可能为零,调用方应展示实际保护值。这里是 token 金额容忍度,不是价格或 tick 容忍度;价格跨边界时交易可能回滚。Fee-on-transfer / rebasing 等特殊 token 未验收。
61
+
62
+ 数学基于以下 MIT 库,并通过本地 EVM 中编译后的库做差分:
63
+
64
+ - v4-core `46c6834698c48bc4a463a86d8420f4eb1d7f3b75`:TickMath、SqrtPriceMath。
65
+ - v4-periphery `9969eec44cfdf07e24b41de47f40276a58401976`:LiquidityAmounts。
66
+ - 许可原文见包内 `THIRD_PARTY_NOTICES.md`。
67
+
68
+ 预览和 prepared 的有效期使用链上 timestamp,默认 60 秒,可设置 1..300 秒;LP preview 的 expiresAt 是 deadline 与快照寿命的较小值。准备 / 发送均拒绝过期、区块 hash 改变或时间倒退。存取的 deadline、max/min、组合领取的 maxFeeBps 进入 calldata,交易在 mempool 中等待也受合约限制;单独 `claimFees` 和 `approve` 没有链上 deadline,SDK 的有效期只能限制广播时机。模拟不是执行保证,签名期间 / 广播后状态仍可能改变。
69
+
70
+ maxFeeBps 是用户容忍的有效分账上限(0..2000),不是固定手续费或最终净收益预言;合约历史策略决定本次有效费率。已结算净额只通过 `getPosition().data.settledOwed0/1` 展示;完整实时收益估算请使用 P4 的 `estimateClaimableFees`,见 [收益说明](./earnings.md)。
71
+
72
+ 准备和发送检查选中 Hook、PoolManager、authority、ClaimFeePolicy 的运行时代码与绑定;RwaRamp 还检查 Calendar 的代码、Hook 绑定及 controller。可选 Reader 或 Registry 故障不阻止直接本金退出。RPC 与合约错误保留 cause:读操作为 MarketReadError,交易阶段为 MarketTransactionError(stage=simulate/send/receipt)。没有静默降级或自动换链。
73
+
74
+ ## 验证范围
75
+
76
+ 参见 `tooling/market-integration/README.md` 的真实 EVM 复现方式。Ramp、RampETH 双 ERC-20、RampETH 原生币与 RwaRamp 双 ERC-20 的本地闭环均已验证,详见 [P5 验收矩阵](./variants.md)。后续 Arc/BSC 三组文档测试池已通过线上只读和本地 fork 存取/收益对账,见[前端指南验证范围](frontend.md#验证范围)。尚未执行主网资金交易,也没有自动开放主网 capability。
77
+
78
+
79
+ ## RampETH 原生币
80
+
81
+ 只有 `kind: "ramp-eth"` 可使用 `currency0 = zeroAddress`,且写操作同时要求 `liquidity` 与 `nativeLiquidity` 为 `verified`。Ramp / RwaRamp 拒绝 native;包装币仍按普通 ERC-20 处理。读和预览不会自动提升能力状态。
82
+
83
+ - 存入 `request.value = preview.data.nativeValue = limit0`,合约实际花费后把剩余原生币退给存入方;`estimatedNativeRefund = limit0 - amount0` 仅是预览价格下的估计。其他操作和所有双 ERC-20 池的 value 均为零。
84
+ - 原生币 `getSpendStatus` 返回 `kind: "native"`、`allowance: null`、`needsApproval: false`。`sufficientBalance` 只比较本金;`prepareApproval` 对原生币明确拒绝,currency1 仍需普通授权。
85
+ - 原生币池的交易准备会模拟、估算 gas,按向上取整的 125% gas 设置限额,绑定 EIP-1559 `maxFeePerGas` / `maxPriorityFeePerGas`,并检查 `balance >= value + gas * maxFeePerGas`。发送前再次检查余额。不会把预计退款用于支付前置预算,也不会用零 gas 预算静默降级;此路径需要 RPC 支持 EIP-1559 费率及 gas 估算。估算不保证状态变化后的交易成功,重新准备可更新预算。
86
+ - 未发布 0.1.0 的公共类型已扩展:`MarketTransactionRequest.value` 为 bigint,新增可选 gas 预算字段;spend allowance 为 `bigint | null`。消费端应按 `kind` 展示授权,按上述预算显示原生币前置余额。
87
+ - 合约接收方拒绝退款时整个 deposit 回滚;退款为零时不调用接收函数。撤出和领取使用 PoolManager 直接支付收款人;拒收时份额、owed 和转账整体回滚。原生币余额对账需扣除发送方实际 gas,而收款人独立于发送方时可直接对比事件金额。
@@ -0,0 +1,73 @@
1
+ # P6 Keeper 与管理接入
2
+
3
+ `getManagementAuthorization(action, account, { when? })` 在固定区块返回权限和编码,`prepareManagement(action, { execution?, validForSeconds? })` 准备交易。默认 execution 为 `{ mode: "direct" }`。它们只接受类型明确的 `MarketManagementAction`,不接受任意目标 / calldata;广播仍显式调用同一 client 的 `send` 和 `waitForTransaction`。
4
+
5
+ 选中 Hook 必须配置 `capabilities.management = "verified"`。此验收记录独立于 liquidity / nativeLiquidity,管理账户无需开放 LP 能力。SDK 核验相关合约代码、authority、Hook / ClaimFeePolicy / Calendar 绑定;Registry / Reader 操作额外核验目标及其绑定。Reader 停用、Registry 下架 / 重新上架不读取 Hook,保留处置故障 Hook 的路径。setHook 只接受已在部署配置登记的 Hook,并要求显式 expectedCodeHash 与审核配置一致,实际代码也必须匹配;不会把现场读取的哈希自动当作可信值。
6
+
7
+ ## 操作和作用域
8
+
9
+ 所有 action 都带 `key` 以选择已审核 Hook。以下 `kind` 为精确 API 名称:
10
+
11
+ | kind | 参数与语义 |
12
+ | --- | --- |
13
+ | pokeFee | fee0For1 / fee1For0 为整数 pips,允许一侧零表示该方向不覆盖,不能同时为零;ttl 为 1..259200 秒。核验链上 floor / cap;执行时仍受自主费率的 50% 下限约束 |
14
+ | clearPoke | 清除两方向临时覆盖 |
15
+ | setPoolAsymmetry | premiumPips、premiumZeroForOne;持久方向加价,不能超过当前 cap-floor |
16
+ | setCryptoPoolConfig | floorPips >=100、flatPips >=floor、cap >=flat 且不超过 Hook ABSOLUTE_MAX_FEE;仅 Ramp / RampETH |
17
+ | setRwaPoolConfig | config: MarketRwaFloorConfig、cap;仅 RwaRamp,显式选择带 FloorConfig tuple 的重载,验证尖峰、下降窗口与收盘曲线约束 |
18
+ | setSessionHours | openSec / closeSec 为本地日内秒;遵守合约的默认时间 ±30 分钟边界 |
19
+ | setDayOverrides | year / month / days: [{day,status:"closed"或"open"}];替换整个月,空数组清除。最多10个不重复日期;SDK Gregorian 日期域为1970..9999 |
20
+ | setDstMode | mode: auto / est / edt;合约要求固定模式与当前 AUTO 偏移一致,实际执行时再次约束 |
21
+ | setEarlyClose | year / month / day / closeSec;0清除,否则不得晚于当前收盘,距当前开盘至少3小时 |
22
+ | setPaused / setPoolPaused | duration: 0..604800 秒;0解除。前者作用于整个 Hook,后者作用于 key 对应池 |
23
+ | setClaimFee | bps:0..2000、recipient、必须显式提供 settleFirst: Address[];比例按池配置,recipient 却是整个 Hook 的全局收款人 |
24
+ | setClaimFeeBps | 只改本池 bps,不轮换收款人;要求已有收款人 |
25
+ | sweepClaimFee | currency;任何账户均可触发,不需要 AccessManager 角色,但合约暂停时拒绝。始终支付给链上全局收款人 |
26
+ | register / deregister / reregister | 针对部署配置的 Registry;登记与资金能力独立,register 已存在时回滚 |
27
+ | setHook / disableHook | 针对配置的 Reader;setHook 必填 expectedCodeHash,Reader kind 根据已审核 Hook 变体决定 |
28
+
29
+ 日历操作全部发送给 RwaRamp,由 Hook 转发到 Calendar;费用比例操作全部发送给 Hook,不直接写 ClaimFeePolicy。日期纯工具 `marketDaysInMonth` / `packMarketDayOverrides` 从根入口导出。金额单位与普通 LP 相同,费率 pips 与分账 bps 不可混用。
30
+
31
+ ## 权限与延迟交易
32
+
33
+ ```ts
34
+ const action = {
35
+ kind: "pokeFee" as const,
36
+ key,
37
+ fee0For1: 2000,
38
+ fee1For0: 0,
39
+ ttl: 300,
40
+ };
41
+ const permission = await client.getManagementAuthorization(action, account);
42
+ // permission.data: permission / delay / operationId / scheduledAt / direct / schedule / execute
43
+ // denied: 不应发送;immediate: 可准备 direct;delayed: 必须先排期。
44
+ const queued = await client.prepareManagement(action, {
45
+ execution: { mode: "schedule" },
46
+ });
47
+ const scheduled = await client.waitForTransaction(await client.send(queued));
48
+ if (scheduled.status !== "confirmed") throw new Error("Schedule reverted");
49
+ // 保存完整 action、chain/deployment、账户、operationId、hash。
50
+ // 等链上 getManagementAuthorization(...).data.scheduledAt 到期后重新准备:
51
+ const execution = await client.prepareManagement(action, {
52
+ execution: { mode: "execute" },
53
+ });
54
+ const result = await client.waitForTransaction(await client.send(execution));
55
+ ```
56
+
57
+ 示例展示 delayed 分支。immediate 直接 `prepareManagement(action)`;拒绝将 delayed 假装成 direct。schedule / execute 发送到 AccessManager,真正的业务 target / data 保存在 context.management 中。schedule 的 when 默认为0,让合约按落块时间加 delay 排期;显式 when 必须至少达到当前区块 timestamp+delay。execute 要求该账户和完整 calldata 对应的有效排期已到期;getSchedule=0 可能表示未排期、已执行、取消或过期。
58
+
59
+ 排期成功只表示 AccessManager 接受排期,不代表业务已执行,也不保证到期时业务状态有效。准备与发送都会重新验证权限、参数相关的当前边界、部署身份、账户、链和请求寿命,并模拟本次真正发送的请求;schedule 时模拟的是 schedule,本次操作不会越过权限去伪装模拟未来业务。延迟完成后必须重新 prepare execute,不能让普通 prepared 对象存活数小时。执行失败会回滚排期消费,可在修正状态后重新准备执行。TTL 从业务执行时间开始,不从排期时间开始。
60
+
61
+ `getManagementAuthorization` 的 direct / schedule / execute 提供所需编码,SDK 的 `send` 仍只接受本实例准备的对象。日后重建实例可根据保存的 action 和当前账户重新查询并 prepare execute。取消或 AccessManager 角色治理未封装为 Market 管理动作,可通过导出的 AccessManager ABI 显式处理;本阶段不交付 Keeper 服务或自动调度器。
62
+
63
+ ## 费用收款人轮换
64
+
65
+ `settleFirst` 没有默认值,也不自动填入 key 的两币。它列出的已累计协议债务先支付给旧收款人,然后才轮换全局收款人;遗漏币种的债务归新收款人。列表可能需要包括同一个 Hook 其他池的币种。`pendingClaimFeeCurrencyCount` 仅是计数,不是完整币种枚举;列表为空是调用方明确选择全部遗留债务转给新收款人。
66
+
67
+ 这一步支付的是已经累计的协议 claim fee,不会遍历所有 LP 区间把尚未同步的收益强制结算。旧收款人拒收、币种支付失败或其他校验失败会回滚整笔交易,包含已支付的前序币种、债务扣减、收款人和比例更新。新收款人不能为零 / Hook / PoolManager;合约仅允许 bps=0 且尚未设置收款人时保留零地址。
68
+
69
+ ## 回执和验收
70
+
71
+ 回执新增 `managementEvents`,仅解码业务目标或配置的 AccessManager 发出的日志;AccessManager 日志匹配 operationId。schedule / execute 成功必须有相应 OperationScheduled / OperationExecuted;零债务 sweep 可以成功且没有支付事件。完整原始 receipt 仍保留,管理事件 args 为只读 Record<string, unknown>,消费端按 eventName 校验字段后展示。
72
+
73
+ 真实本地测试覆盖三个 Hook 的管理配置、方向费率、暂停、Registry / Reader、RWA 日历和重载选择;无角色拒绝、延迟排期 / 到期执行 / 重复执行拒绝 / 撤销权限 / 排期过期;执行失败保留排期;显式遗漏币种的债务归属与原生币拒收导致的轮换回滚。无主网写入或自动 capability 提升。P7 仍负责最终发布验收。
@@ -0,0 +1,30 @@
1
+ # Pool state read boundary
2
+
3
+ P2 supports two explicit readers. Neither is selected by guessing the chain or calling a nonexistent `PoolManager.slot0()` getter. All queries use the same block as Registry reads; code hashes are checked against caller-reviewed deployment records at that block.
4
+
5
+ ```ts
6
+ // StateView is preferred when a reviewed deployment exists.
7
+ poolStateReader: {
8
+ kind: "state-view",
9
+ contract: { address: reviewedStateView, runtimeCodeHash: reviewedRuntimeHash },
10
+ }
11
+
12
+ // Otherwise opt into the specific audited PoolManager layout.
13
+ poolStateReader: { kind: "extsload", layout: "uniswap-v4-v1" }
14
+ ```
15
+
16
+ An omitted reader causes `getPoolState` to reject with `MarketFeatureUnavailableError`. A configured StateView failure never silently falls back to direct storage. The expected PoolManager hash is always checked; StateView additionally checks its own runtime hash and `poolManager()` binding. These checks establish identity against supplied records, not an independent audit of code or layout.
17
+
18
+ ## Pinned interface and layout sources
19
+
20
+ - [Uniswap StateView at 9969eec44cfdf07e24b41de47f40276a58401976](https://github.com/Uniswap/v4-periphery/blob/9969eec44cfdf07e24b41de47f40276a58401976/src/lens/StateView.sol): `getSlot0`, `getLiquidity` and its PoolManager binding.
21
+ - [StateLibrary at 46c6834698c48bc4a463a86d8420f4eb1d7f3b75](https://github.com/Uniswap/v4-core/blob/46c6834698c48bc4a463a86d8420f4eb1d7f3b75/src/libraries/StateLibrary.sol): pool mapping slot 6 and liquidity offset 3.
22
+ - [Slot0 at the same core revision](https://github.com/Uniswap/v4-core/blob/46c6834698c48bc4a463a86d8420f4eb1d7f3b75/src/types/Slot0.sol): packed price, signed tick, protocol fee and stored LP fee.
23
+
24
+ These external source revisions document the optional adapter, not the missing `lib/` revision of the upstream contract checkout. The caller must establish that its pinned PoolManager uses this layout before opting in. No Arc default or blanket claim of compatibility is shipped.
25
+
26
+ The adapter computes `keccak256(abi.encode(poolId, uint256(6)))`, reads this slot and offset 3 through `extsload(bytes32[])`, and decodes the signed tick separately from fee fields. Minimal handwritten interfaces live in `src/abi/v4.ts`; the eight generated Ramp ABI snapshots are unchanged.
27
+
28
+ `activeLiquidity` is in liquidity units. `storedLpFeePips` is the pool's stored fee, not the Hook's current direction-dependent execution fee; use `getCurrentFees` for the latter. `protocolFeePacked` retains v4's packed directional protocol rates. `sqrtPriceX96=0` yields `initialized=false`; it does not invent a price, TVL, APR or tradeability.
29
+
30
+ Tests cover packed negative ticks, field isolation, storage offsets, malformed responses, zero state, bounded reads, code/binding mismatch and equivalent StateView/extsload results. P2's initial public Arc RPC call timed out. Retrying with Launchpad's `https://rpc.arc-scan.org` returned HTTP 530 / error 1033 on 2026-09-30; those P2 checks were local evidence only. Later R4–R5 checks used an explicit Arc Blockdaemon RPC and a BSC RPC to read the three documented test pools and complete local-fork fund-flow reconciliation. This does not attest PoolManager source identity or mainnet transaction execution; see [frontend verification scope](frontend.md#验证范围).
package/docs/pools.md ADDED
@@ -0,0 +1,118 @@
1
+ # 池子列表与市场详情
2
+
3
+ `client.listPools()` 查询当前部署的 PoolRegistry,Arc 与 BSC 使用相同接口、不同链配置。调用方先执行 `verifyDeployment()`;读取无需钱包。前端创建客户端见[接入指南](frontend.md)。
4
+
5
+ ## 参数与分页
6
+
7
+ | 参数 | 类型 | 默认值 / 含义 |
8
+ | --- | --- | --- |
9
+ | `offset` | `bigint` | `0n`,Registry 的扫描起点 |
10
+ | `limit` | `number` | `20`,每次扫描 1–100 条登记记录 |
11
+ | `activeOnly` | `boolean` | `false`;为 true 时过滤停用记录 |
12
+ | `blockNumber` | `bigint` | 未提供时选择最新已出块区块 |
13
+ | `blockHash` | `Hex` | 可选;必须同时提供 blockNumber,用于续读身份核对 |
14
+
15
+ ```ts
16
+ import type { MarketClient, MarketPoolPage, MarketSnapshot } from "@masterpeach/market-sdk";
17
+
18
+ export function firstPoolPage(client: MarketClient) {
19
+ return client.listPools({ offset: 0n, limit: 20, activeOnly: true });
20
+ }
21
+
22
+ export function nextPoolPage(
23
+ client: MarketClient,
24
+ previous: MarketSnapshot<MarketPoolPage>,
25
+ ) {
26
+ if (previous.data.nextOffset === null) return null;
27
+ return client.listPools({
28
+ ...previous.block,
29
+ offset: previous.data.nextOffset,
30
+ limit: 20,
31
+ activeOnly: true,
32
+ });
33
+ }
34
+ ```
35
+
36
+ `limit` 是扫描量,不是过滤后返回数量。某页可能 `pools=[]` 且仍有 `nextOffset`;只有 `nextOffset === null` 才表示扫描结束。`totalCount` 是该区块 Registry 的总登记数,不是 active 池数量。
37
+
38
+ 续页保留第一页的区块,避免分页间数据漂移;主动刷新时清空旧分页并从最新区块重新查询。如果节点不能提供该历史区块或检测到重组,展示错误并允许重新加载,不能拼接新旧区块的结果。接口读取每批最多 8 个池,不依赖 Multicall3。
39
+
40
+ ## 返回数据
41
+
42
+ 返回 `MarketSnapshot<MarketPoolPage>`,即 `{ block, data }`。
43
+
44
+ | 字段 | 类型 | 含义 |
45
+ | --- | --- | --- |
46
+ | `block.chainId` | `number` | 5042 或 56 等实际查询链 |
47
+ | `block.deploymentId` | `string` | 配置的部署标识 |
48
+ | `block.blockNumber` | `bigint` | 本次快照区块 |
49
+ | `block.blockHash` | `Hex` | 区块身份,读取结束时复核 |
50
+ | `block.timestamp` | `bigint` | 区块 Unix 时间,单位秒 |
51
+ | `data.pools` | `readonly MarketPool[]` | 本页符合条件的池 |
52
+ | `data.totalCount` | `bigint` | 未按 activeOnly 过滤的总登记数 |
53
+ | `data.nextOffset` | `bigint \| null` | 下一次扫描起点 |
54
+
55
+ 每个 `MarketPool` 包含:
56
+
57
+ | 字段 | 类型 | 含义 |
58
+ | --- | --- | --- |
59
+ | `poolId` | `Hex` | 完整 PoolKey 的哈希 |
60
+ | `active` | `boolean` | Registry 登记启用状态 |
61
+ | `hookKind` | `"ramp" \| "ramp-eth" \| "rwa-ramp" \| null` | 当前配置识别到的 Hook 类型;null 为未配置支持的 Hook |
62
+ | `key.currency0` / `currency1` | `Address` | 已按地址顺序排列的两币地址,不一定是业务 base/quote 顺序 |
63
+ | `key.fee` | `number` | v4 动态费率标记 `8388608`,不是当前 swap 费率 |
64
+ | `key.tickSpacing` | `number` | tick 间距 |
65
+ | `key.hooks` | `Address` | 该池的 Hook 地址 |
66
+
67
+ 池没有独立 Pair 合约地址。跨链用 `(chainId, poolId)` 标识市场,缓存还应包含 deploymentId。不能仅按代币对聚合不同 Hook、fee 或 spacing 的池。
68
+
69
+ `active=true` 不等于未暂停、已有流动性或具备写权限;`active=false` 也不表示本金不能退出。发现未知 Hook 时可展示其基础记录,但不调用 SDK 的该 Hook 业务方法。RWA `closed` 仅是费率时段,不能直接当成“禁止交易”。
70
+
71
+ ## 详情与展示字段
72
+
73
+ 列表本身不附带 token symbol、decimals、logo、价格、TVL、APR、成交量、用户仓位或收益。这些字段应按实际页面需要补充,不应对每行自动重复执行全量查询。
74
+
75
+ | 页面字段 | 获取方式 / 边界 |
76
+ | --- | --- |
77
+ | 双向当前费率、全局/池暂停状态 | `getCurrentFees(poolId, options)`;读取 `fee0For1Pips` / `fee1For0Pips` |
78
+ | 费率构成 | `getFeeBreakdown(poolId, zeroForOne, options)`;需要配置 Reader |
79
+ | RWA 时段 | `getSession(poolId, options)`;非 RWA 的 session 为 null |
80
+ | sqrtPriceX96、tick、活动流动性 | `getPoolState(poolId, options)`;需要显式 StateView / extsload 配置 |
81
+ | 两币相对价格 | 使用 `getMarketPriceRatio` 和真实 decimals;不是美元价格 |
82
+ | 名称、symbol、精度、图标 | 前端 token 元数据服务或 ERC-20 查询;原生币单独处理 |
83
+ | TVL、APR、历史成交量、USD 估值 | 索引服务及价格数据,SDK 没有直接接口;activeLiquidity 不是 TVL |
84
+ | 用户仓位 | 索引提供区间标识,再调用 `getPosition`,见[仓位说明](positions.md) |
85
+
86
+ ```ts
87
+ import type { MarketClient, MarketPool } from "@masterpeach/market-sdk";
88
+
89
+ export async function loadPoolDetails(client: MarketClient, selected: MarketPool) {
90
+ if (selected.hookKind === null) throw new Error("Unsupported Hook");
91
+ const pool = await client.getPool(selected.poolId);
92
+ const [fees, state, session] = await Promise.all([
93
+ client.getCurrentFees(selected.poolId, pool.block),
94
+ client.getPoolState(selected.poolId, pool.block),
95
+ client.getSession(selected.poolId, pool.block),
96
+ ]);
97
+ return { pool, fees, state, session };
98
+ }
99
+ ```
100
+
101
+ 上例要求配置 poolStateReader;缺少时该查询会报错,不能把缺失状态显示成零价格。费率 pips 为百万分之一:`100 pips = 0.01%`,展示百分数为 `pips / 10000`。收益分账使用 bps,不能与 pips 混用。
102
+
103
+ ## 测试资产识别
104
+
105
+ ```ts
106
+ import { marketTestPools } from "@masterpeach/market-sdk";
107
+ import type { Hex } from "viem";
108
+
109
+ export function findKnownTestPool(chainId: number, poolId: Hex) {
110
+ return marketTestPools.find(
111
+ (pool) => pool.chainId === chainId && pool.poolId.toLowerCase() === poolId.toLowerCase(),
112
+ );
113
+ }
114
+ ```
115
+
116
+ 目录覆盖 Arc tBTC/tUSDC、BSC tBNB/tUSDT 和 tQQQ/tUSDT,包含 PoolKey、币种精度、组件地址与来源区块。三组均为无价值、可公开 mint 的测试 ERC-20。tBNB 不是原生 BNB;Arc 的 tUSDC/6 位也不是 gas 所用原生 USDC/18 位。
117
+
118
+ 共享 Registry 可以混合真实资产与测试资产。未命中有限的测试目录只能标记为“未分类”,不能据此判定为真实资产。目录不是实时池状态,也不是可信部署配置。
@@ -0,0 +1,94 @@
1
+ # 我的仓位、收益与提取流动性
2
+
3
+ SDK 支持查询已知区间的用户份额、收益以及存取操作,**不提供按钱包自动枚举全部历史仓位的接口**。LP 是 Hook 管理的 ERC-6909 区间份额,不是 ERC-721 tokenId。
4
+
5
+ ## 仓位列表的数据来源
6
+
7
+ 前端或索引服务维护候选 `(chainId, deploymentId, hook, rangeId, owner)`,再调用 SDK 读取当前链上状态。候选记录至少考虑存入、退出、份额转入/转出;仅保存当前浏览器的 deposit 回执会遗漏其他设备操作及别人转入的份额。
8
+
9
+ 索引合约日志时须处理分页、回滚/重组和重复事件。链上快照才是余额与收益依据,不能把历史事件累计值直接当当前可提金额。一个 Hook 可服务多个池,多个用户可持有同一 rangeId;候选记录不要只按 rangeId 或代币对去重。
10
+
11
+ ## 查询参数和返回字段
12
+
13
+ `getPosition({ hook, rangeId, owner, blockNumber?, blockHash? })` 返回 `{ block, data }`。
14
+
15
+ | data 字段 | 含义 |
16
+ | --- | --- |
17
+ | `owner` | 被查询钱包 |
18
+ | `shares` | bigint 用户 LP 份额,不是 currency0/1 数量,也不能套代币 decimals 展示 |
19
+ | `range` | 区间状态,见下文 |
20
+ | `settledOwed0/1` | 已结算的净手续费欠款,token 最小单位;不是完整实时可领取金额 |
21
+ | `checkpoint0X128/1X128` | 用户净收益检查点,用于精确记账 |
22
+ | `remainder0X128/1X128` | Q128 余数,不能当成整数 token 金额相加 |
23
+
24
+ `range.status="unset"` 时仅有 hook / rangeId,不能读取 key、ticks 或估算该区间收益。`status="set"` 时还包含 poolId、key、tickLower、tickUpper、state、globalPaused、poolPaused、effectiveClaimFeeBps。`state` 包含 totalShares、accFee0X128/1X128、collectionSeq 和 resetSeq。
25
+
26
+ 不要只用 `shares > 0` 过滤仓位:全部退出后仍可能有历史净收益待领取;已建立但 totalShares=0 的区间也不同于 unset。重建区间后 resetSeq 可能变化,不能沿用旧缓存的收益增长基线。
27
+
28
+ ```ts
29
+ import type { MarketClient } from "@masterpeach/market-sdk";
30
+ import type { Address } from "viem";
31
+
32
+ export async function loadPosition(
33
+ client: MarketClient,
34
+ hook: Address,
35
+ rangeId: bigint,
36
+ owner: Address,
37
+ ) {
38
+ const position = await client.getPosition({ hook, rangeId, owner });
39
+ if (position.data.range.status === "unset") return { position, earnings: null };
40
+ const earnings = await client.estimateClaimableFees({
41
+ hook, rangeId, owner, ...position.block,
42
+ });
43
+ return { position, earnings };
44
+ }
45
+ ```
46
+
47
+ 收益结果 `earnings.data.token0/1.claimable` 为完整估算,包含 settled / synchronized / pending 三部分并处理余数;`hasClaimableAmount` 表示是否有整数可领取额。链上状态可能在查询后变化,准备交易仍需模拟。详细记账语义见[收益与权限](earnings.md)。
48
+
49
+ “我的仓位”列表应为每个条目保留加载/错误状态,单个池异常不要清空其他仓位;RPC 失败不能显示成余额为零。页面批量查询应限制并发。
50
+
51
+ ## 部分提取、全部提取和领取
52
+
53
+ 使用用户份额计算提取比例。以下只准备交易,不发送;`withdrawBps=10000` 为全部提取,`5000` 为一半。整数舍入后为零时不应提交。
54
+
55
+ ```ts
56
+ import type { MarketClient } from "@masterpeach/market-sdk";
57
+ import type { Address } from "viem";
58
+
59
+ export async function preparePositionExit(
60
+ client: MarketClient,
61
+ params: {
62
+ hook: Address; rangeId: bigint; owner: Address; to: Address;
63
+ withdrawBps: number; slippageBps: number; deadline: bigint;
64
+ // undefined 表示只提本金;传入值表示同时领取并限制分账费。
65
+ maxFeeBps?: number;
66
+ },
67
+ ) {
68
+ if (!Number.isInteger(params.withdrawBps) || params.withdrawBps < 1 || params.withdrawBps > 10000)
69
+ throw new Error("Invalid withdrawal percentage");
70
+ const position = await client.getPosition({
71
+ hook: params.hook, rangeId: params.rangeId, owner: params.owner,
72
+ });
73
+ const range = position.data.range;
74
+ if (range.status !== "set") throw new Error("Unknown range");
75
+ const liquidity = position.data.shares * BigInt(params.withdrawBps) / 10000n;
76
+ if (liquidity === 0n) throw new Error("No withdrawable shares at this percentage");
77
+ const quote = await client.previewWithdraw({
78
+ key: range.key, tickLower: range.tickLower, tickUpper: range.tickUpper,
79
+ liquidity, slippageBps: params.slippageBps, deadline: params.deadline,
80
+ });
81
+ return params.maxFeeBps === undefined
82
+ ? client.prepareWithdraw(quote, params.to)
83
+ : client.prepareWithdrawAndClaim(quote, params.to, params.maxFeeBps);
84
+ }
85
+ ```
86
+
87
+ 此示例的 owner 必须是当前连接钱包;它不是替其他钱包提取的授权接口。deadline 使用链上时间生成的 Unix 秒,slippageBps 是本金容忍度。UI 应展示 quote 的最低到账 limit0/1;maxFeeBps 是 LP 收益分账上限,范围 0–2000,不是提取本金的费率。
88
+
89
+ - `prepareWithdraw` 只提本金;手续费结算到 owed,后续单独领取。暂停期间仍支持本金退出。
90
+ - `prepareWithdrawAndClaim` 同时退出和领取,暂停时不可用,不能作为暂停期间唯一退出入口。
91
+ - `prepareClaim({ key, tickLower, tickUpper, to, maxFeeBps })` 单独领取;不要求先提取本金。领取成功可能没有支付事件,例如金额不足一个最小单位。
92
+ - `prepareClaimFeesFor` 是单独的代领能力,需要区间 claim operator 授权;普通份额 operator 不自动取得代领权限。
93
+
94
+ 交易确认后刷新用户份额、收益、币余额及 allowance。持有旧 hash 的 pending/超时交易先查回执,不要再次发起相同资金操作。[发送与回执处理](frontend.md#交易发送与界面状态)使用同一个 client 实例。
@@ -0,0 +1,24 @@
1
+ # P5 变体实现与部署验收
2
+
3
+ ## 实现矩阵
4
+
5
+ | 本地真实合约 | 双 ERC-20 LP | 原生币 LP | 净费用对账 | 本金限制与暂停退出 |
6
+ | --- | --- | --- | --- | --- |
7
+ | Ramp | 通过 P3 / P4 | 合约不支持,SDK 拒绝 | 通过 | 通过 |
8
+ | RampETH | 通过 P5,value=0 | 通过 P5,currency0=0 | 通过 | 通过 |
9
+ | RwaRamp | 通过 P5 | 合约不支持,SDK 拒绝 | 通过 | 通过 |
10
+
11
+ P5 验证存入、swap 产生收益、领取、部分组合撤出、暂停后完整本金退出;拒绝错误 max/min、过期 deadline、超额份额、分账费上限和错误 msg.value。RampETH 另核对预付款退款、发送方 gas、零退款合约账户,以及退款 / 领取 / 撤出拒收的真实 reverted 回执和状态回滚。测试工具的合约账户只用于检验退款边界,不表示 SDK 实现了智能账户发送适配。
12
+
13
+ RwaRamp 复用同一 LP 协议;closed 表示费率时段,不禁止 LP 操作。真实 Calendar / Reader 对照覆盖开盘前一秒 / 开盘、收盘前一秒 / 收盘、春秋 AUTO DST、强制开放 DST 周日的本地日期边界、early close,以及双方向 poke 到期前一秒 / 到期 / 到期后一秒。测试使用固定区块配置对假设时间做预览,不代表查询那一时刻的历史配置。Calendar 的 AUTO 按 DST 周日 UTC 日期切换,这是当前合约语义;节假日表仅覆盖合约内置的 2026–2030 年。
14
+
15
+ ## 部署矩阵
16
+
17
+ | 环境 | 代码 / 绑定身份 | 资金验收 | capability |
18
+ | --- | --- | --- | --- |
19
+ | 独立本地 Anvil / 31337 | 本次固定源码编译并部署 | 上述矩阵通过 | 仅测试 fixture 声明 verified |
20
+ | Arc / 用户提供的目标部署 | 尚需审核地址、runtime hash、布局及依赖 | 未执行目标链资金验收 | 不自动提升,没有新增可信主网预置 |
21
+
22
+ 实现支持不等于目标部署已验收。原生币写路径必须显式配置 `liquidity=verified` 和 `nativeLiquidity=verified`;Arc 没有已验收 RampETH 时保留 native disabled/unverified,不影响单独验收 ERC-20 能力。用户指定的 Launchpad RPC 保持 `https://rpc.arc-scan.org`,当前未取得恢复证据;没有切换链或执行主网写操作。
23
+
24
+ 复现见 [本地 EVM 工作流](../../../tooling/market-integration/README.md)。生产源码和 ABI 快照未改动,无新增依赖。管理写 API 属于后续 P6;本阶段测试直接调用管理合约仅用于搭建验收状态。