@masterpeach/launchpad-sdk 0.1.0-next.0 → 1.0.0
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.
- package/README.md +345 -178
- package/README.zh-CN.md +300 -154
- package/dist/abi/index.cjs +20947 -1433
- package/dist/abi/index.cjs.map +1 -1
- package/dist/abi/index.d.cts +16370 -1159
- package/dist/abi/index.d.ts +16370 -1159
- package/dist/abi/index.js +41 -13
- package/dist/chunk-DZMMNDQL.js +22254 -0
- package/dist/chunk-DZMMNDQL.js.map +1 -0
- package/dist/index.cjs +24445 -2739
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2978 -712
- package/dist/index.d.ts +2978 -712
- package/dist/index.js +4465 -1200
- package/dist/index.js.map +1 -1
- package/package.json +18 -17
- package/dist/chunk-SH2Y5F5L.js +0 -2754
- package/dist/chunk-SH2Y5F5L.js.map +0 -1
package/README.zh-CN.md
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @masterpeach/launchpad-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
面向 ARC Launchpad **Production V2** 合约的 TypeScript SDK,仅依赖 Viem。一个 Launch 先在 Pons 式
|
|
4
|
+
bonding curve 上出售代币,卖到固定的 Token 侧边界后,通过两个可独立重试的无许可步骤毕业到 Uniswap V4 池。
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
SDK 唯一需要消费端提供的依赖是 `viem`。金额、网络、交易和错误等共享基础能力已内联,并从根入口
|
|
9
|
-
重新导出。SDK 不包含 React hooks、Wagmi adapter 或 Indexer 客户端 —— 这些属于应用层。
|
|
6
|
+
本包只支持 Production V2。它**不是**上一版 V3 SDK 的升级:合约体系整个重写了,V3 的地址、ABI、类型
|
|
7
|
+
没有任何一个能沿用。Referrer 系统、独立的 creator denylist 注册表、暂停开关、一步式迁移、WAD 定点曲线
|
|
8
|
+
全部不复存在。
|
|
10
9
|
|
|
11
10
|
## 安装
|
|
12
11
|
|
|
@@ -14,229 +13,376 @@ SDK 唯一需要消费端提供的依赖是 `viem`。金额、网络、交易和
|
|
|
14
13
|
pnpm add @masterpeach/launchpad-sdk viem
|
|
15
14
|
```
|
|
16
15
|
|
|
17
|
-
##
|
|
16
|
+
## 最容易踩的四个坑
|
|
17
|
+
|
|
18
|
+
写任何对接代码之前先看这四条,每一条都会让调用**看起来对但实际失败**。
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
| `@masterpeach/launchpad-sdk` | 客户端、actions、领域类型、部署、错误、纯函数 |
|
|
22
|
-
| `@masterpeach/launchpad-sdk/abi` | 生成的 ABI 绑定与 artifact commit |
|
|
20
|
+
**1. 每个 Launch 有自己的 `BondingCurve`,交易发给 curve,不是 factory。** Factory 只负责创建和查询。
|
|
21
|
+
用 `getCurveAddress`(或从 `TokenCreated` / `getLaunchRecord` 里取)解析出 curve 并按 token 缓存。
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
**2. 授权的 spender 因操作而异,而且 forwarder 地址换过。** 交易资金由 **curve** 拉取;直接
|
|
24
|
+
`createToken` 由 **factory** 拉取;走 forwarder 的两个入口都由 **forwarder** 自己拉取。forwarder
|
|
25
|
+
地址请从 deployment 读,不要写死——2026-09-07 已轮换到 Holder-aware forwarder,授权给旧地址的额度
|
|
26
|
+
不会自动转移。`prepareApprove` 要求显式传 `spender`;用
|
|
27
|
+
`launchpadCreationSpender(deployment, entryPoint)` 拿到正确的那个。
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
**3. 开盘狙击税按交易的 token recipient 判定,不是付款人。** Router 代付时既不会借出也不会继承自己的
|
|
30
|
+
豁免资格。调 `prepareBuy` 时要传真实的 recipient,否则会按错误的费率报价、并设出一个永远达不到的
|
|
31
|
+
`minTokenOut`。
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
**4. 一个 Launch 可能在别人的交易里毕业。** RC2 会在某笔买入(包括创建时的首购)吃掉最后一份可售
|
|
34
|
+
Token 时自动毕业。不要假设刚创建的 Launch 一定是 `notGraduated`,也不要不重新读状态就调 `graduate`。
|
|
35
|
+
SDK 会给这类交易显式设置 gas limit;如果你从 `request` 手工重建交易,请保留 `gas` 字段——只用估算值
|
|
36
|
+
会让交易内的自动毕业 gas 不足、必然 revert。
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
配置只有一个真源,不存在第二处可漂移,写入门禁也无法被绕过。
|
|
38
|
+
## 创建客户端
|
|
34
39
|
|
|
35
40
|
```ts
|
|
36
41
|
import {
|
|
37
42
|
arcTestnetLaunchpadDeployment,
|
|
38
|
-
|
|
43
|
+
createLaunchpadFactoryClient,
|
|
39
44
|
defineLaunchpadChain,
|
|
40
45
|
verifyLaunchpadDeployment,
|
|
41
46
|
} from "@masterpeach/launchpad-sdk";
|
|
42
47
|
|
|
48
|
+
// 另一个是 `arcMainnetLaunchpadDeployment`(chain 5042)。两者没有任何地址相同,contractVersion 也
|
|
49
|
+
// 不同;都不是注册的默认值,按名字选。
|
|
50
|
+
const deployment = arcTestnetLaunchpadDeployment;
|
|
51
|
+
|
|
43
52
|
const chain = defineLaunchpadChain({
|
|
44
53
|
slug: "arc-testnet",
|
|
45
|
-
chainId:
|
|
54
|
+
chainId: deployment.chainId,
|
|
46
55
|
family: "evm",
|
|
47
|
-
nativeCurrency: { symbol: "
|
|
56
|
+
nativeCurrency: { symbol: "ARC", decimals: 18 },
|
|
48
57
|
rpcUrls: ["https://rpc.testnet.arc.io"],
|
|
49
58
|
explorerUrl: "https://testnet.arcscan.app",
|
|
50
|
-
apiBaseUrl: "https://
|
|
59
|
+
apiBaseUrl: "https://api.testnet.arc.example",
|
|
51
60
|
realtimeUrl: "wss://rpc.testnet.arc.io",
|
|
52
|
-
deployment
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
antiFarmer: true,
|
|
61
|
-
earlySellTax: false,
|
|
62
|
-
rewards: false, // Locker claim 在产品确认前保持关闭
|
|
63
|
-
referral: false,
|
|
64
|
-
},
|
|
61
|
+
deployment,
|
|
62
|
+
// 一个 factory 支持多个 QuoteToken,把你要提供的币种都列出来。
|
|
63
|
+
quoteTokens: deployment.economics.quoteTokens.map((quoteToken) => ({
|
|
64
|
+
address: quoteToken.address,
|
|
65
|
+
decimals: quoteToken.decimals,
|
|
66
|
+
symbol: quoteToken.symbol,
|
|
67
|
+
})),
|
|
68
|
+
capabilities: { create: true, bondingTrade: true, partnerAttribution: true },
|
|
65
69
|
confirmations: 2,
|
|
66
70
|
});
|
|
67
71
|
|
|
68
|
-
// 校验链 ID、Portal runtime bytecode hash 及全部模块绑定。
|
|
69
72
|
await verifyLaunchpadDeployment(publicClient, chain.deployment);
|
|
70
|
-
|
|
71
|
-
const client = createLaunchpadPortalClient({ publicClient, walletClient, chain });
|
|
73
|
+
const client = createLaunchpadFactoryClient({ publicClient, walletClient, chain });
|
|
72
74
|
```
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
`verifyLaunchpadDeployment` 分三类校验,因为它们失败的原因完全不同:factory 的 runtime code hash
|
|
77
|
+
(身份)、factory 报告的每个模块地址加上 hook 自己看到的 V4 PoolManager(接线)、以及 fee policy、
|
|
78
|
+
partner policy、狙击税、launch fee、creator 上限和每个 pinned 的 QuoteToken 与 LaunchConfig(经济参数)。
|
|
79
|
+
接入新部署前一定要跑:V2 的依赖是构造后用一组独立 setter 逐个接上的,接错一半的部署在调用真正打到零地址
|
|
80
|
+
之前看起来一切正常。
|
|
75
81
|
|
|
76
|
-
|
|
77
|
-
`prepareCreateToken` 需要 `create`,`prepareBuy`/`prepareSell` 需要 `bondingTrade`,
|
|
78
|
-
`prepareClaimReward` 需要 `rewards`(默认关闭)。`prepareMigration` 与 `prepareCollectFees` **不**受
|
|
79
|
-
门控 —— 两者在链上都是 permissionless;禁用迁移会让已毕业的 Token 卡住,禁用手续费收取则会冻住
|
|
80
|
-
本属于 creator 的收益。读取永不受门控,因此页面在无签名者时也能渲染。
|
|
81
|
-
|
|
82
|
-
## 读取
|
|
83
|
-
|
|
84
|
-
所有 read 都接受 `blockNumber` / `blockTag`,可以把状态、报价、余额和 allowance 固定在同一个区块,
|
|
85
|
-
避免拼接不同高度的数据。
|
|
82
|
+
## 读取 Launch 状态
|
|
86
83
|
|
|
87
84
|
```ts
|
|
85
|
+
// 先 pin 住区块。curve 快照是由很多个 getter 拼出来的,不 pin 会混用不同高度,
|
|
86
|
+
// 拼出一个从未同时存在过的储备状态。
|
|
88
87
|
const blockNumber = await publicClient.getBlockNumber();
|
|
89
|
-
const [state, quote, availability] = await Promise.all([
|
|
90
|
-
client.getTokenState(token, { blockNumber }),
|
|
91
|
-
client.quoteBuy(token, parseUnits("100", 6), { blockNumber }),
|
|
92
|
-
client.getTradeAvailability(token, { blockNumber }),
|
|
93
|
-
]);
|
|
94
|
-
```
|
|
95
88
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
89
|
+
const state = await client.getTokenState(token, { blockNumber });
|
|
90
|
+
state.record; // 创建时冻结的经济参数(curve 地址、quote token、creator tax、各项分成)
|
|
91
|
+
state.curveState; // 实时储备、可售量、是否已到毕业边界
|
|
92
|
+
state.graduation; // "notGraduated" | "swept" | "poolCreated" | "emergencyExited"
|
|
99
93
|
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
const progress = await client.getCurveProgress(token, { blockNumber });
|
|
95
|
+
progress.progressBps; // 按 Token 侧衡量,所以恰好封顶在 100%
|
|
96
|
+
```
|
|
102
97
|
|
|
103
|
-
|
|
98
|
+
用户粘贴进来的地址必须先过 `isLaunchToken`。和 V3 不同,V2 的 record getter 对陌生地址**不 revert**,
|
|
99
|
+
而是返回一个全零结构体——跳过这个检查会让后续每一次调用都打到零地址。
|
|
104
100
|
|
|
105
|
-
|
|
106
|
-
`writeContract`。没有经过模拟的交易不会被广播。
|
|
101
|
+
## 交易
|
|
107
102
|
|
|
108
103
|
```ts
|
|
109
|
-
|
|
104
|
+
const curve = await client.getCurveAddress(token);
|
|
110
105
|
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
// curve 拉取 quote,所以 spender 是 curve。
|
|
107
|
+
await client.send(
|
|
108
|
+
await client.prepareApprove({ asset: quoteToken, spender: curve, amount: quoteIn }),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const buy = await client.prepareBuy({
|
|
113
112
|
token,
|
|
114
|
-
quoteIn,
|
|
115
|
-
slippage: { bps: 100 },
|
|
116
|
-
deadline
|
|
113
|
+
quoteInRaw: quoteIn,
|
|
114
|
+
slippage: { bps: 100 },
|
|
115
|
+
deadline,
|
|
116
|
+
tokenRecipient, // 决定狙击税;不传则用当前连接账户
|
|
117
|
+
partnerId, // 可选归因;bytes32(0) 或不传表示无 Partner
|
|
117
118
|
});
|
|
119
|
+
const result = await client.wait(await client.send(buy));
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`buy.preview` 会给出 `tokenOut`、`quoteSpent`、`refund`、三项费用明细,以及实际适用的 `snipeTaxBps`。
|
|
123
|
+
在一条 curve 的收尾买入里,`quoteSpent` 会小于你报的金额,差额在同一笔交易里原子退回——授权和展示都要用
|
|
124
|
+
`quoteSpent`,不要用用户输入的数字。
|
|
125
|
+
|
|
126
|
+
卖出动的是 launch token,需要它自己的授权,spender 同样是 curve。卖出永远不收狙击税。
|
|
127
|
+
|
|
128
|
+
### 收尾买入的滑点陷阱
|
|
118
129
|
|
|
119
|
-
|
|
120
|
-
|
|
130
|
+
合约按实际花费等比缩放最低成交量:`mulDiv(minTokenOut, quoteSpent, quoteInRaw)`,RC2 起向上取整
|
|
131
|
+
(RC1 是向下取整,所以现在的舍入对用户略有利)。但这仍然会让保护**按退款比例等比减弱**。报价约为实际所需两倍时,有效最低量直接砍半;再往上,任何用户物理上能填的 `minTokenOut`
|
|
132
|
+
(哪怕填满整个 supply)都无法生效。
|
|
133
|
+
|
|
134
|
+
想在收尾那笔上要一个真实的下限,就先报价、再按实际需要的金额下单;**不要**报一个大整数指望
|
|
135
|
+
`minTokenOut` 兜底。
|
|
136
|
+
|
|
137
|
+
## 创建 Token
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { UNCHECKED_ECONOMICS, launchpadEconomicsHashFromDeployment } from "@masterpeach/launchpad-sdk";
|
|
141
|
+
|
|
142
|
+
const gate = await client.getLaunchGate(creator);
|
|
143
|
+
// gate.canLaunch,以及背后的三个输入:launchEnabled / whitelisted / denied。
|
|
144
|
+
// Denylist 优先级最高:被拉黑的 creator 即使同时在白名单里也不能创建。
|
|
145
|
+
|
|
146
|
+
const params = {
|
|
147
|
+
name: "Peach Demo",
|
|
148
|
+
symbol: "PEACH",
|
|
149
|
+
logo: "ipfs://...",
|
|
150
|
+
description: "...",
|
|
151
|
+
socials: { twitter: "", telegram: "", discord: "", website: "", farcaster: "" },
|
|
152
|
+
quoteToken,
|
|
153
|
+
launchConfigId: 0n,
|
|
154
|
+
// 把这次创建锁定到一份确切的经济参数模板。可选:传 bytes32(0) 就跳过校验。
|
|
155
|
+
expectedEconomics: launchpadEconomicsHashFromDeployment(client, { quoteToken, launchConfigId: 0n }),
|
|
156
|
+
launchTaxExemptions: [],
|
|
157
|
+
creatorFeeRecipient: zeroAddress, // 链上会解析成 creator 本人;这里原样保留不代填
|
|
158
|
+
buybackEnabled: true,
|
|
159
|
+
creatorTaxBps: 100,
|
|
160
|
+
creatorInitialBuyQuoteRaw: 0n,
|
|
161
|
+
creatorInitialBuyRecipient: creator,
|
|
162
|
+
minInitialTokenOut: 0n,
|
|
163
|
+
deadline,
|
|
164
|
+
salt,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const predicted = await client.predictAddresses(params, creator); // { token, curve }
|
|
168
|
+
const created = await client.wait(await client.send(await client.prepareCreateToken(params)));
|
|
169
|
+
const launch = created.events.tokenCreated[0]; // 唯一能证明创建成功的依据
|
|
121
170
|
```
|
|
122
171
|
|
|
123
|
-
|
|
124
|
-
|
|
172
|
+
`prepareCreateToken` 会校验所有「纯粹由参数决定」的规则,并**一次性返回全部违规项**,这样表单可以一遍
|
|
173
|
+
标出所有错误字段。长度按 UTF-8 字节算,和合约一致:emoji 或中文的名字能通过朴素的 `.length <= 64`
|
|
174
|
+
检查,但链上照样 revert。
|
|
175
|
+
|
|
176
|
+
有两条规则容易漏。salt 每个 creator 只能用一次(用 `isLaunchpadSaltUsed` 查)。以及豁免地址会和
|
|
177
|
+
**自动豁免**的那几个一起去重——creator、fee recipient、首购 recipient 本来就已豁免,所以把 creator
|
|
178
|
+
自己填进 `launchTaxExemptions` 会直接 revert,不是无害的冗余项。
|
|
179
|
+
|
|
180
|
+
`prepareLaunchAndBuy` 走 forwarder,用于在由另一个合约发起创建时保留 creator 身份。
|
|
181
|
+
**它的授权给 forwarder,不是 factory**——RC2 改成由 forwarder 精确收款、在调用期间临时授权 factory、
|
|
182
|
+
调用结束后撤销授权并退回余额。这与 RC1 正好相反,从 RC1 迁过来的代码会授权错对象。
|
|
183
|
+
|
|
184
|
+
创建也不保证 Launch 还留在内盘:如果首购吃掉了最后一份可售 Token,factory 会在同一笔交易里完成毕业。
|
|
185
|
+
收到 `TokenCreated` 后请读 `getGraduation(token)`,不要默认它是 `notGraduated`。
|
|
125
186
|
|
|
126
|
-
###
|
|
187
|
+
### Holder Fee Sharing(按持有人分成)
|
|
127
188
|
|
|
128
|
-
|
|
129
|
-
|
|
189
|
+
可选模式:Launch 的 creator 费用不再进单一收款地址,而是进一个「每个 Launch 一份」的 distributor,
|
|
190
|
+
由它与持有人分成。
|
|
130
191
|
|
|
131
192
|
```ts
|
|
132
|
-
const prepared = await client.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
metadata: JSON.stringify({ description, image, website }),
|
|
136
|
-
userSalt: keccak256(crypto.getRandomValues(new Uint8Array(32))),
|
|
137
|
-
initialQuoteIn, // 不首购则传 0n
|
|
138
|
-
slippage: { bps: 100 }, // initialQuoteIn > 0 时必填
|
|
139
|
-
deadline: toDeadline(block.timestamp),
|
|
140
|
-
protectionBlocks: 6, // 0..6
|
|
193
|
+
const prepared = await client.prepareLaunchWithHolderFeeSharing({
|
|
194
|
+
...params,
|
|
195
|
+
creatorFeeRecipient: zeroAddress, // 必须为零地址,见下
|
|
141
196
|
});
|
|
197
|
+
prepared.preview.distributor; // 预测出来的地址,也是这笔交易将要安装的那个
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
一笔交易内完成:创建 distributor → 装成 `creatorFeeRecipient` → 创建 token 与 curve → 校验所有绑定。
|
|
201
|
+
任何一步失败整笔回滚,不会留下半成品。
|
|
202
|
+
|
|
203
|
+
**`creatorFeeRecipient` 必须是零地址。** recipient 由 forwarder 自己推导。知道有 distributor 之后
|
|
204
|
+
自己预测一个填进去——这个最自然的动作,恰恰是合约明确拒绝的。
|
|
205
|
+
|
|
206
|
+
distributor 由 `(creator, salt)` 决定,**不**由 token 决定:token 自己的地址要经 `creatorFeeRecipient`
|
|
207
|
+
依赖 distributor,反过来预测会循环。签名前可以用 `predictLaunchpadHolderDistributor` 拿到地址。
|
|
208
|
+
|
|
209
|
+
**它不是不可逆的。** 链上没有任何东西把 distributor 钉死:当前 recipient 可以用
|
|
210
|
+
`transferCreatorFeeRecipient` 把后续收益转走,治理也能按常规延迟覆盖 recipient。留下来的只有注册表
|
|
211
|
+
记录和 distributor 已经收到的资产。不要向用户承诺"开启 Holder Sharing 后就锁死了"。
|
|
212
|
+
|
|
213
|
+
**Buyback 最终也会分给持有人,但晚、而且是 token 形式。** 开了 buyback 之后,creator gross 的一部分
|
|
214
|
+
会被用来买入 LaunchToken 存进 vault、按五年释放;`harvest` 会把已归属的那部分拉进 distributor,所以它
|
|
215
|
+
确实会被分掉——只是作为后续某一期的 coin 侧,而不是交易当时的 quote。"全部 creator fee 立即分给持有人"
|
|
216
|
+
和"开启 buyback"仍然是两个不同的承诺。
|
|
217
|
+
|
|
218
|
+
领取也覆盖了。收益按 Epoch 发放:distributor 累积 creator 费用,publisher 用一个 Merkle Root 开一期,
|
|
219
|
+
持有人在 90 天窗口内凭 proof 领取,没领完的滚回池子留给下一期。
|
|
142
220
|
|
|
143
|
-
|
|
144
|
-
|
|
221
|
+
```ts
|
|
222
|
+
const { distributor } = await getLaunchpadHolderBinding(ctx, token);
|
|
223
|
+
const epoch = await getLaunchpadHolderEpoch(ctx, { distributor, epochId });
|
|
224
|
+
|
|
225
|
+
// allocation 来自 distributor 项目的 Proof 服务
|
|
226
|
+
if (!launchpadHolderAllocationMatchesRoot(allocation, epoch.root)) throw new Error("stale proof");
|
|
227
|
+
await client.send(await prepareLaunchpadHolderClaim(ctx, { distributor, allocation }));
|
|
145
228
|
```
|
|
146
229
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
230
|
+
每份 allocation **同时**发两边——quote 收益和 launch token,一次领完;而且任何人都能提交,因为付款按
|
|
231
|
+
`allocation.account` 走而不是按发起人。领之前先对着 epoch root 本地校验:合约的 `InvalidMerkleProof`
|
|
232
|
+
说不出到底是金额、账户还是 epoch 错了,而最常见的原因是把 Proof 服务自己的数据库 epoch id 当成了链上
|
|
233
|
+
id——链上要的是 `onchain_epoch_id`。
|
|
234
|
+
|
|
235
|
+
发布 Epoch 和提供 Proof 仍属于 distributor 项目,见[对接指南](docs/integration-guide.md#holder-claims)。
|
|
236
|
+
|
|
237
|
+
交易回执里会**同时**出现两半:forwarder 的 `HolderSharingLaunchCreated` 与 distributor factory 的
|
|
238
|
+
`DistributorCreated`,都在 `events.holderSharing` 里。它们**故意不合并**——只带其中一个的 Launch
|
|
239
|
+
不是官方的 Holder Sharing Launch。权威读法是 `getHolderDistributor(token)` 与
|
|
240
|
+
`isHolderDistributor(address)`。任何 Launch 都可以把 `creatorFeeRecipient` 填成任意地址,所以不查注册表
|
|
241
|
+
就把它标成 holder distributor 是不对的。
|
|
151
242
|
|
|
152
|
-
|
|
243
|
+
## 毕业
|
|
153
244
|
|
|
154
|
-
|
|
155
|
-
|
|
245
|
+
```ts
|
|
246
|
+
const graduation = await client.getGraduation(token);
|
|
156
247
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
248
|
+
// 第一步:把 curve 的资产 sweep 给 executor。curve 关闭后任何人都能调——而且常常已经被别人做掉了,
|
|
249
|
+
// 因为 RC2 会在清空 curve 的那笔买入里自动毕业。prepareGraduate 会直接拒绝,而不是构造一笔必然失败的交易。
|
|
250
|
+
if (graduation.status === "notGraduated" && state.curveState.graduationReady) {
|
|
251
|
+
await client.send(await client.prepareGraduate(token));
|
|
252
|
+
}
|
|
162
253
|
|
|
163
|
-
|
|
254
|
+
// 第二步:建 V4 池并铸出 position。同样无许可,且可重试。
|
|
255
|
+
if (graduation.status === "swept") {
|
|
256
|
+
await client.send(await client.prepareCreateGraduatedPool(token));
|
|
257
|
+
}
|
|
164
258
|
|
|
165
|
-
|
|
259
|
+
const poolId = await client.getPoolId(token); // 池子存在之前是 bytes32(0)
|
|
260
|
+
```
|
|
166
261
|
|
|
167
|
-
|
|
168
|
-
|
|
262
|
+
`swept` 是一个可以停留的状态,不是过渡态。第二步失败时资产安全地留在 executor,直接重试即可——这是相对
|
|
263
|
+
V3 那个全有或全无的 `migrate` 的实质改进。
|
|
264
|
+
|
|
265
|
+
自动毕业尝试失败时 RC2 会发 `AutoGraduationFailed`,**但对应的买入或创建仍然成功**。
|
|
266
|
+
`parseLaunchpadReceiptEvents` 特意把它放进独立的 `autoGraduationFailures` 分组,就是为了避免前端把它
|
|
267
|
+
显示成购买失败;Launch 只是停在 `notGraduated`,由常规的无许可路径接手。
|
|
268
|
+
|
|
269
|
+
## 费用
|
|
270
|
+
|
|
271
|
+
费用先在 curve 上累计,再经由单独的 sweep 步骤转出,最后落到 `FeeEscrow` 里由受益人自己领取。
|
|
169
272
|
|
|
170
273
|
```ts
|
|
171
|
-
|
|
172
|
-
|
|
274
|
+
const pending = await client.getPendingFees(curve); // 尚未 sweep
|
|
275
|
+
const claimable = await client.getClaimableBalance({ recipient, asset }); // 已在 escrow 里
|
|
276
|
+
await client.send(await client.prepareClaim({ asset })); // 领取自己的余额
|
|
173
277
|
```
|
|
174
278
|
|
|
175
|
-
|
|
279
|
+
**Escrow 只按 `(recipient, asset)` 两维记账。** 它不记录某笔 credit 来自哪个 Launch,所以同一个
|
|
280
|
+
recipient 被多个 Launch 复用时,每种资产只会汇成一个无法拆分的池子,链上没有办法还原归属。需要按
|
|
281
|
+
Launch 归因的话,要么每个 Launch 用不同的 recipient 地址,要么从 `CurveFeesSwept` / `FeeCredited`
|
|
282
|
+
事件在链下自行重建。
|
|
176
283
|
|
|
177
|
-
##
|
|
284
|
+
## Partner
|
|
178
285
|
|
|
179
|
-
|
|
286
|
+
Partner 是**按笔交易声明**的,不在创建时绑定到 token,而且比例是实时从注册表读的——所以调整比例从下一笔
|
|
287
|
+
交易开始生效,绝不追溯。
|
|
180
288
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
| `SlippageExceeded` | `LaunchpadSlippageError` |
|
|
187
|
-
| `DeadlineExpired` | `LaunchpadDeadlineExpiredError` |
|
|
188
|
-
| allowance 不足 | `LaunchpadApprovalRequiredError` |
|
|
189
|
-
| 余额不足 | `LaunchpadInsufficientBalanceError` |
|
|
190
|
-
| `OfficialPoolLocked` | `LaunchpadOfficialPoolLockedError` |
|
|
191
|
-
| LP NFT 编号未注册 | `LaunchpadUnknownPositionError` |
|
|
192
|
-
| 钱包链不匹配 | `WrongWalletChainError` |
|
|
193
|
-
| prepare 后切换了账户 | `PreparedAccountMismatchError` |
|
|
194
|
-
| prepared 值来源不明 | `ForgedPreparedTransactionError` |
|
|
195
|
-
| 该链 capability 已关闭 | `UnsupportedLaunchpadCapabilityError` |
|
|
196
|
-
| 组合读取传入不可固定的 block tag | `UnpinnableBlockTagError` |
|
|
289
|
+
```ts
|
|
290
|
+
const usability = await client.getPartnerUsability(partnerId);
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
三种结果,前端不能把它们混为一谈:
|
|
197
294
|
|
|
198
|
-
|
|
295
|
+
| | `usable` | `earning` | 对交易的影响 |
|
|
296
|
+
|---|---|---|---|
|
|
297
|
+
| `bytes32(0)` | 是 | 否 | 正常成交,无归因 |
|
|
298
|
+
| 已注册、已禁用 | 是 | 否 | 正常成交,事件里仍记录,但拿不到分成 |
|
|
299
|
+
| 未注册的非零 ID | **否** | 否 | **整笔交易 revert** |
|
|
199
300
|
|
|
200
|
-
|
|
301
|
+
在已毕业的 V4 池上做 swap 时,用 `encodeLaunchpadPartnerHookData` 编码。冻结的 ABI 就是
|
|
302
|
+
`abi.encode(bytes32)`,无 Partner 时是空 bytes;hook 会严格校验长度,其他任何形式都会 revert
|
|
303
|
+
而不是被忽略。
|
|
201
304
|
|
|
202
|
-
|
|
203
|
-
同名导出,同时引入不会冲突。需要共享能力时,从任一 SDK 的 `./core` 导入:
|
|
305
|
+
## Buyback 与紧急退出
|
|
204
306
|
|
|
205
307
|
```ts
|
|
206
|
-
|
|
308
|
+
const vest = await client.getBuybackVest(token);
|
|
309
|
+
await client.send(await client.prepareBuybackRelease(token));
|
|
207
310
|
```
|
|
208
311
|
|
|
209
|
-
|
|
210
|
-
`
|
|
312
|
+
Vesting 时钟是对「历史全部锁仓」的单一五年加权平均,不是按笔分 tranche:每次新存入都会把整个余额的
|
|
313
|
+
`vestingEnd` 往后推,所以只要 buyback 持续有流入,这个 Launch 就永远不会完全归属完。
|
|
314
|
+
|
|
315
|
+
`prepareSetBuybackEnabled` 的权限在链上是不对称的:当前 creator fee recipient 可以**开也可以关**,
|
|
316
|
+
而 protocol owner **只能关**。
|
|
211
317
|
|
|
212
318
|
```ts
|
|
213
|
-
import {
|
|
319
|
+
import { launchpadEmergencyExitAvailableAt } from "@masterpeach/launchpad-sdk";
|
|
214
320
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
321
|
+
const availableAt = launchpadEmergencyExitAvailableAt(graduation); // 非 swept 时返回 undefined
|
|
322
|
+
const { distributor } = await client.getEmergencyDistributor({ launchToken: token, quoteToken });
|
|
323
|
+
const quote = await client.quoteEmergencyRedeem({ distributor, tokenIn });
|
|
218
324
|
```
|
|
219
325
|
|
|
220
|
-
|
|
221
|
-
`PeachSdkErrorCode` 取值为稳定契约。上表中的 Launchpad 专有错误只存在于本 Package 且从根入口导出,
|
|
222
|
-
仍应使用 `instanceof LaunchpadError` 判别。
|
|
326
|
+
触发退出是 **owner-only** 的,与两步毕业不同——被套住的持有人只能在治理触发之后赎回,自己拉不动这个闸。
|
|
223
327
|
|
|
224
|
-
|
|
328
|
+
紧急退出的适用面比「卡住的 Launch」窄得多:它要求 Launch **已经处于 `swept`**,并从 `sweptAt` 起算
|
|
329
|
+
七天,因为它填的正是两步毕业之间的那个缺口。还在 curve 上交易的 Launch 根本无从退出,而且没有任何快速通道。
|
|
225
330
|
|
|
226
|
-
|
|
331
|
+
赎回的分母是代币的**实时** `totalSupply()`,每次重新读取而非快照。因此有人直接 burn 而不赎回时,会抬高
|
|
332
|
+
其余所有人的单位赎回额,最后一个赎回的人拿走全部剩余。报价会随别人的行为漂移,提交前请重新读取。
|
|
227
333
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
334
|
+
## Keeper 操作
|
|
335
|
+
|
|
336
|
+
Curve sweep 和 V4 池的 Epoch 分页 Partner 结算在 keeper 接口里。注意一个很容易搞错的参数不对称:
|
|
337
|
+
`beginPoolFeeSweep` 收的是 **token**,而 `settlePoolFeeSweepPartners` 和 `finalizePoolFeeSweep`
|
|
338
|
+
收的是 **poolId**(用 `getLaunchpadPoolId` 解析)。协议对 Partner 数量和分页大小都**没有上限**,
|
|
339
|
+
每批多少完全取决于你自己的 gas 预算。
|
|
340
|
+
|
|
341
|
+
RC2 改变了 sweep 的失败方式:碰到内部价格影响上限不再整笔回滚,而是能换多少换多少,剩下的留在
|
|
342
|
+
Pending,Partner 未结算的部分滚入下一个 Epoch。`InternalPriceImpactExceeded` 已经不存在,所以没有
|
|
343
|
+
异常可捕获重试——改为读 `getLaunchpadPartnerEpochAccounting({ poolId, epochId })`,在
|
|
344
|
+
`allPartnersSettled` 为 false 时继续分页,只在 `finalizeReady` 为 true 时才 finalize(链上校验的是
|
|
345
|
+
五个条件,不只是 Partner 计数),并把 `partnerLaunchInputConsumed < partnerLaunchInputTotal`
|
|
346
|
+
当作"还有活没干完"而不是错误。
|
|
347
|
+
|
|
348
|
+
还有一个 RC2 之前就存在的坑:`MemeHook.settlingEpoch` 是**一基**存储的,0 表示没有正在结算的 Epoch。
|
|
349
|
+
Epoch id 请取 `getLaunchpadPoolSweepState(...).settlingEpochId`(已解码),直接用原始值会大 1,
|
|
350
|
+
导致 settle 和 finalize 全部 revert。
|
|
351
|
+
|
|
352
|
+
另外 curve 的 `sweepFees` 可能顺带触发毕业:它内部的 buyback 有可能买走最后的可售 Token。每次 sweep
|
|
353
|
+
成功后都要重新读一次毕业状态。
|
|
354
|
+
|
|
355
|
+
## 错误处理
|
|
356
|
+
|
|
357
|
+
这套部署能产生的每一个合约 revert 都映射到稳定、可捕获的类型,且映射表对生成的 ABI 做了穷尽性断言。
|
|
358
|
+
值得单独分支处理的几个:
|
|
359
|
+
|
|
360
|
+
- `LaunchpadTradingClosedError` —— curve 已到边界。**永久性**关闭,之后去 V4 池交易。
|
|
361
|
+
- `LaunchpadTradeFeeConsumesInputError` —— 费用吃光了输入。加大金额,或等狙击税衰减。
|
|
362
|
+
- `LaunchpadInsufficientQuoteReserveError` —— 卖出超过了 curve 真实(非 phantom)的储备。
|
|
363
|
+
- `LaunchpadSlippageError`、`LaunchpadDeadlineExpiredError` —— 重新报价并重建交易。
|
|
364
|
+
- `LaunchpadCreatorDeniedError` 与 `LaunchpadLaunchNotPermittedError` —— 前者是针对单个账户的封禁,
|
|
365
|
+
后者是对所有非白名单账户关闭的闸门,处理方式完全不同。
|
|
366
|
+
- `LaunchpadEconomicsHashMismatchError` —— 治理改了模板;重新读取并重建哈希。
|
|
367
|
+
- `LaunchpadApprovalRequiredError` —— 带着确切的 `spender`,而它因操作而异。
|
|
368
|
+
- `prepareGraduate` 抛出的 `LaunchpadPhaseUnavailableError` —— 在 RC2 下这通常意味着别人的交易已经把
|
|
369
|
+
它毕业了。看 `error.status`:`launchpadGraduateIsIdempotentSuccess(status)` 对 `swept` 和
|
|
370
|
+
`poolCreated` 返回 true,keeper 应当按"已完成"处理而不是告警。
|
|
371
|
+
|
|
372
|
+
## 主网
|
|
373
|
+
|
|
374
|
+
合约与经济参数完全相同,只剩一处不同。主网已于 2026-09-08 轮换到 Holder-aware forwarder 并绑定了
|
|
375
|
+
distributor factory,两条创建路径和 Holder Fee Sharing 的行为与测试网完全一致——只有地址不同。仍然不
|
|
376
|
+
同的是 Quote Token:只有 USDC,tUSDG 是测试网水龙头代币,照搬过去等于没有可交易资产。
|
|
377
|
+
|
|
378
|
+
Public Launch 已开启,创建、买入、卖出都已在链上跑通;毕业路径尚未验证。细节见
|
|
379
|
+
[对接指南](docs/integration-guide.md#mainnet-what-differs)。
|
|
380
|
+
|
|
381
|
+
## 包入口
|
|
238
382
|
|
|
239
|
-
|
|
383
|
+
- `@masterpeach/launchpad-sdk` —— 客户端、actions、math、类型与错误
|
|
384
|
+
- `@masterpeach/launchpad-sdk/abi` —— 生成的 Production V2 ABI
|
|
385
|
+
- `@masterpeach/launchpad-sdk/core` —— 跨 SDK 共享的链无关原语
|
|
240
386
|
|
|
241
|
-
|
|
242
|
-
|
|
387
|
+
完整生命周期、索引注意事项、keeper 运维手册和上线检查清单见
|
|
388
|
+
[对接指南](docs/integration-guide.md)。
|