@parabolicfamily/mcp 0.1.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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -0
  3. package/abi/ParabolicBondingCurve.json +1223 -0
  4. package/abi/ParabolicLaunchFactory.json +2212 -0
  5. package/abi/ParabolicLauncherToken.json +577 -0
  6. package/abi/ParabolicMemoRouter.json +950 -0
  7. package/dist/abi.d.ts +17 -0
  8. package/dist/abi.js +37 -0
  9. package/dist/abi.js.map +1 -0
  10. package/dist/chain.d.ts +32 -0
  11. package/dist/chain.js +48 -0
  12. package/dist/chain.js.map +1 -0
  13. package/dist/coins.d.ts +52 -0
  14. package/dist/coins.js +75 -0
  15. package/dist/coins.js.map +1 -0
  16. package/dist/config.d.ts +55 -0
  17. package/dist/config.js +86 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/curve.d.ts +92 -0
  20. package/dist/curve.js +127 -0
  21. package/dist/curve.js.map +1 -0
  22. package/dist/docs.d.ts +3 -0
  23. package/dist/docs.js +63 -0
  24. package/dist/docs.js.map +1 -0
  25. package/dist/format.d.ts +19 -0
  26. package/dist/format.js +30 -0
  27. package/dist/format.js.map +1 -0
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +15 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/parabolic.d.ts +780 -0
  32. package/dist/parabolic.js +710 -0
  33. package/dist/parabolic.js.map +1 -0
  34. package/dist/server.d.ts +32 -0
  35. package/dist/server.js +149 -0
  36. package/dist/server.js.map +1 -0
  37. package/dist/subgraph.d.ts +85 -0
  38. package/dist/subgraph.js +43 -0
  39. package/dist/subgraph.js.map +1 -0
  40. package/dist/tx.d.ts +115 -0
  41. package/dist/tx.js +105 -0
  42. package/dist/tx.js.map +1 -0
  43. package/package.json +64 -0
  44. package/src/abi.ts +41 -0
  45. package/src/chain.ts +60 -0
  46. package/src/coins.ts +118 -0
  47. package/src/config.ts +123 -0
  48. package/src/curve.ts +171 -0
  49. package/src/docs.ts +63 -0
  50. package/src/format.ts +34 -0
  51. package/src/index.ts +14 -0
  52. package/src/parabolic.ts +753 -0
  53. package/src/server.ts +229 -0
  54. package/src/subgraph.ts +61 -0
  55. package/src/tx.ts +146 -0
@@ -0,0 +1,710 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { encodeFunctionData, toHex } from "viem";
3
+ import { curveAbi, factoryAbi, tokenAbi } from "./abi.js";
4
+ import { mapLimit, viemRpc } from "./chain.js";
5
+ import { isNative, progressPct, sortCoins, summaryFromGql } from "./coins.js";
6
+ import { NATIVE_QUOTE, chainName, explorerUrl } from "./config.js";
7
+ import { applySlippage, graduationMarketCap, graduationSplit, marketCap, passesPriceBound, poolIdOf, priceImpact, quoteBuy, quoteSell, reservedTokensFor, spotPrice, toNumber } from "./curve.js";
8
+ import { docsText } from "./docs.js";
9
+ import { addrOf, amount, big, bool, num, parseAddress, parseAmount, str } from "./format.js";
10
+ import { Subgraph } from "./subgraph.js";
11
+ import { encodeApprove, encodeBuy, encodeLaunch, encodeLaunchAndBuy, encodeMemoBuy, encodeSell } from "./tx.js";
12
+ export const PHASES = ["NotGraduated", "Swept", "PoolCreated", "Rescued"];
13
+ export const DEFAULT_SLIPPAGE_BPS = 100;
14
+ export const DEV_BUY_SLIPPAGE_BPS = 200n;
15
+ /** ParabolicLaunchDeployer metadata caps, in bytes. */
16
+ export const METADATA_LIMITS = { name: 64, symbol: 16, logo: 512, description: 2048, social: 256 };
17
+ /** Newest launches the TokenLaunched log scan considers when no subgraph is configured. */
18
+ export const CHAIN_LIST_MAX = 50;
19
+ const LAUNCHED_FIELDS = ["token", "curve", "deployer", "creatorFeeRecipient", "pairToken", "graduationThreshold", "poolFee", "tickSpacing", "creatorTaxBps", "buybackEnabled", "phase", "sweptQuote", "sweptTokens", "sweptAt", "exists"];
20
+ const CONFIG_FIELDS = ["supply", "curveFeeBps", "phantomQuote", "graduationThreshold", "poolFee", "tickSpacing", "enabled"];
21
+ /** viem returns a tuple whose components are all named as an object; accept a positional array as well. */
22
+ function asStruct(raw, fields) {
23
+ if (Array.isArray(raw))
24
+ return Object.fromEntries(fields.map((f, i) => [f, raw[i]]));
25
+ if (raw && typeof raw === "object")
26
+ return raw;
27
+ throw new Error("Unexpected struct encoding from the RPC");
28
+ }
29
+ const slippageOf = (bps) => {
30
+ const v = bps ?? DEFAULT_SLIPPAGE_BPS;
31
+ if (!Number.isInteger(v) || v < 0 || v >= 10_000)
32
+ throw new Error("slippageBps must be an integer between 0 and 9999");
33
+ return BigInt(v);
34
+ };
35
+ const bytesOf = (s) => new TextEncoder().encode(s).length;
36
+ /**
37
+ * Read-only view of Parabolic plus unsigned-transaction builders. Holds no keys; every method either reads
38
+ * (RPC and/or subgraph) or encodes calldata for the caller's wallet to sign.
39
+ */
40
+ export class Parabolic {
41
+ config;
42
+ rpc;
43
+ subgraph;
44
+ hookPromise;
45
+ constructor(config, deps = {}) {
46
+ this.config = config;
47
+ this.rpc = deps.rpc ?? viemRpc(config.rpcUrl, config.chainId);
48
+ this.subgraph = config.subgraphUrl ? new Subgraph(config.subgraphUrl, deps.fetch) : undefined;
49
+ }
50
+ // ---- reads --------------------------------------------------------------------------------------------------
51
+ read(address, abi, functionName, args) {
52
+ return this.rpc.read({ address, abi, functionName, args });
53
+ }
54
+ async readMany(address, abi, names) {
55
+ const values = await Promise.all(names.map((n) => this.read(address, abi, n)));
56
+ return Object.fromEntries(names.map((n, i) => [n, values[i]]));
57
+ }
58
+ requireFactory() {
59
+ if (!this.config.factory)
60
+ throw new Error("PARABOLIC_FACTORY is not set: needed for this operation");
61
+ return this.config.factory;
62
+ }
63
+ hookAddress() {
64
+ if (this.config.hook)
65
+ return Promise.resolve(this.config.hook);
66
+ if (!this.config.factory)
67
+ return Promise.resolve(undefined);
68
+ this.hookPromise ??= this.read(this.config.factory, factoryAbi, "memeHook").then((v) => {
69
+ const a = addrOf(v);
70
+ return a === NATIVE_QUOTE ? undefined : a;
71
+ }, () => undefined);
72
+ return this.hookPromise;
73
+ }
74
+ async launchFromFactory(factory, token) {
75
+ const s = asStruct(await this.read(factory, factoryAbi, "getLaunchedToken", [token]), LAUNCHED_FIELDS);
76
+ if (!s.exists)
77
+ return undefined;
78
+ return {
79
+ token: addrOf(s.token), curve: addrOf(s.curve), deployer: addrOf(s.deployer), creatorFeeRecipient: addrOf(s.creatorFeeRecipient), pairToken: addrOf(s.pairToken),
80
+ graduationThreshold: big(s.graduationThreshold), poolFee: num(s.poolFee), tickSpacing: num(s.tickSpacing), creatorTaxBps: num(s.creatorTaxBps),
81
+ buybackEnabled: bool(s.buybackEnabled), phase: num(s.phase), source: "factory",
82
+ };
83
+ }
84
+ launchFromGql(g) {
85
+ return { token: addrOf(g.id), curve: addrOf(g.curve), deployer: addrOf(g.deployer), pairToken: addrOf(g.pairToken), graduationThreshold: BigInt(g.graduationThreshold), phase: g.status === "CLIMBING" ? 0 : g.status === "SWEPT" ? 1 : g.status === "RESCUED" ? 3 : 2, source: "subgraph" };
86
+ }
87
+ /** Finds a launch by token address (or curve address) through the factory, falling back to the subgraph. */
88
+ async resolveLaunch(input) {
89
+ const address = parseAddress(input, "address");
90
+ if (this.config.factory) {
91
+ const direct = await this.launchFromFactory(this.config.factory, address);
92
+ if (direct)
93
+ return direct;
94
+ const token = await this.read(address, curveAbi, "token").then((v) => (addrOf(v) === NATIVE_QUOTE ? undefined : addrOf(v)), () => undefined);
95
+ if (token) {
96
+ const viaCurve = await this.launchFromFactory(this.config.factory, token);
97
+ if (viaCurve)
98
+ return viaCurve;
99
+ }
100
+ }
101
+ if (this.subgraph) {
102
+ const g = (await this.subgraph.coin(address)) ?? (await this.subgraph.coinByCurve(address));
103
+ if (g)
104
+ return this.launchFromGql(g);
105
+ }
106
+ const hint = this.config.factory || this.subgraph ? "" : " (set PARABOLIC_FACTORY and/or PARABOLIC_SUBGRAPH_URL)";
107
+ throw new Error(`No Parabolic coin at ${address} on ${chainName(this.config.chainId)}${hint}`);
108
+ }
109
+ async liveTerms(curve) {
110
+ const c = await this.readMany(curve, curveAbi, ["getReserves", "feeBps", "creatorTaxBps", "currentSnipeTaxBps", "sellableTokens", "graduated", "isNativeQuote", "readyToGraduate"]);
111
+ const reserves = c.getReserves;
112
+ const native = bool(c.isNativeQuote);
113
+ return {
114
+ quoteReserve: big(reserves[0]), tokenReserve: big(reserves[1]), feeBps: big(c.feeBps), creatorTaxBps: big(c.creatorTaxBps), snipeBps: big(c.currentSnipeTaxBps),
115
+ sellableTokens: big(c.sellableTokens), graduated: bool(c.graduated), readyToGraduate: bool(c.readyToGraduate), native, quoteDecimals: native ? 18 : 6,
116
+ };
117
+ }
118
+ assertOpen(t, side) {
119
+ if (t.graduated)
120
+ throw new Error("This coin has graduated: the curve is closed, trade it on its Uniswap v4 pool");
121
+ if (side === "sell" && t.readyToGraduate)
122
+ throw new Error("The curve is ready to graduate and no longer accepts sells; anyone can call factory.graduate(token), then trade on Uniswap v4");
123
+ if (side === "buy" && t.sellableTokens <= 0n)
124
+ throw new Error("The curve's sellable allocation is exhausted; it is waiting for factory.graduate(token)");
125
+ }
126
+ // ---- listing ------------------------------------------------------------------------------------------------
127
+ async listCoins(input = {}) {
128
+ const sort = input.sort ?? "trending";
129
+ const limit = Math.min(100, Math.max(1, Math.floor(input.limit ?? 20)));
130
+ let coins;
131
+ let source;
132
+ if (this.subgraph) {
133
+ const where = input.status === "climbing" ? { status: "CLIMBING" } : input.status === "graduated" ? { status_not: "CLIMBING" } : undefined;
134
+ coins = (await this.subgraph.coins(where, 200)).map((g) => summaryFromGql(g));
135
+ source = "subgraph";
136
+ }
137
+ else if (this.config.factory) {
138
+ coins = await this.chainCoins(this.config.factory);
139
+ source = `factory TokenLaunched log scan of recent blocks (newest ${CHAIN_LIST_MAX} launches at most; public Arc RPCs cap the range, so older coins are not in this list. Set PARABOLIC_SUBGRAPH_URL for the full list with holders and volume, or read https://www.parabolic.family/api/v1/coins)`;
140
+ }
141
+ else {
142
+ throw new Error("Listing coins needs PARABOLIC_SUBGRAPH_URL (recommended) or PARABOLIC_FACTORY (TokenLaunched log-scan fallback): the factory ABI exposes getLaunchedToken(address) but no enumeration function");
143
+ }
144
+ if (input.status)
145
+ coins = coins.filter((c) => c.status === input.status);
146
+ const sorted = sortCoins(coins, sort).slice(0, limit);
147
+ return { source, sort, status: input.status ?? "all", count: sorted.length, coins: sorted };
148
+ }
149
+ /**
150
+ * Newest TokenLaunched logs, walked backwards from head in bounded windows.
151
+ *
152
+ * A single scan from the deployment block is refused by every public Arc RPC: they cap eth_getLogs
153
+ * at about 10,000 blocks and answer `requested range too large`, so the old unbounded scan made
154
+ * list_coins fail outright on a default install. Walking back a window at a time returns the newest
155
+ * launches, which is what this listing promises, and stops as soon as it has enough. A rate-limited
156
+ * or refused window ends the walk with what has been collected rather than failing the call.
157
+ *
158
+ * This covers recent history only. PARABOLIC_SUBGRAPH_URL is the full list, and carries holders and
159
+ * volume with it.
160
+ */
161
+ async recentLaunchLogs(factory) {
162
+ const WINDOW = 9000n;
163
+ const MAX_WINDOWS = 20;
164
+ const floor = this.config.factoryStartBlock;
165
+ const collected = [];
166
+ let to;
167
+ try {
168
+ to = await this.rpc.blockNumber();
169
+ }
170
+ catch {
171
+ // No block number, no windowing: fall back to one scan from the configured floor.
172
+ return this.rpc.logs({ address: factory, abi: factoryAbi, eventName: "TokenLaunched", fromBlock: floor });
173
+ }
174
+ for (let i = 0; i < MAX_WINDOWS && to >= floor; i++) {
175
+ const from = to - WINDOW + 1n > floor ? to - WINDOW + 1n : floor;
176
+ try {
177
+ const batch = await this.rpc.logs({ address: factory, abi: factoryAbi, eventName: "TokenLaunched", fromBlock: from, toBlock: to });
178
+ collected.unshift(...batch);
179
+ }
180
+ catch {
181
+ break;
182
+ }
183
+ if (collected.length >= CHAIN_LIST_MAX || from === floor)
184
+ break;
185
+ to = from - 1n;
186
+ }
187
+ return collected;
188
+ }
189
+ async chainCoins(factory) {
190
+ const logs = await this.recentLaunchLogs(factory);
191
+ const newest = logs.slice(-CHAIN_LIST_MAX).reverse();
192
+ return mapLimit(newest, 8, async (log) => {
193
+ const a = log.args;
194
+ const token = addrOf(a.token);
195
+ const curve = addrOf(a.curve);
196
+ const pairToken = addrOf(a.pairToken);
197
+ const threshold = big(a.graduationThreshold);
198
+ const [tk, c] = await Promise.all([
199
+ this.readMany(token, tokenAbi, ["name", "symbol", "totalSupply"]),
200
+ this.readMany(curve, curveAbi, ["getReserves", "realQuoteReserve", "graduated", "launchedAt", "phantomQuote", "reservedTokens"]),
201
+ ]);
202
+ const native = isNative(pairToken);
203
+ const qd = native ? 18 : 6;
204
+ const reserves = c.getReserves;
205
+ const qR = big(reserves[0]);
206
+ const tR = big(reserves[1]);
207
+ const supply = big(tk.totalSupply);
208
+ const graduated = bool(c.graduated);
209
+ const real = big(c.realQuoteReserve);
210
+ const mcap = graduated ? graduationMarketCap(big(c.phantomQuote), threshold, big(c.reservedTokens), supply, qd) : marketCap(qR, tR, supply, qd);
211
+ const raised = graduated ? threshold : real;
212
+ return {
213
+ address: token, curve, name: str(tk.name), ticker: str(tk.symbol), creator: addrOf(a.deployer), pair: native ? "USDC" : "EURC", pairToken, quoteDecimals: qd,
214
+ status: graduated ? "graduated" : "climbing", raised: amount(raised, qd).amount, raisedRaw: raised, threshold: amount(threshold, qd).amount, thresholdRaw: threshold,
215
+ progressPct: graduated ? 100 : progressPct(real, threshold), price: graduated ? (supply > 0n ? mcap / toNumber(supply) : 0) : spotPrice(qR, tR, qd), marketCap: mcap,
216
+ createdAt: num(c.launchedAt), launchConfigId: num(a.launchConfigId), source: "chain",
217
+ };
218
+ });
219
+ }
220
+ // ---- coin detail --------------------------------------------------------------------------------------------
221
+ async getCoin(address) {
222
+ const launch = await this.resolveLaunch(address);
223
+ const [chain, gql] = await Promise.all([this.chainDetail(launch), this.subgraph ? this.subgraph.coin(launch.token).catch(() => null) : Promise.resolve(null)]);
224
+ const indexed = gql ? summaryFromGql(gql) : undefined;
225
+ return {
226
+ ...chain,
227
+ holders: indexed?.holders ?? null,
228
+ volume24h: indexed?.volume24h ?? null,
229
+ change24h: indexed?.change24h ?? null,
230
+ tradeCount: indexed?.tradeCount ?? null,
231
+ lastTradeAt: indexed?.lastTradeAt ?? null,
232
+ pool: { ...chain.pool, poolId: chain.pool.poolId ?? indexed?.poolId ?? null, positionId: gql?.positionId ?? null },
233
+ logo: chain.logo || indexed?.logo || "",
234
+ description: chain.description || indexed?.description || "",
235
+ indexed: gql ? { status: gql.status, graduatedAt: indexed?.graduatedAt ?? null, volumeQuote: amount(BigInt(gql.volumeQuote), chain.quoteDecimals), buyCount: Number(gql.buyCount), sellCount: Number(gql.sellCount) } : null,
236
+ sources: { chain: chain.source, subgraph: gql ? "coin entity" : this.subgraph ? "not indexed yet" : "not configured" },
237
+ };
238
+ }
239
+ async chainDetail(l) {
240
+ const [tk, c, hook] = await Promise.all([
241
+ this.readMany(l.token, tokenAbi, ["name", "symbol", "totalSupply", "getTokenInfo"]),
242
+ this.readMany(l.curve, curveAbi, ["getReserves", "realQuoteReserve", "graduated", "readyToGraduate", "launchedAt", "currentSnipeTaxBps", "snipeTaxStartBps", "snipeTaxSeconds", "feeBps", "creatorTaxBps", "phantomQuote", "reservedTokens", "sellableTokens", "protocolFeeShareBps", "buybackBurnBps", "isNativeQuote"]),
243
+ this.hookAddress(),
244
+ ]);
245
+ const native = bool(c.isNativeQuote);
246
+ const qd = native ? 18 : 6;
247
+ const reserves = c.getReserves;
248
+ const qR = big(reserves[0]);
249
+ const tR = big(reserves[1]);
250
+ const supply = big(tk.totalSupply);
251
+ const info = (Array.isArray(tk.getTokenInfo) ? tk.getTokenInfo : []);
252
+ const socials = (info[3] ?? {});
253
+ const graduated = bool(c.graduated) || (l.phase ?? 0) > 0;
254
+ const threshold = l.graduationThreshold;
255
+ const phantom = big(c.phantomQuote);
256
+ const reserved = big(c.reservedTokens);
257
+ const real = big(c.realQuoteReserve);
258
+ const raised = graduated ? threshold : real;
259
+ const mcap = graduated ? graduationMarketCap(phantom, threshold, reserved, supply, qd) : marketCap(qR, tR, supply, qd);
260
+ const launchedAt = num(c.launchedAt);
261
+ const windowSeconds = num(c.snipeTaxSeconds);
262
+ const now = Math.floor(Date.now() / 1000);
263
+ const currentSnipeBps = num(c.currentSnipeTaxBps);
264
+ const protocolShareBps = num(c.protocolFeeShareBps);
265
+ const buybackBurnBps = num(c.buybackBurnBps);
266
+ const buybackOfFeeBps = Math.round(((10_000 - protocolShareBps) * buybackBurnBps) / 10_000);
267
+ const poolId = l.poolFee !== undefined && l.tickSpacing !== undefined && hook ? poolIdOf(l.token, l.pairToken, l.poolFee, l.tickSpacing, hook) : undefined;
268
+ const chainId = this.config.chainId;
269
+ return {
270
+ address: l.token,
271
+ curve: l.curve,
272
+ name: str(tk.name),
273
+ ticker: str(tk.symbol),
274
+ creator: l.deployer,
275
+ creatorFeeRecipient: l.creatorFeeRecipient ?? null,
276
+ pair: native ? "USDC" : "EURC",
277
+ pairToken: l.pairToken,
278
+ quoteDecimals: qd,
279
+ status: graduated ? "graduated" : "climbing",
280
+ phase: PHASES[l.phase ?? (graduated ? 1 : 0)] ?? "Unknown",
281
+ readyToGraduate: bool(c.readyToGraduate),
282
+ raised: amount(raised, qd),
283
+ threshold: amount(threshold, qd),
284
+ progressPct: graduated ? 100 : progressPct(real, threshold),
285
+ price: graduated ? (supply > 0n ? mcap / toNumber(supply) : 0) : spotPrice(qR, tR, qd),
286
+ marketCap: mcap,
287
+ marketCapSource: graduated ? "pool seeding price: (phantomQuote + graduationThreshold) / reservedTokens" : "curve reserves",
288
+ supply: amount(supply, 18),
289
+ reserves: { quote: amount(qR, qd), tokens: amount(tR, 18), realQuote: amount(real, qd), phantomQuote: amount(phantom, qd), reservedTokens: amount(reserved, 18), sellableTokens: amount(big(c.sellableTokens), 18) },
290
+ fees: {
291
+ feeBps: num(c.feeBps),
292
+ creatorTaxBps: num(c.creatorTaxBps),
293
+ split: { protocolBps: protocolShareBps, buybackBps: buybackOfFeeBps, creatorBps: 10_000 - protocolShareBps - buybackOfFeeBps, note: "shares of the base fee (and of snipe-tax revenue); the creator tax goes entirely to the creator" },
294
+ policy: { protocolFeeShareBps: protocolShareBps, buybackBurnBps },
295
+ },
296
+ snipeTax: {
297
+ currentBps: currentSnipeBps,
298
+ startBps: num(c.snipeTaxStartBps),
299
+ windowSeconds,
300
+ launchedAt,
301
+ windowEndsAt: launchedAt + windowSeconds,
302
+ windowOpen: currentSnipeBps > 0,
303
+ secondsRemaining: Math.max(0, launchedAt + windowSeconds - now),
304
+ exempt: "the creator only, for the buy in their own launch; there is no other exemption pathway. Check curve.snipeTaxExempt(address) or pass `buyer` to quote_buy",
305
+ },
306
+ pool: { poolId: poolId ?? null, poolFee: l.poolFee ?? null, tickSpacing: l.tickSpacing ?? null, hook: hook ?? null },
307
+ createdAt: launchedAt || null,
308
+ logo: str(info[1]),
309
+ description: str(info[2]),
310
+ socials: { twitter: str(socials.twitter), telegram: str(socials.telegram), discord: str(socials.discord), website: str(socials.website), farcaster: str(socials.farcaster) },
311
+ links: { app: `https://parabolic.family/coin/${l.token}`, token: explorerUrl(chainId, "address", l.token) ?? null, curve: explorerUrl(chainId, "address", l.curve) ?? null, creator: explorerUrl(chainId, "address", l.deployer) ?? null },
312
+ source: l.source === "factory" ? "factory.getLaunchedToken + curve + token reads" : "subgraph lookup + curve + token reads",
313
+ };
314
+ }
315
+ // ---- quotes -------------------------------------------------------------------------------------------------
316
+ async buyerSnipeBps(curve, t, buyer) {
317
+ if (buyer === undefined)
318
+ return { snipeBps: t.snipeBps, exempt: false };
319
+ const exempt = bool(await this.read(curve, curveAbi, "snipeTaxExempt", [parseAddress(buyer, "buyer")]));
320
+ return { snipeBps: exempt ? 0n : t.snipeBps, exempt };
321
+ }
322
+ async quoteBuy(input) {
323
+ const launch = await this.resolveLaunch(input.address);
324
+ const t = await this.liveTerms(launch.curve);
325
+ this.assertOpen(t, "buy");
326
+ const qd = t.quoteDecimals;
327
+ const quoteIn = parseAmount(input.quoteIn, qd, "quoteIn");
328
+ const { snipeBps, exempt } = await this.buyerSnipeBps(launch.curve, t, input.buyer);
329
+ const q = quoteBuy(quoteIn, { ...t, snipeBps });
330
+ const slippage = slippageOf(input.slippageBps);
331
+ const minTokensOut = applySlippage(q.tokensOut, slippage);
332
+ return {
333
+ side: "buy",
334
+ coin: launch.token,
335
+ curve: launch.curve,
336
+ pair: t.native ? "USDC" : "EURC",
337
+ quoteIn: amount(quoteIn, qd),
338
+ tokensOut: amount(q.tokensOut, 18),
339
+ minTokensOut: { ...amount(minTokensOut, 18), slippageBps: Number(slippage) },
340
+ spent: amount(q.spent, qd),
341
+ refund: amount(q.refund, qd),
342
+ fees: {
343
+ totalBps: Number(t.feeBps + t.creatorTaxBps + snipeBps),
344
+ fee: { bps: Number(t.feeBps), ...amount(q.fee, qd) },
345
+ creatorTax: { bps: Number(t.creatorTaxBps), ...amount(q.creatorTax, qd) },
346
+ snipeTax: { bps: Number(snipeBps), ...amount(q.snipeTax, qd), windowOpen: t.snipeBps > 0n, buyerExempt: exempt },
347
+ netToCurve: amount(q.netToCurve, qd),
348
+ },
349
+ spotPrice: spotPrice(t.quoteReserve, t.tokenReserve, qd),
350
+ effectivePrice: q.tokensOut > 0n ? toNumber(q.spent, qd) / toNumber(q.tokensOut) : 0,
351
+ priceImpactPct: priceImpact(q.spent, q.tokensOut, t.quoteReserve, t.tokenReserve),
352
+ graduatesCoin: q.crossing,
353
+ reserves: { quote: amount(t.quoteReserve, qd), tokens: amount(t.tokenReserve, 18), sellableTokens: amount(t.sellableTokens, 18) },
354
+ notes: [
355
+ ...(q.crossing ? ["This buy takes the curve's last sellable tokens: it fills to the threshold, refunds the rest, and graduates the coin in the same transaction. Send extra gas (the web adds 300,000 plus 25% headroom)."] : []),
356
+ ...(t.snipeBps > 0n && !exempt ? ["The launch snipe-tax window is open; the tax decays every second, so a quote taken now overstates the tax a later block charges."] : []),
357
+ "minTokensOut is enforced as a price bound: spent × minTokensOut ≤ received × tokensOut.",
358
+ ],
359
+ };
360
+ }
361
+ async quoteSell(input) {
362
+ const launch = await this.resolveLaunch(input.address);
363
+ const t = await this.liveTerms(launch.curve);
364
+ this.assertOpen(t, "sell");
365
+ const qd = t.quoteDecimals;
366
+ const tokensIn = parseAmount(input.tokensIn, 18, "tokensIn");
367
+ const q = quoteSell(tokensIn, t);
368
+ const slippage = slippageOf(input.slippageBps);
369
+ const minQuoteOut = applySlippage(q.quoteOut, slippage);
370
+ return {
371
+ side: "sell",
372
+ coin: launch.token,
373
+ curve: launch.curve,
374
+ pair: t.native ? "USDC" : "EURC",
375
+ tokensIn: amount(tokensIn, 18),
376
+ quoteOut: amount(q.quoteOut, qd),
377
+ minQuoteOut: { ...amount(minQuoteOut, qd), slippageBps: Number(slippage) },
378
+ fees: {
379
+ totalBps: Number(t.feeBps + t.creatorTaxBps),
380
+ fee: { bps: Number(t.feeBps), ...amount(q.fee, qd) },
381
+ creatorTax: { bps: Number(t.creatorTaxBps), ...amount(q.creatorTax, qd) },
382
+ gross: amount(q.gross, qd),
383
+ snipeTax: { bps: 0, note: "sells carry no snipe tax" },
384
+ },
385
+ spotPrice: spotPrice(t.quoteReserve, t.tokenReserve, qd),
386
+ effectivePrice: tokensIn > 0n ? toNumber(q.quoteOut, qd) / toNumber(tokensIn) : 0,
387
+ priceImpactPct: priceImpact(tokensIn, q.quoteOut, t.tokenReserve, t.quoteReserve),
388
+ reserves: { quote: amount(t.quoteReserve, qd), tokens: amount(t.tokenReserve, 18) },
389
+ notes: ["The curve pulls the tokens with transferFrom: approve the curve for tokensIn first (build_sell_tx returns the approval)."],
390
+ };
391
+ }
392
+ // ---- unsigned transactions ----------------------------------------------------------------------------------
393
+ async buildBuyTx(input) {
394
+ const launch = await this.resolveLaunch(input.address);
395
+ const t = await this.liveTerms(launch.curve);
396
+ this.assertOpen(t, "buy");
397
+ const qd = t.quoteDecimals;
398
+ const chainId = this.config.chainId;
399
+ const quoteIn = parseAmount(input.quoteIn, qd, "quoteIn");
400
+ const recipient = parseAddress(input.recipient, "recipient");
401
+ const { snipeBps, exempt } = await this.buyerSnipeBps(launch.curve, t, input.buyer);
402
+ const q = quoteBuy(quoteIn, { ...t, snipeBps });
403
+ const slippage = slippageOf(input.slippageBps);
404
+ const minTokensOut = input.minTokensOut !== undefined ? parseAmount(input.minTokensOut, 18, "minTokensOut") : applySlippage(q.tokensOut, slippage);
405
+ const warnings = [];
406
+ if (!passesPriceBound(quoteIn, q, minTokensOut))
407
+ warnings.push("minTokensOut is above the current quote: the transaction would revert with SlippageExceeded unless the price improves before it lands");
408
+ if (t.snipeBps > 0n && !exempt)
409
+ warnings.push(`The snipe-tax window is open (${t.snipeBps} bps now, decaying to 0 at the end of the window); consider waiting or expect fewer tokens`);
410
+ const quote = {
411
+ quoteIn: amount(quoteIn, qd), expectedTokensOut: amount(q.tokensOut, 18), minTokensOut: amount(minTokensOut, 18), slippageBps: input.minTokensOut !== undefined ? null : Number(slippage),
412
+ fees: { feeBps: Number(t.feeBps), creatorTaxBps: Number(t.creatorTaxBps), snipeTaxBps: Number(snipeBps), buyerExempt: exempt }, priceImpactPct: priceImpact(q.spent, q.tokensOut, t.quoteReserve, t.tokenReserve), graduatesCoin: q.crossing, refund: amount(q.refund, qd),
413
+ };
414
+ const gas = { note: q.crossing ? "estimate, add 300,000 for the graduation the buy triggers, then multiply by 1.25 (web/lib/gas.ts)" : "estimate then multiply by 1.25: the snipe tax is priced per block, so a buy quoted in one block runs a slightly different path in the next (web/lib/gas.ts)" };
415
+ if (input.memo) {
416
+ if (!this.config.memoRouter)
417
+ throw new Error("Memo-routed buys need PARABOLIC_MEMO_ROUTER");
418
+ if (!t.native)
419
+ throw new Error("Memo-routed buys are only available for native-USDC curves");
420
+ const m = encodeMemoBuy({ arcMemo: this.config.arcMemo, router: this.config.memoRouter, curve: launch.curve, quoteIn, minTokensOut, recipient, referral: input.memo.referral, note: input.memo.note, chainId });
421
+ return {
422
+ kind: "memo_buy",
423
+ coin: launch.token, curve: launch.curve,
424
+ eoaOnly: true,
425
+ steps: ["1. approval (skip if the router's allowance on the USDC ERC-20 view already covers usdcUnits)", "2. tx"],
426
+ approval: m.approval,
427
+ tx: m.tx,
428
+ usdcUnits: m.usdcUnits,
429
+ memo: { id: m.memoId, payload: m.payload, referral: input.memo.referral ?? null, note: input.memo.note ?? "" },
430
+ quote, gas, warnings,
431
+ notes: ["Arc's Memo contract only accepts calls from an EOA (its caller must be tx.origin) and forwards no value, so the router pulls whole 6-decimal USDC units through the ERC-20 view. Smart-account wallets must use the direct curve buy instead (omit `memo`)."],
432
+ };
433
+ }
434
+ const tx = encodeBuy(launch.curve, quoteIn, minTokensOut, recipient, t.native, chainId);
435
+ const approval = t.native ? null : encodeApprove(launch.pairToken, launch.curve, quoteIn, chainId, "pair token");
436
+ return {
437
+ kind: "buy",
438
+ coin: launch.token, curve: launch.curve,
439
+ steps: t.native ? ["1. tx (quote rides as msg.value)"] : ["1. approval (skip if the curve's allowance already covers quoteIn)", "2. tx"],
440
+ approval,
441
+ tx,
442
+ quote, gas, warnings,
443
+ notes: [
444
+ "Unsigned. Sign and send from the wallet that owns the funds; recipient receives the tokens.",
445
+ "A memo-routed variant (ParabolicMemoRouter through Arc's Memo contract, carrying a referral code) exists: pass `memo: { referral }`. It is EOA-only.",
446
+ ],
447
+ };
448
+ }
449
+ async buildSellTx(input) {
450
+ const launch = await this.resolveLaunch(input.address);
451
+ const t = await this.liveTerms(launch.curve);
452
+ this.assertOpen(t, "sell");
453
+ const qd = t.quoteDecimals;
454
+ const chainId = this.config.chainId;
455
+ const tokensIn = parseAmount(input.tokensIn, 18, "tokensIn");
456
+ const recipient = parseAddress(input.recipient, "recipient");
457
+ const q = quoteSell(tokensIn, t);
458
+ const slippage = slippageOf(input.slippageBps);
459
+ const minQuoteOut = input.minQuoteOut !== undefined ? parseAmount(input.minQuoteOut, qd, "minQuoteOut") : applySlippage(q.quoteOut, slippage);
460
+ const warnings = [];
461
+ if (minQuoteOut > q.quoteOut)
462
+ warnings.push("minQuoteOut is above the current quote: the transaction would revert with SlippageExceeded unless the price improves before it lands");
463
+ return {
464
+ kind: "sell",
465
+ coin: launch.token, curve: launch.curve,
466
+ steps: ["1. approval (skip if the curve's allowance already covers tokensIn)", "2. tx"],
467
+ approval: encodeApprove(launch.token, launch.curve, tokensIn, chainId, "coin"),
468
+ tx: encodeSell(launch.curve, tokensIn, minQuoteOut, recipient, chainId),
469
+ quote: { tokensIn: amount(tokensIn, 18), expectedQuoteOut: amount(q.quoteOut, qd), minQuoteOut: amount(minQuoteOut, qd), slippageBps: input.minQuoteOut !== undefined ? null : Number(slippage), fees: { feeBps: Number(t.feeBps), creatorTaxBps: Number(t.creatorTaxBps) }, priceImpactPct: priceImpact(tokensIn, q.quoteOut, t.tokenReserve, t.quoteReserve) },
470
+ warnings,
471
+ notes: ["Unsigned. The seller signs both; the curve pays the quote to recipient.", "Sells close once the curve is ready to graduate; after graduation sell on the Uniswap v4 pool."],
472
+ };
473
+ }
474
+ async buildLaunchTx(input) {
475
+ const factory = this.requireFactory();
476
+ const chainId = this.config.chainId;
477
+ const creator = parseAddress(input.creator, "creator");
478
+ const pair = input.pair ?? "USDC";
479
+ let pairToken = NATIVE_QUOTE;
480
+ if (pair === "EURC") {
481
+ if (!this.config.eurc)
482
+ throw new Error("EURC launches need PARABOLIC_EURC");
483
+ pairToken = this.config.eurc;
484
+ }
485
+ const name = input.name.trim();
486
+ const symbol = input.ticker.trim();
487
+ const logo = (input.image ?? "").trim();
488
+ const description = (input.description ?? "").trim();
489
+ if (name.length === 0 || bytesOf(name) > METADATA_LIMITS.name)
490
+ throw new Error(`name must be 1–${METADATA_LIMITS.name} bytes`);
491
+ if (!/^[A-Za-z0-9]{1,16}$/.test(symbol))
492
+ throw new Error(`ticker must be 1–${METADATA_LIMITS.symbol} letters or digits (the app shows it upper-case)`);
493
+ if (bytesOf(logo) > METADATA_LIMITS.logo)
494
+ throw new Error(`image must be at most ${METADATA_LIMITS.logo} bytes (URL or ipfs:// pointer)`);
495
+ if (bytesOf(description) > METADATA_LIMITS.description)
496
+ throw new Error(`description must be at most ${METADATA_LIMITS.description} bytes`);
497
+ const socials = { twitter: "", telegram: "", discord: "", website: "", farcaster: "" };
498
+ for (const k of Object.keys(socials)) {
499
+ const v = (input.socials?.[k] ?? "").trim();
500
+ if (bytesOf(v) > METADATA_LIMITS.social)
501
+ throw new Error(`socials.${k} must be at most ${METADATA_LIMITS.social} bytes`);
502
+ socials[k] = v;
503
+ }
504
+ const creatorTaxBps = input.creatorTaxBps ?? 0;
505
+ if (!Number.isInteger(creatorTaxBps) || creatorTaxBps < 0)
506
+ throw new Error("creatorTaxBps must be a non-negative integer (100 = 1%)");
507
+ const configId = this.config.launchConfigId;
508
+ const [f, economics, canLaunch, cfgRaw, pairEconRaw, pairApproved] = await Promise.all([
509
+ this.readMany(factory, factoryAbi, ["launchFee", "launchEnabled", "maxCreatorTaxBps", "snipeTaxStartBps", "snipeTaxSeconds", "launchForwarder"]),
510
+ this.read(factory, factoryAbi, "previewLaunchEconomics", [configId, pairToken]),
511
+ this.read(factory, factoryAbi, "canLaunch", [creator]),
512
+ this.read(factory, factoryAbi, "getLaunchConfig", [configId]),
513
+ pair === "EURC" ? this.read(factory, factoryAbi, "pairTokenEconomics", [pairToken]) : Promise.resolve(undefined),
514
+ pair === "EURC" ? this.read(factory, factoryAbi, "approvedPairTokens", [pairToken]) : Promise.resolve(true),
515
+ ]);
516
+ const cfg = asStruct(cfgRaw, CONFIG_FIELDS);
517
+ const maxTax = num(f.maxCreatorTaxBps);
518
+ if (creatorTaxBps > maxTax)
519
+ throw new Error(`creatorTaxBps ${creatorTaxBps} exceeds the factory's maxCreatorTaxBps (${maxTax})`);
520
+ const launchFee = big(f.launchFee);
521
+ const warnings = [];
522
+ if (!bool(f.launchEnabled))
523
+ warnings.push("launchEnabled() is false on the factory: the transaction would revert until launching is re-enabled");
524
+ if (!bool(canLaunch))
525
+ warnings.push("canLaunch(creator) is false: the launcher allowlist is on and this wallet is not on it (NotWhitelisted)");
526
+ if (!bool(cfg.enabled))
527
+ warnings.push(`launch config ${configId} is disabled (LaunchConfigDisabled)`);
528
+ if (!bool(pairApproved))
529
+ warnings.push("the EURC pair token is not approved on the factory (PairTokenNotApproved)");
530
+ // Pair-specific economics override the config's phantom / threshold for ERC-20 pairs.
531
+ const pairEcon = Array.isArray(pairEconRaw) ? pairEconRaw : undefined;
532
+ const qd = pair === "USDC" ? 18 : 6;
533
+ const phantom = pairEcon && big(pairEcon[0]) > 0n ? big(pairEcon[0]) : big(cfg.phantomQuote);
534
+ const threshold = pairEcon && big(pairEcon[1]) > 0n ? big(pairEcon[1]) : big(cfg.graduationThreshold);
535
+ const supply = big(cfg.supply);
536
+ const reserved = reservedTokensFor(supply, phantom, threshold);
537
+ const salt = toHex(randomBytes(32));
538
+ const expectedEconomics = (typeof economics === "string" ? economics : "0x");
539
+ const router = this.config.memoRouter;
540
+ const devIn = input.devBuy !== undefined ? parseAmount(input.devBuy, qd, "devBuy") : undefined;
541
+ // Fair launch: with the router configured, the opening buy rides inside the launch transaction.
542
+ const atomic = devIn !== undefined && router !== undefined;
543
+ const forwarder = addrOf(f.launchForwarder);
544
+ if (atomic && forwarder !== router)
545
+ warnings.push(`factory.launchForwarder() is ${forwarder}, not PARABOLIC_MEMO_ROUTER ${router}: the router's launch would revert (NotLaunchForwarder)`);
546
+ // A routed launch must leave creatorFeeRecipient zero (the factory then uses the creator) or equal to the signer.
547
+ const params = { name, symbol, logo, description, socials, creatorFeeRecipient: atomic ? NATIVE_QUOTE : creator, creatorTaxBps, buybackEnabled: input.buybackEnabled ?? true, expectedEconomics, salt };
548
+ // The opening buy is quoted on the fresh curve; the creator is snipe-tax exempt, so it carries no snipe leg.
549
+ const devTerms = { quoteReserve: phantom, tokenReserve: supply, feeBps: big(cfg.curveFeeBps), creatorTaxBps: BigInt(creatorTaxBps), snipeBps: 0n, sellableTokens: supply - reserved };
550
+ const devQuote = devIn !== undefined ? quoteBuy(devIn, devTerms) : undefined;
551
+ const devMin = devQuote ? applySlippage(devQuote.tokensOut, DEV_BUY_SLIPPAGE_BPS) : 0n;
552
+ const devSummary = devIn !== undefined && devQuote
553
+ ? { quoteIn: amount(devIn, qd), expectedTokensOut: amount(devQuote.tokensOut, 18), minTokensOut: { ...amount(devMin, 18), slippageBps: Number(DEV_BUY_SLIPPAGE_BPS) }, fees: { feeBps: Number(cfg.curveFeeBps), creatorTaxBps, snipeTaxBps: 0 }, graduatesCoin: devQuote.crossing, refund: amount(devQuote.refund, qd) }
554
+ : undefined;
555
+ const split = graduationSplit(reserved, phantom, threshold);
556
+ const pctOf = (v) => (supply > 0n ? Number((v * 1000000n) / supply) / 10_000 : 0);
557
+ const common = {
558
+ params,
559
+ launchConfigId: configId.toString(),
560
+ pair,
561
+ pairToken,
562
+ launchFee: amount(launchFee, 18),
563
+ terms: {
564
+ supply: amount(supply, 18),
565
+ curveFeeBps: Number(cfg.curveFeeBps),
566
+ creatorTaxBps,
567
+ phantomQuote: amount(phantom, qd),
568
+ graduationThreshold: amount(threshold, qd),
569
+ reservedTokens: { ...amount(reserved, 18), pct: pctOf(reserved) },
570
+ poolSeedTokens: { ...amount(split.poolSeed, 18), pct: pctOf(split.poolSeed) },
571
+ lockedTokens: { ...amount(split.locked, 18), pct: pctOf(split.locked) },
572
+ openingMarketCap: marketCap(phantom, supply, supply, qd),
573
+ graduationMarketCap: graduationMarketCap(phantom, threshold, reserved, supply, qd),
574
+ snipeTax: { startBps: num(f.snipeTaxStartBps), windowSeconds: num(f.snipeTaxSeconds) },
575
+ poolFee: num(cfg.poolFee),
576
+ tickSpacing: num(cfg.tickSpacing),
577
+ },
578
+ event: "TokenLaunched(address indexed token, address indexed curve, address indexed deployer, address pairToken, uint256 launchConfigId, uint256 graduationThreshold)",
579
+ warnings,
580
+ };
581
+ const economicsNote = "expectedEconomics pins the curve terms read now; if the factory's config changes before the transaction lands it reverts with LaunchEconomicsMismatch instead of launching on different terms.";
582
+ if (atomic && router !== undefined && devIn !== undefined && devSummary) {
583
+ const native = pair === "USDC";
584
+ const { tx, approval } = encodeLaunchAndBuy(router, params, configId, pairToken, devIn, devMin, launchFee, native, chainId);
585
+ return {
586
+ kind: "launch_and_buy",
587
+ tx,
588
+ approval,
589
+ steps: approval ? ["1. approval (the router pulls devBuy of the pair token once the launch has succeeded)", "2. tx (launch fee as value)"] : ["1. tx (launch fee + opening buy as value)"],
590
+ ...common,
591
+ snipeTaxExemptions: [],
592
+ devBuy: { atomic: true, ...devSummary },
593
+ events: ["RoutedLaunch(user, token, curve, launchConfigId, pairToken)", "RoutedBuy(user, curve, recipient, quoteSpent, tokensOut, refund)", common.event],
594
+ notes: [
595
+ `Unsigned single transaction to ParabolicMemoRouter.${native ? "launchAndBuy" : "launchAndBuyWithToken"}: the launch and the creator's opening buy settle together, before anyone else can trade. Any wallet can call it directly (the memo-wrapped form is EOA-only and cannot carry value).`,
596
+ "The router forwards the signer as deployer (factory.launchTokenFor) and places the buy through curve.buyFor(signer, …), so it is snipe-tax exempt; unspent quote (rounding, or a fill capped at graduation) is refunded to the signer. creatorFeeRecipient is left zero: the router rejects any value other than zero or the signer (FeeRecipientMustBeCreator), and the factory then uses the creator.",
597
+ native ? "value must equal launchFee + devBuy exactly (ValueMismatch otherwise)." : "value must equal launchFee exactly; devBuy is pulled in the pair token after the approval.",
598
+ economicsNote,
599
+ ],
600
+ };
601
+ }
602
+ const tx = encodeLaunch(factory, params, configId, pairToken, launchFee, chainId);
603
+ const devBuy = devIn !== undefined && devSummary
604
+ ? {
605
+ atomic: false,
606
+ how: "Second transaction from the creator wallet after the launch is mined: read `curve` from the TokenLaunched event (or parabolic.get_coin), then send this calldata to it. The deployer is snipe-tax exempt, so the quote carries no snipe tax. Set PARABOLIC_MEMO_ROUTER to get this as one launchAndBuy transaction instead.",
607
+ // Null, not prose: this object has the same shape as the unsigned transactions the other
608
+ // tools return, and an agent that sends to every `to` it sees must fail here rather than
609
+ // resolve a sentence into an address.
610
+ to: null,
611
+ toHint: "the curve address from the TokenLaunched event, or parabolic.get_coin on the new token",
612
+ data: encodeFunctionData({ abi: curveAbi, functionName: "buy", args: [devIn, devMin, creator] }),
613
+ value: pair === "USDC" ? toHex(devIn) : "0x0",
614
+ valueWei: pair === "USDC" ? devIn.toString() : "0",
615
+ chainId,
616
+ ...devSummary,
617
+ approvalNeeded: pair === "EURC" ? "approve the curve to spend devBuy EURC first" : null,
618
+ }
619
+ : null;
620
+ return {
621
+ kind: "launch",
622
+ tx,
623
+ steps: ["1. tx (launch fee rides as msg.value)", ...(devBuy ? ["2. devBuy after the receipt (see devBuy.how)"] : [])],
624
+ ...common,
625
+ devBuy,
626
+ notes: [
627
+ "Unsigned. The creator wallet signs; it becomes the deployer (snipe-tax exempt, creator-fee claimant through the fee escrow).",
628
+ economicsNote,
629
+ "ParabolicMemoRouter.launch / launchAndBuy exist for routed launches (the opening buy inside the launch transaction; a memo-wrapped call also carries a referral code but is EOA-only). Set PARABOLIC_MEMO_ROUTER and pass devBuy to get launchAndBuy.",
630
+ ],
631
+ };
632
+ }
633
+ // ---- protocol -----------------------------------------------------------------------------------------------
634
+ async protocolStats() {
635
+ const [indexed, factory] = await Promise.all([
636
+ this.subgraph ? this.subgraph.protocol().then(mapProtocol).catch((e) => ({ error: e instanceof Error ? e.message : String(e) })) : Promise.resolve(null),
637
+ this.config.factory ? this.factoryStats(this.config.factory).catch((e) => ({ error: e instanceof Error ? e.message : String(e) })) : Promise.resolve(null),
638
+ ]);
639
+ return {
640
+ chain: { id: this.config.chainId, name: chainName(this.config.chainId), rpc: this.config.rpcUrl },
641
+ indexed,
642
+ factory,
643
+ notes: [
644
+ ...(indexed ? [] : ["Set PARABOLIC_SUBGRAPH_URL for launches, graduations, volume and fees."]),
645
+ ...(factory ? [] : ["Set PARABOLIC_FACTORY for the live launch fee, curve preset and snipe-tax terms."]),
646
+ ],
647
+ };
648
+ }
649
+ async factoryStats(factory) {
650
+ const names = ["launchFee", "launchEnabled", "maxCreatorTaxBps", "snipeTaxStartBps", "snipeTaxSeconds", "launchConfigCount", "feeEscrow", "buybackVault", "locker", "memeHook", "poolManager", "positionManager", "launchDeployer", "graduationExecutor", "launchForwarder", "owner"];
651
+ const [f, cfgRaw] = await Promise.all([this.readMany(factory, factoryAbi, names), this.read(factory, factoryAbi, "getLaunchConfig", [this.config.launchConfigId])]);
652
+ const cfg = asStruct(cfgRaw, CONFIG_FIELDS);
653
+ const supply = big(cfg.supply);
654
+ const phantom = big(cfg.phantomQuote);
655
+ const threshold = big(cfg.graduationThreshold);
656
+ const reserved = reservedTokensFor(supply, phantom, threshold);
657
+ const split = graduationSplit(reserved, phantom, threshold);
658
+ const pctOf = (v) => (supply > 0n ? Number((v * 1000000n) / supply) / 10_000 : 0);
659
+ return {
660
+ address: factory,
661
+ launchFee: amount(big(f.launchFee), 18),
662
+ launchEnabled: bool(f.launchEnabled),
663
+ maxCreatorTaxBps: num(f.maxCreatorTaxBps),
664
+ snipeTax: { startBps: num(f.snipeTaxStartBps), windowSeconds: num(f.snipeTaxSeconds) },
665
+ launchConfigCount: num(f.launchConfigCount),
666
+ launchConfig: {
667
+ id: this.config.launchConfigId.toString(),
668
+ enabled: bool(cfg.enabled),
669
+ supply: amount(supply, 18),
670
+ curveFeeBps: num(cfg.curveFeeBps),
671
+ phantomQuote: amount(phantom, 18),
672
+ graduationThreshold: amount(threshold, 18),
673
+ reservedTokens: amount(reserved, 18),
674
+ reservedPct: pctOf(reserved),
675
+ poolSeedTokens: amount(split.poolSeed, 18),
676
+ poolSeedPct: pctOf(split.poolSeed),
677
+ lockedTokens: amount(split.locked, 18),
678
+ lockedPct: pctOf(split.locked),
679
+ openingMarketCap: marketCap(phantom, supply, supply, 18),
680
+ graduationMarketCap: graduationMarketCap(phantom, threshold, reserved, supply, 18),
681
+ poolFee: num(cfg.poolFee),
682
+ tickSpacing: num(cfg.tickSpacing),
683
+ },
684
+ contracts: { feeEscrow: addrOf(f.feeEscrow), buybackVault: addrOf(f.buybackVault), locker: addrOf(f.locker), hook: addrOf(f.memeHook), poolManager: addrOf(f.poolManager), positionManager: addrOf(f.positionManager), launchDeployer: addrOf(f.launchDeployer), graduationExecutor: addrOf(f.graduationExecutor), launchForwarder: addrOf(f.launchForwarder), owner: addrOf(f.owner) },
685
+ };
686
+ }
687
+ docs() {
688
+ return docsText(this.config);
689
+ }
690
+ }
691
+ function mapProtocol(d) {
692
+ const p = d.protocol;
693
+ const today = Math.floor(Date.now() / 1000 / 86_400);
694
+ const day = (x) => ({ day: x.day, date: new Date(x.day * 86_400_000).toISOString().slice(0, 10), coinsLaunched: Number(x.coinsLaunched), coinsGraduated: Number(x.coinsGraduated), tradeCount: Number(x.tradeCount), volume: amount(BigInt(x.volumeQuote), 18), fees: amount(BigInt(x.feesQuote), 18) });
695
+ const launched = p ? Number(p.coinsLaunched) : 0;
696
+ const graduated = p ? Number(p.coinsGraduated) : 0;
697
+ const todayStat = d.dailyStats.find((x) => x.day === today);
698
+ return {
699
+ coinsLaunched: launched,
700
+ coinsGraduated: graduated,
701
+ graduationRatePct: launched > 0 ? Math.round((graduated / launched) * 10_000) / 100 : 0,
702
+ tradeCount: p ? Number(p.tradeCount) : 0,
703
+ volume: p ? amount(BigInt(p.volumeQuote), 18) : amount(0n, 18),
704
+ fees: p ? amount(BigInt(p.feesQuote), 18) : amount(0n, 18),
705
+ today: todayStat ? day(todayStat) : null,
706
+ daily: d.dailyStats.map(day),
707
+ units: "native USDC, whole units; EURC-quoted volume is mixed in at its raw 6-decimal scale by the indexer",
708
+ };
709
+ }
710
+ //# sourceMappingURL=parabolic.js.map