@arcnow/mcp 0.1.2 → 0.2.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.
@@ -13,16 +13,18 @@
13
13
  *
14
14
  * @module
15
15
  */
16
- import { Bps, isArcNowError, QuoteAmount, TRADE_FEE_BPS, Tokens, } from "@arcnow/sdk";
17
- import { addr, curveParamsLine, effectivePrice, money, note, price, priceMove, progress, qty, quoteLine, report, section, share, venueOf, ZERO_ADDRESS, } from "../format.js";
16
+ import { Bps, isArcNowError, POOL_LP_FEE_PIPS, POOL_TOTAL_FEE_BPS, POOL_TRADE_FEE_BPS, QuoteAmount, TRADE_FEE_BPS, Tokens, } from "@arcnow/sdk";
17
+ import { addr, bpsPercent, curveFeeSplitSection, curveParamsLine, curveSplitRows, effectivePrice, lpFee, money, note, poolFeeSplitSection, poolFeesLine, price, priceMove, progress, qty, quoteLine, report, section, share, venueOf, ZERO_ADDRESS, } from "../format.js";
18
18
  import { renderError } from "./errors.js";
19
19
  import { addressArg, boolArg, decimalArg, defineTool, intArg, metadataUriArg, textArg, z } from "./schema.js";
20
20
  import { launchParams } from "./launch-params.js";
21
- import { buyPaymentLine, lpFee, quotePoolBuy, quotePoolSell } from "./pool.js";
21
+ import { buyPaymentLine, quotePoolBuy, quotePoolSell } from "./pool.js";
22
22
  import { chooseQuote, parseQuoteAmount } from "./quote.js";
23
23
  import { capText } from "./spend.js";
24
24
  import { configuredRouter, resolveMarket, strandedText } from "./venue.js";
25
25
  const TRADE_FEE = Bps.of(TRADE_FEE_BPS);
26
+ /** The shipped pool rates, for prose about the network — a pool's own are read from it. */
27
+ const POOL_HOOK_FEE = Bps.of(POOL_TRADE_FEE_BPS);
26
28
  /** The tolerances a quote is shown at, so a floor is a choice and not a guess. */
27
29
  const TOLERANCES = [50n, 100n, 300n, 1000n];
28
30
  // ─────────────────────────────────────────────────────────────────────────────
@@ -97,8 +99,15 @@ const network = defineTool({
97
99
  : `${addr(config.usdcErc20)}, 6 decimals — the SAME asset through an ERC-20 `
98
100
  + "interface. It does not pay for gas and no arcnow.io contract touches it. "
99
101
  + "The two raw integers differ by 1e12."],
100
- ["trade fee", `${TRADE_FEE.bps} bps — a flat 1% on every buy and sell, in the token's `
101
- + "quote, not a platform's to change. What a platform configures is how that 1% is divided."],
102
+ ["trade fee, on a curve", `${TRADE_FEE.bps} bps — a flat ${bpsPercent(TRADE_FEE)} on every `
103
+ + "buy and sell, in the token's quote, not a platform's to change. What a platform "
104
+ + "configures is how it is divided, FOUR ways: creator, referrer, platform, protocol. "
105
+ + "There is no developer share."],
106
+ ["trade fee, in a pool", `the same ${bpsPercent(Bps.of(POOL_TOTAL_FEE_BPS))} `
107
+ + `in all, as two charges: arcnow.io's fee hook takes ${bpsPercent(POOL_HOOK_FEE)} `
108
+ + `(${POOL_HOOK_FEE.bps} bps) of the trade in the pool's quote, split creator / platform / `
109
+ + `protocol with no referrer, and the pool keeps ${lpFee(POOL_LP_FEE_PIPS)} as its LP fee. `
110
+ + "Every pool quote reads both rates off that pool rather than assuming these."],
102
111
  ]), section("quote tokens — what a token can be priced in, for life", [
103
112
  ...config.quoteTokens.map((token) => [
104
113
  token.symbol,
@@ -177,8 +186,8 @@ const listTokens = defineTool({
177
186
  ["platform", addr(launch.platform)],
178
187
  ["graduates to", `${addr(launch.migrator)} — ${venueOf(launch.migrator, ctx.port.config)}`],
179
188
  ["quote", quoteLine(launch.quoteToken)],
180
- ["at launch", `${money(launch.initialBuy)} initial buy, ${money(launch.launchFee)} `
181
- + `launch fee, ${qty(launch.tokensOut)} tokens to the creator`],
189
+ ["at launch", `${money(launch.initialBuy)} initial buy, ${launchFeeLine(launch.launchFee)}, `
190
+ + `${qty(launch.tokensOut)} tokens to the creator`],
182
191
  ["block", `${launch.blockNumber} (tx ${launch.txHash})`],
183
192
  ];
184
193
  if (args.includeState) {
@@ -219,8 +228,8 @@ const tokenTool = defineTool({
219
228
  + "TRADES NOW: its bonding curve, its Uniswap v4 pool, or nowhere.\n\n"
220
229
  + "For a token in its pool this reports the router this server would trade through, "
221
230
  + "whether that router can actually reach the pool, the PoolManager the liquidity is in "
222
- + "(a v4 pool has no address of its own), and the pool's own LP fee, which is charged on "
223
- + "top of arcnow.io's 1%.\n\n"
231
+ + "(a v4 pool has no address of its own), and what a trade there costs arcnow.io's fee "
232
+ + "hook's 0.80% plus the pool's own 0.20% LP fee, 1.00% in all, both read off the pool.\n\n"
224
233
  + "It names the token's QUOTE TOKEN — native USDC or an ERC-20 such as EURC, fixed at "
225
234
  + "launch — with its address and decimals: every amount here, and every amount the trade "
226
235
  + "tools take for this token, is in that quote.\n\n"
@@ -341,9 +350,11 @@ async function whereItTrades(ctx, market) {
341
350
  settle(pool.quoteToken()),
342
351
  settle(pool.quoteIsCurrency0()),
343
352
  ]);
344
- // The SDK checks the hook's VERSION() before it reads the accrual, and refuses
345
- // any hook but arcnow/arc-now-fee-hook@3.x.x as UnknownHookVersion.
353
+ // The SDK checks the hook's VERSION() before it reads the accrual or the
354
+ // rates, and refuses any hook but arcnow/arc-now-fee-hook@4.x.x as
355
+ // UnknownHookVersion.
346
356
  const accrued = await settle(pool.accruedHookFee());
357
+ const fees = await settle(pool.fees());
347
358
  return section("where it trades", [
348
359
  ["venue", "its Uniswap v4 pool — graduated and migrated. The quote and trade tools route "
349
360
  + "there on their own, and every pool quote says it is one."],
@@ -370,12 +381,17 @@ async function whereItTrades(ctx, market) {
370
381
  + `currency${quoteIsCurrency0 ? 1 : 0}, as the SDK reads the key. The key orders its `
371
382
  + "currencies by address, so an ERC-20 quote can be either; every amount here is already "
372
383
  + `in ${quote.symbol}`],
373
- ["pool fee", key instanceof Error
384
+ ["fees", fees instanceof Error
385
+ ? isArcNowError(fees) && fees.code === "UnknownHookVersion"
386
+ ? `refused: UnknownHookVersion — ${fees.message}`
387
+ : `could not be read: ${fees.message}`
388
+ : poolFeesLine(fees, market.state.quoteToken)],
389
+ ["fee hook", key instanceof Error
374
390
  ? `could not be read: ${key.message}`
375
- : `${lpFee(key.fee)} — Uniswap's LP fee, on top of arcnow.io's 1%, which the fee hook `
376
- + `at ${addr(key.hooks)} takes in ${market.state.quoteToken.symbol}`],
377
- ["fee hook", "accrues arcnow.io's 1% as a PoolManager claim and pays it out to the fee "
378
- + "recipients at the start of a later swap in this pool, or when anyone calls distributeFees"],
391
+ : `${addr(key.hooks)} — takes its ${fees instanceof Error ? "share" : bpsPercent(fees.hookFeeBps)} `
392
+ + `in ${market.state.quoteToken.symbol}, accrues it as a PoolManager claim and pays it out `
393
+ + "to the creator, the platform and the protocol at the start of a later swap in this "
394
+ + "pool, or when anyone calls distributeFees. A pool swap has no referrer share."],
379
395
  ["hook fees accrued", accrued instanceof Error
380
396
  ? isArcNowError(accrued) && accrued.code === "UnknownHookVersion"
381
397
  ? `refused: UnknownHookVersion — ${accrued.message}`
@@ -387,7 +403,7 @@ async function whereItTrades(ctx, market) {
387
403
  + "part of a trader's fill"],
388
404
  ["selling", "needs an ERC-20 approval to the router first, a separate transaction. "
389
405
  + "arcnow_sell grants one only when asked, for exactly the amount sold."],
390
- ]);
406
+ ]) + (fees instanceof Error ? "" : `\n\n${poolFeeSplitSection(fees)}`);
391
407
  }
392
408
  async function settle(promise) {
393
409
  try {
@@ -403,9 +419,9 @@ const quoteBuy = defineTool({
403
419
  title: "Quote a buy",
404
420
  access: "read",
405
421
  description: "What a given amount of the token's quote would buy right now, wherever the token trades: "
406
- + "tokens out, "
407
- + "every fee and who receives it, the average price the order would fill at, and the "
408
- + "minimum-out floor at several slippage tolerances.\n\n"
422
+ + "tokens out, every fee and who receives it — on a curve the flat 1% split four ways "
423
+ + "between creator, platform, referrer and protocol the average price the order would "
424
+ + "fill at, and the minimum-out floor at several slippage tolerances.\n\n"
409
425
  + "Costs nothing and signs nothing. Use it before every buy, and use it INSTEAD of a spot "
410
426
  + "price whenever someone asks what an amount would get them — an order moves the price "
411
427
  + "across its own size, so the spot price is only ever its first infinitesimal slice.\n\n"
@@ -414,10 +430,10 @@ const quoteBuy = defineTool({
414
430
  + "that same transaction or never. Read that part before quoting a graduating buy to "
415
431
  + "anyone.\n\n"
416
432
  + "IN A UNISWAP V4 POOL (the token graduated and migrated): the quote says it is a pool "
417
- + "quote, priced by simulating the real swap through arcnow.io's router. It shows "
418
- + "arcnow.io's 1% and the pool's own LP fee as two separate charges, the average fill "
419
- + "price against the pool's spot price, and the price impact. referrer and developer do "
420
- + "not exist on a pool swap and are refused.\n\n"
433
+ + "quote, priced by simulating the real swap through arcnow.io's router. It shows the two "
434
+ + "charges apart — arcnow.io's fee hook's 0.80% and the pool's own 0.20% LP fee, 1.00% in "
435
+ + "all, both read off the pool — the average fill price against the pool's spot price, and "
436
+ + "the price impact. referrer does not exist on a pool swap and is refused.\n\n"
421
437
  + "A token that graduated but never migrated trades nowhere; this says so and names "
422
438
  + "arcnow_migrate. A quote is a snapshot of one block. Anyone else's trade changes it.\n\n"
423
439
  + "THE AMOUNT IS IN THE TOKEN'S QUOTE: native USDC, or the ERC-20 (such as EURC) it was "
@@ -432,9 +448,8 @@ const quoteBuy = defineTool({
432
448
  + "decimals."),
433
449
  referrer: addressArg("CURVE ONLY: the address credited with the referral share of the fee. With no referrer "
434
450
  + "that share goes to the platform instead. Refused for a token in its pool, whose swap "
435
- + "has no referrer.").optional(),
436
- developer: addressArg("CURVE ONLY: the address credited with the developer share of the fee. With none, it "
437
- + "goes to the platform. Refused for a token in its pool.").optional(),
451
+ + "has no referrer. (There is no developer share and no developer argument: the fee has "
452
+ + "four parties creator, platform, referrer, protocol.)").optional(),
438
453
  },
439
454
  async run(args, ctx) {
440
455
  const market = await resolveMarket(ctx, args.address);
@@ -447,14 +462,14 @@ const quoteBuy = defineTool({
447
462
  return quotePoolBuy(args, ctx, market, symbol);
448
463
  const quoteIn = parseQuoteAmount(state.quoteToken, args.quoteIn, "quoteIn");
449
464
  const quote = await curve.quoteBuy(quoteIn);
450
- const split = await curve.previewFeeSplit(quote.fee, args.referrer, args.developer);
465
+ const split = await curve.previewFeeSplit(quote.fee, args.referrer);
451
466
  const avg = effectivePrice(quote.quoteSpent, quote.tokensOut);
452
467
  return {
453
468
  text: report(`Buy quote — ${money(quoteIn)} into ${symbol} (curve ${addr(curve.address)})`, section("what you pay and get", [
454
469
  ["venue", "its bonding curve"],
455
470
  ["you send", buyPaymentLine(quoteIn, `the curve ${addr(curve.address)}`)],
456
- ["trade fee", `${money(quote.fee)} — a flat 1% of the input, taken before anything `
457
- + "reaches the reserve"],
471
+ ["trade fee", `${money(quote.fee)} — a flat ${bpsPercent(state.tradeFeeBps)} of the input, `
472
+ + "taken before anything reaches the reserve and split four ways (below)"],
458
473
  ["reaches the curve", money(quote.quoteSpent)],
459
474
  ["tokens out", qty(quote.tokensOut, symbol)],
460
475
  ["refund", quote.refund.isZero()
@@ -471,7 +486,7 @@ const quoteBuy = defineTool({
471
486
  + `of ${money(state.target)}`],
472
487
  ["tokens sold", `${qty(state.tokensSold)} → ${qty(quote.newTokensSold)}`],
473
488
  ["graduates", quote.graduates ? "YES — this is the buy that ends trading" : "no"],
474
- ]), feeSplitSection(quote.fee, split, args.referrer, args.developer), section("minimum tokens out, by tolerance", TOLERANCES.map((bps) => {
489
+ ]), curveFeeSplitSection(quote.fee, split, args.referrer), section("minimum tokens out, by tolerance", TOLERANCES.map((bps) => {
475
490
  const floor = Bps.of(10000n - bps).applyToTokens(quote.tokensOut);
476
491
  return [`${bps} bps (${Number(bps) / 100}%)`, qty(floor, symbol)];
477
492
  })), quote.graduates ? note(GRADUATING_BUY_WARNING) : undefined, note("This quote is one block old the moment it is returned. Any other trade against "
@@ -485,8 +500,10 @@ const quoteSell = defineTool({
485
500
  title: "Quote a sell",
486
501
  access: "read",
487
502
  description: "What a given quantity of tokens would fetch right now, wherever the token trades: "
488
- + "gross proceeds, every fee and who receives it, net proceeds in the token's quote (native "
489
- + "USDC or an ERC-20 such as EURC), and the minimum-out floor at several tolerances.\n\n"
503
+ + "gross proceeds, every fee and who receives it on a curve the flat 1% split four ways "
504
+ + "between creator, platform, referrer and protocol net proceeds in the token's quote "
505
+ + "(native USDC or an ERC-20 such as EURC), and the minimum-out floor at several "
506
+ + "tolerances.\n\n"
490
507
  + "Costs nothing and signs nothing.\n\n"
491
508
  + "ON A BONDING CURVE: selling needs NO approval, ever. The curve pulls the tokens "
492
509
  + "through a privileged path that reads no allowance at all, so a holder who has "
@@ -495,11 +512,12 @@ const quoteSell = defineTool({
495
512
  + "IN A UNISWAP V4 POOL (the token graduated and migrated) it is the other way round, and "
496
513
  + "worth telling a user before they sell: the router pulls the tokens with transferFrom, "
497
514
  + "so a pool sell needs an ERC-20 APPROVAL to the router first — a separate transaction. "
498
- + "This quote says it is a pool quote, shows arcnow.io's 1% and the pool's own LP fee "
499
- + "separately, the average fill against the pool's spot price, the price impact, and "
500
- + "how much `holder` has approved the router for already. A pool sell is priced as a "
501
- + "real holder, so `holder` must actually hold the tokens; on a writing server it "
502
- + "defaults to the signing address. referrer and developer are refused on a pool.\n\n"
515
+ + "This quote says it is a pool quote, shows arcnow.io's fee hook's 0.80% and the pool's "
516
+ + "own 0.20% LP fee apart (1.00% in all, read off the pool), the average fill against the "
517
+ + "pool's spot price, the price impact, and how much `holder` has approved the router for "
518
+ + "already. A pool sell is priced as a real holder, so `holder` must actually hold the "
519
+ + "tokens; on a writing server it defaults to the signing address. referrer is refused on "
520
+ + "a pool.\n\n"
503
521
  + "A token that graduated but never migrated trades nowhere; this says so and names "
504
522
  + "arcnow_migrate.",
505
523
  input: {
@@ -510,9 +528,7 @@ const quoteSell = defineTool({
510
528
  + "signing address on a writing server; required on a read-only one. Not needed on a "
511
529
  + "bonding curve, where a sell is priced for nobody in particular.").optional(),
512
530
  referrer: addressArg("CURVE ONLY: credited with the referral share of the fee. Refused for a token in its "
513
- + "pool.").optional(),
514
- developer: addressArg("CURVE ONLY: credited with the developer share of the fee. Refused for a token in its "
515
- + "pool.").optional(),
531
+ + "pool. There is no developer argument: the fee has four parties.").optional(),
516
532
  },
517
533
  async run(args, ctx) {
518
534
  const market = await resolveMarket(ctx, args.address);
@@ -525,14 +541,15 @@ const quoteSell = defineTool({
525
541
  return quotePoolSell(args, ctx, market, symbol);
526
542
  const tokensIn = Tokens.parse(args.tokensIn);
527
543
  const quote = await curve.quoteSell(tokensIn);
528
- const split = await curve.previewFeeSplit(quote.fee, args.referrer, args.developer);
544
+ const split = await curve.previewFeeSplit(quote.fee, args.referrer);
529
545
  const avg = effectivePrice(quote.gross, tokensIn);
530
546
  return {
531
547
  text: report(`Sell quote — ${qty(tokensIn, symbol)} into curve ${addr(curve.address)}`, section("what you give and get", [
532
548
  ["venue", "its bonding curve"],
533
549
  ["you send", `${qty(tokensIn, symbol)} — no approval needed, and none will be asked for`],
534
550
  ["gross", money(quote.gross)],
535
- ["trade fee", `${money(quote.fee)} — the same flat 1%, taken from the proceeds`],
551
+ ["trade fee", `${money(quote.fee)} — the same flat ${bpsPercent(state.tradeFeeBps)}, taken `
552
+ + "from the proceeds and split four ways (below)"],
536
553
  ["you receive", money(quote.quoteOut.floorToRepresentable())],
537
554
  ["average fill price", avg === undefined ? "n/a" : price(avg)],
538
555
  ]), section("the curve after this sell", [
@@ -540,7 +557,7 @@ const quoteSell = defineTool({
540
557
  + `(${priceMove(state.spotPrice, quote.newPrice)})`],
541
558
  ["real reserve", `${money(state.realReserve)} → ${money(quote.newReserve)}`],
542
559
  ["tokens sold", `${qty(state.tokensSold)} → ${qty(quote.newTokensSold)}`],
543
- ]), feeSplitSection(quote.fee, split, args.referrer, args.developer), section(`minimum ${state.quoteToken.symbol} out, by tolerance`, TOLERANCES.map((bps) => {
560
+ ]), curveFeeSplitSection(quote.fee, split, args.referrer), section(`minimum ${state.quoteToken.symbol} out, by tolerance`, TOLERANCES.map((bps) => {
544
561
  const floor = Bps.of(10000n - bps).applyToQuote(quote.quoteOut);
545
562
  return [`${bps} bps (${Number(bps) / 100}%)`, money(floor)];
546
563
  })), args.holder === undefined
@@ -556,17 +573,18 @@ const quoteLaunch = defineTool({
556
573
  name: "arcnow_quote_launch",
557
574
  title: "Quote a launch",
558
575
  access: "read",
559
- description: "What launching a token would cost, before anything is spent: the flat launch fee, the "
560
- + "initial buy, the 1% trade fee the initial buy itself pays, the tokens the creator "
561
- + "would receive, and given a creator address the exact addresses the token and its "
562
- + "curve would land at.\n\n"
576
+ description: "What launching a token would cost, before anything is spent: the launch fee the quote "
577
+ + "registry sets for the quote (zero on arcnow.io's networks launching is free but read "
578
+ + "live, never assumed), the initial buy, the 1% trade fee the initial buy itself pays, the "
579
+ + "tokens the creator would receive, and — given a creator address — the exact addresses "
580
+ + "the token and its curve would land at.\n\n"
563
581
  + "Costs nothing and signs nothing. Always call this before arcnow_launch and show the "
564
582
  + "user the total, because a launch is irreversible: the name, symbol, supply, curve "
565
583
  + "shape and graduation venue are fixed at that transaction and can never be changed "
566
584
  + "afterwards, by anyone, including whoever launched it.\n\n"
567
585
  + "THE QUOTE. A token is priced for life in the quote it launches in: `quote` names it by "
568
586
  + "symbol or address — native USDC by default, or an ERC-20 such as EURC that the quote "
569
- + "registry accepts (arcnow_quote_tokens). The launch fee and the initial buy are both in that "
587
+ + "registry accepts (arcnow_quote_tokens). The initial buy and any launch fee is in that "
570
588
  + "quote, and so is every trade in the token afterwards. Native USDC is paid as msg.value; an "
571
589
  + "ERC-20 is pulled by the launchpad after an exact approval.\n\n"
572
590
  + "The total is EXACTLY what a launch requires, not a minimum — for native USDC the launchpad "
@@ -578,8 +596,9 @@ const quoteLaunch = defineTool({
578
596
  metadataUri: metadataUriArg(),
579
597
  initialBuy: decimalArg("How much of the launch's quote the creator spends buying their own token in the launch "
580
598
  + "transaction, in whole units, with no more decimals than the quote has. It is an "
581
- + "ORDINARY buy: it runs through the curve and pays the 1% trade fee on top of the flat "
582
- + "launch fee. There is no fee-free entry into a curve. \"0\" is allowed."),
599
+ + "ORDINARY buy: it runs through the curve and pays the 1% trade fee. There is no fee-free "
600
+ + "entry into a curve, and no launch fee on top of it on arcnow.io's networks. \"0\" is "
601
+ + "allowed."),
583
602
  quote: quoteArg(),
584
603
  creator: addressArg("Optional: the address that would launch. Given one, this also predicts the token and "
585
604
  + "curve addresses — valid only for that address's CURRENT launch nonce and exactly "
@@ -614,10 +633,11 @@ const quoteLaunch = defineTool({
614
633
  ? `${money(quote.totalCost)} — this is the exact value the launch transaction must `
615
634
  + "carry. Overpaying reverts; there is no refund path."
616
635
  : `${money(quote.totalCost)} — exactly what the launchpad pulls, no more`],
617
- [" launch fee", `${money(quote.launchFee)} — flat, to the launchpad, as the quote registry `
618
- + `sets it for ${quoteToken.symbol} now`],
636
+ [" launch fee", `${launchFeeLine(quote.launchFee)} — as the quote registry sets it for `
637
+ + `${quoteToken.symbol} now`],
619
638
  [" initial buy", money(quote.initialBuy)],
620
- [" of which fee", `${money(quote.tradeFee)} — the initial buy's own 1%`],
639
+ [" of which fee", `${money(quote.tradeFee)} — the initial buy's own 1%, split four ways `
640
+ + "like every curve trade's"],
621
641
  ["paid as", quoteToken.isNative
622
642
  ? `msg.value: exactly ${money(quote.nativeValue)}`
623
643
  : `an ERC-20 pull by the launchpad — the transaction carries no value. An exact approve `
@@ -668,8 +688,8 @@ const quoteLaunch = defineTool({
668
688
  /** The launch tools' `quote` argument. */
669
689
  export function quoteArg() {
670
690
  return z.string().min(1).max(64).optional().describe("Optional: the quote token to launch in, by symbol (\"USDC\", \"EURC\") or by address. "
671
- + "Defaults to native USDC. The token is priced in it FOR LIFE: its launch fee, its initial "
672
- + "buy, and every trade in it afterwards. A symbol is looked up in this network's own quote "
691
+ + "Defaults to native USDC. The token is priced in it FOR LIFE: its initial buy and every "
692
+ + "trade in it afterwards. A symbol is looked up in this network's own quote "
673
693
  + "tokens only. arcnow_quote_tokens lists them with whether the quote registry accepts each "
674
694
  + "and this server's spend cap for it; a quote with no cap cannot be launched in here.");
675
695
  }
@@ -678,15 +698,16 @@ const platform = defineTool({
678
698
  title: "Inspect a platform's fees and defaults",
679
699
  access: "read",
680
700
  description: "One platform's configuration: who administers it, who receives its cut, how the 1% "
681
- + "trade fee is divided between creator, referrer, developer, the platform and the "
682
- + "protocol, which migrator it launches tokens into by default, and the curve template "
683
- + "it stamps onto every token it launches.\n\n"
701
+ + "trade fee is divided FOUR ways — creator, referrer, the platform and the protocol; "
702
+ + "there is no developer share — which migrator it launches tokens into by default, and "
703
+ + "the curve template it stamps onto every token it launches.\n\n"
684
704
  + "With no address, reports arcnow.io's own platform — the default a launch uses.\n\n"
685
705
  + "The single easiest thing to misread here is what a share means. Every share is basis "
686
706
  + "points OF THE FEE, never of the trade. A creator share of 3000 bps is 30% of the fee "
687
707
  + "and 0.30% of the trade. This tool prints both, every time. The platform's own share "
688
708
  + "is never configured: it is the residual, whatever is left after the protocol, "
689
- + "creator, referrer and developer shares.",
709
+ + "creator and referrer shares arcnow.io's own is creator 3000 / referrer 1000 / "
710
+ + "platform 3500 / protocol 2500.",
690
711
  input: {
691
712
  address: addressArg("Optional: the PlatformConfig address. Defaults to arcnow.io's own platform.").optional(),
692
713
  },
@@ -715,16 +736,13 @@ const platform = defineTool({
715
736
  ["admin", addr(settings.admin)],
716
737
  ["fee recipient", addr(settings.feeRecipient)],
717
738
  ["certified", "yes — this registry deployed this PlatformConfig itself"],
718
- ]), section("how the 1% trade fee is divided", [
719
- ["creator", share(settings.creatorShareBps, TRADE_FEE)],
720
- ["referrer", share(settings.refShareBps, TRADE_FEE)],
721
- ["developer", share(settings.devShareBps, TRADE_FEE)],
722
- ["protocol", share(fees.protocolShareBps, TRADE_FEE)],
723
- ["platform", `${share(settings.platformShareBps, TRADE_FEE)} the RESIDUAL. Not an `
724
- + "input anywhere in the contracts; it is 10000 minus the four above, computed on "
725
- + "demand."],
726
- ["", "A share whose address is zero at swap time — no referrer, no developer — is "
727
- + "paid to the platform instead, as is the rounding dust."],
739
+ ]), section("how the 1% trade fee is divided — four ways, on a curve", [
740
+ ...curveSplitRows(fees, TRADE_FEE),
741
+ ["developer", "none — there is no developer share; the fee has these four parties"],
742
+ ["", "A share whose address is zero at swap time — no referrer named — is paid to "
743
+ + "the platform instead, as is the rounding dust. In a graduated token's pool the "
744
+ + `hook applies its own split of its ${bpsPercent(POOL_HOOK_FEE)}, not this one: `
745
+ + "arcnow_token shows it."],
728
746
  ]), section("defaults it stamps on a launch", [
729
747
  ["migrator", `${addr(settings.defaultMigrator)} — `
730
748
  + `${venueOf(settings.defaultMigrator, ctx.port.config)}. Snapshotted into each `
@@ -741,8 +759,8 @@ const platform = defineTool({
741
759
  ]), section("the protocol, above every platform", [
742
760
  ["share", share(protocol.protocolShareBps, TRADE_FEE)],
743
761
  ["recipient", addr(protocol.protocolRecipient)],
744
- ["launch fee", `${money(protocol.launchFee)} — flat, per launch in native USDC, on top of `
745
- + "any initial buy. Each quote token has its own: arcnow_quote_tokens"],
762
+ ["launch fee", `${launchFeeLine(protocol.launchFee)} — per launch in native USDC, as the `
763
+ + "quote registry sets it. Each quote token has its own: arcnow_quote_tokens"],
746
764
  ])),
747
765
  };
748
766
  },
@@ -780,9 +798,9 @@ const listPlatforms = defineTool({
780
798
  const settings = await ctx.port.platforms.settings(address);
781
799
  blocks.push(section(`#${i} ${addr(address)}${isDefault ? " (arcnow.io's own)" : ""}`, [
782
800
  ["fee recipient", addr(settings.feeRecipient)],
783
- ["creator / ref / dev / platform", `${settings.creatorShareBps.bps} / `
784
- + `${settings.refShareBps.bps} / ${settings.devShareBps.bps} / `
785
- + `${settings.platformShareBps.bps} bps of the fee`],
801
+ ["creator / ref / platform", `${settings.creatorShareBps.bps} / `
802
+ + `${settings.refShareBps.bps} / ${settings.platformShareBps.bps} bps of the fee, `
803
+ + "the protocol's share above all of them; no developer share"],
786
804
  ["default venue", `${addr(settings.defaultMigrator)} — `
787
805
  + venueOf(settings.defaultMigrator, ctx.port.config)],
788
806
  ["graduation target", "per quote token — arcnow_platform shows it"],
@@ -808,8 +826,9 @@ const quoteTokens = defineTool({
808
826
  + "is priced in for life: native USDC (the gas currency, 18 decimals, paid as msg.value) and "
809
827
  + "the ERC-20s arcnow.io's quote registry allowlists, such as EURC (6 decimals, pulled with "
810
828
  + "an exact ERC-20 approval). For each: symbol, name, decimals, address, whether it is "
811
- + "native, its flat launch fee in its own units, whether the registry accepts it for new "
812
- + "launches now, and this server's spend cap for it.\n\n"
829
+ + "native, its launch fee in its own units (zero on arcnow.io's networks launching is "
830
+ + "free — but read from the registry, never assumed), whether the registry accepts it for "
831
+ + "new launches now, and this server's spend cap for it.\n\n"
813
832
  + "SPEND CAPS ARE PER QUOTE, AND FAIL CLOSED. The operator caps native USDC with "
814
833
  + "ARCNOW_MCP_MAX_SPEND_USDC (default 100) and every other quote with "
815
834
  + "ARCNOW_MCP_MAX_SPEND_<SYMBOL>, in that quote's own units (ARCNOW_MCP_MAX_SPEND_EURC=50). "
@@ -817,8 +836,7 @@ const quoteTokens = defineTool({
817
836
  + "list can have no cap at all. Tell the user which quotes they can actually spend here "
818
837
  + "before offering a launch or a buy in one.\n\n"
819
838
  + "Reads the registry in at most three eth_calls. If the chain has no quote registry to ask "
820
- + "— the 2.x contracts Arc testnet ran until the multi-quote reset have none — this says so, "
821
- + "shows the error, "
839
+ + "— a deployment older than quote registries has none — this says so, shows the error, "
822
840
  + "and lists this network's own quote-token metadata instead, with NOTHING known to be "
823
841
  + "accepted. Takes no arguments and spends nothing.",
824
842
  input: {},
@@ -832,9 +850,8 @@ const quoteTokens = defineTool({
832
850
  text: report("The quote registry could not be read, so which quote tokens a launch accepts is NOT "
833
851
  + "known. Below is this network's own quote-token metadata: what a token could be "
834
852
  + "priced in, not what the launchpad takes.", renderError("arcnow_quote_tokens", error), note("Do not tell a user a quote is accepted on the strength of this list. A launch in "
835
- + "a quote the registry does not accept reverts QuoteTokenNotSupported; the 2.x "
836
- + "contracts Arc testnet ran until the multi-quote reset have no registry at all and "
837
- + "accept only native USDC."), ...ctx.port.config.quoteTokens.map((token) => section(`${token.symbol} — ${token.name}`, [
853
+ + "a quote the registry does not accept reverts QuoteTokenNotSupported; a deployment "
854
+ + "with no quote registry at all accepts only native USDC."), ...ctx.port.config.quoteTokens.map((token) => section(`${token.symbol} ${token.name}`, [
838
855
  ...quoteTokenRows(token),
839
856
  ["launch fee", "unknown — the registry could not be read"],
840
857
  ["accepted", "unknown — the registry could not be read"],
@@ -845,7 +862,8 @@ const quoteTokens = defineTool({
845
862
  return {
846
863
  text: report(`${entries.length} quote token(s) registered with this launchpad's quote registry.`, ...entries.map(({ token, launchFee, active }) => section(`${token.symbol} — ${token.name}`, [
847
864
  ...quoteTokenRows(token),
848
- ["launch fee", `${money(launchFee)} — flat, per launch in ${token.symbol}`],
865
+ ["launch fee", `${launchFeeLine(launchFee)} — per launch, in ${token.symbol}, as the `
866
+ + "registry sets it"],
849
867
  ["active", active
850
868
  ? "yes — a launch may use it"
851
869
  : "NO — deregistered: no new launch may use it. Curves already priced in it keep "
@@ -889,19 +907,15 @@ export function statusLine(graduated, migrated) {
889
907
  return "GRADUATED but NOT MIGRATED — trading is over and the pool was never created, so it "
890
908
  + "trades nowhere. migrate() is permissionless; anyone can finish it.";
891
909
  }
892
- export function feeSplitSection(fee, split, referrer, developer) {
893
- const rows = [
894
- ["creator", `${money(split.creatorAmount)} → ${addr(split.creator)}`],
895
- ["platform", `${money(split.platformAmount)} → ${addr(split.platform)}`],
896
- ["referrer", referrer === undefined
897
- ? `${money(split.refAmount)} → no referrer given, so this goes to the platform`
898
- : `${money(split.refAmount)} → ${addr(split.ref)}`],
899
- ["developer", developer === undefined
900
- ? `${money(split.devAmount)} no developer given, so this goes to the platform`
901
- : `${money(split.devAmount)} → ${addr(split.dev)}`],
902
- ["protocol", `${money(split.protocolAmount)} → ${addr(split.protocol)}`],
903
- ];
904
- return section(`fee split — where the ${money(fee)} goes`, rows);
910
+ /**
911
+ * A launch fee, as the registry reports it. Zero on every quote arcnow.io
912
+ * registers — launching is free — and said so; anything else is printed as the
913
+ * amount it is, because the figure is read, not assumed.
914
+ */
915
+ export function launchFeeLine(fee) {
916
+ return fee.isZero()
917
+ ? `${money(fee)} launch fee — launching is free`
918
+ : `${money(fee)} launch fee`;
905
919
  }
906
920
  function describe(error) {
907
921
  return error instanceof Error ? error.message : String(error);