@candledottv/mcp 0.10.20 → 0.10.22

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 (3) hide show
  1. package/README.md +5 -3
  2. package/dist/index.js +212 -24
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -57,8 +57,10 @@ Both take decimal amounts, never raw base units, and resolve the scale themselve
57
57
 
58
58
  Both launch tools also take `mode: "exp-custom"` with `curve: { virtualQuote, threshold }`: a
59
59
  Solana curve with your own V and G, in whole SOL or CNDL as decimal strings, inside limits the
60
- API enforces and each tool's description states. Dry-run first: the dry run returns the curve's
61
- terms. It works only where the deployment has switched it on. See
60
+ API enforces and each tool's description states. Any `exp-` mode takes `creatorFeeBps` (0, 100 or
61
+ 200). Dry-run first: the dry run returns the launch preview (`curveTerms`: the curve's terms, the
62
+ share your own `buyAmount` takes, the fees and the migration terms) and `devBuyMax`, the dev-buy
63
+ ceiling that applies. `exp-custom` works only where the deployment has switched it on. See
62
64
  [Custom curves](https://docs.candle.tv/developers/headless-launch#custom-curves-choose-v-and-g-switched-off).
63
65
 
64
66
  For a full walkthrough of `candle_launch_and_seed` and `candle_trade`, including the keyless read
@@ -114,7 +116,7 @@ tools, getting a key, funding the embedded wallet, and idempotent retries, see
114
116
  | `candle_list_agents` | The agent directory: every published agent with its strategy, declared models, all-time P&L and trade and launch counts, sorted `newest`, `active` or `pnl`, filtered by `strategy`, searched by `q` | `GET /api/v1/agent/directory` | none |
115
117
  | `candle_get_plans` | Plans: prices, fees, limits and what each can do, as JSON and a Markdown table | `GET /api/v1/agent/plans` | none |
116
118
  | `candle_trade` | Buy or sell a token | Reads the market for its decimals (or wallet balance, for a percent sell) then `POST /api/v1/trade/agent/build` | `CANDLE_AGENT_API_KEY` (`swap:write`) |
117
- | `candle_launch_and_seed` | Launch a token and seed it | `POST /api/v1/launch/headless` (or `/dry-run`), then a follow-up `GET /api/v1/markets/:chain/:mint`; with `imagePath`, `POST /api/v1/uploads/agent-image` first | `CANDLE_AGENT_API_KEY` |
119
+ | `candle_launch_and_seed` | Launch a token and seed it | `POST /api/v1/launch/headless` (or `/dry-run`); a Solana seed is an atomic launch instead, `POST /api/v1/launch/atomic/build` then `/submit` (or `/atomic/dry-run`), one Jito bundle from the embedded wallet (Pro or Max, `swap:write`); then a follow-up `GET /api/v1/markets/:chain/:mint`; with `imagePath`, `POST /api/v1/uploads/agent-image` first | `CANDLE_AGENT_API_KEY` |
118
120
  | `candle_swap` | Swap between base assets | `POST /api/v1/agent/swap` | `CANDLE_AGENT_API_KEY` (`swap:write`) |
119
121
  | `candle_transfer` | Transfer an asset | `POST /api/v1/agent/transfer` | `CANDLE_AGENT_API_KEY`, Withdraw (`transfer:write`) |
120
122
  | `candle_sweep` | Sweep a wallet to one destination | One `POST /api/v1/agent/transfer` per asset, `amountRaw: "max"` | `CANDLE_AGENT_API_KEY`, Withdraw (`transfer:write`) |
package/dist/index.js CHANGED
@@ -151,7 +151,7 @@ import { lstat, open, realpath } from "node:fs/promises";
151
151
  import { dirname, isAbsolute, relative, sep } from "node:path";
152
152
 
153
153
  // src/version.ts
154
- var SERVER_VERSION = "0.10.20";
154
+ var SERVER_VERSION = "0.10.22";
155
155
 
156
156
  // src/update-notice.ts
157
157
  var PLAIN_VERSION = /^\d+\.\d+\.\d+$/;
@@ -691,7 +691,7 @@ async function executeTrade(args, cfg, doFetch) {
691
691
  const wrapped = JSON.stringify({ clientTradeId, resolved, api: posted.body });
692
692
  return posted.ok ? { text: wrapped } : { text: wrapped, isError: true };
693
693
  }
694
- async function executeLaunchAndSeed(args, cfg, doFetch, imageDir, imageFs) {
694
+ async function executeLaunchAndSeed(args, cfg, doFetch, imageDir, imageFs, opts = {}) {
695
695
  const apiKey = requireApiKey(cfg);
696
696
  const clientLaunchId = args.clientLaunchId ?? randomUUID();
697
697
  const { devBuy, dryRun, buyAmount: _rawBuyAmount, ...imageFields } = args;
@@ -705,6 +705,12 @@ async function executeLaunchAndSeed(args, cfg, doFetch, imageDir, imageFs) {
705
705
  return converted.err;
706
706
  buyAmount = converted.raw;
707
707
  }
708
+ const atomic = buyAmount !== undefined && (args.chain ?? "solana") === "solana" && !opts.headlessSeed;
709
+ if (atomic) {
710
+ const refused = seedCeilingRefusal(args.quoteAsset ?? "sol", args.mode, buyAmount, clientLaunchId);
711
+ if (refused)
712
+ return refused;
713
+ }
708
714
  const image = await resolveLaunchImage(imageFields, imageDir, cfg, doFetch, imageFs);
709
715
  if ("refused" in image)
710
716
  return { text: imageRefusalBody(image.refused, { clientLaunchId }), isError: true };
@@ -718,16 +724,20 @@ async function executeLaunchAndSeed(args, cfg, doFetch, imageDir, imageFs) {
718
724
  return { text: JSON.stringify({ clientLaunchId, api }), isError: true };
719
725
  }
720
726
  const uploaded = uploadedFields(image);
727
+ const transportFailure = (thrown) => {
728
+ const failed = transportError("clientLaunchId", clientLaunchId, thrown);
729
+ return Object.keys(uploaded).length === 0 ? failed : { ...failed, text: JSON.stringify({ ...JSON.parse(failed.text), ...uploaded }) };
730
+ };
731
+ if (atomic)
732
+ return launchAndSeedAtomic({ ...image.args, clientLaunchId }, buyAmount, { clientLaunchId, uploaded, dryRun: dryRun === true, transportFailure }, cfg, doFetch);
721
733
  const path = dryRun ? "/api/v1/launch/headless/dry-run" : "/api/v1/launch/headless";
722
734
  const posted = await postJson(`${base(cfg)}${path}`, apiKey, {
723
735
  ...image.args,
724
736
  clientLaunchId,
725
737
  ...buyAmount !== undefined ? { buyAmount } : {}
726
738
  }, doFetch);
727
- if ("thrown" in posted) {
728
- const failed = transportError("clientLaunchId", clientLaunchId, posted.thrown);
729
- return Object.keys(uploaded).length === 0 ? failed : { ...failed, text: JSON.stringify({ ...JSON.parse(failed.text), ...uploaded }) };
730
- }
739
+ if ("thrown" in posted)
740
+ return transportFailure(posted.thrown);
731
741
  if (!posted.ok) {
732
742
  return { text: JSON.stringify({ clientLaunchId, ...uploaded, api: posted.body }), isError: true };
733
743
  }
@@ -735,25 +745,115 @@ async function executeLaunchAndSeed(args, cfg, doFetch, imageDir, imageFs) {
735
745
  return { text: JSON.stringify({ clientLaunchId, dryRun: true, ...uploaded, api: posted.body }) };
736
746
  }
737
747
  const launch = posted.body;
738
- let market = null;
739
- let note;
748
+ const { market, note } = await readLaunchedMarket(launch.chain ?? "solana", launch.mint, cfg, doFetch);
749
+ return {
750
+ text: JSON.stringify({ clientLaunchId, ...uploaded, launch, market, ...note ? { note } : {} })
751
+ };
752
+ }
753
+ async function readLaunchedMarket(chain, mint, cfg, doFetch) {
754
+ const note = "launch confirmed; the follow-up market read failed, read it via candle_get_market";
740
755
  try {
741
- if (launch.mint === undefined)
756
+ if (mint === undefined)
742
757
  throw new Error("the launch response carried no mint");
743
- const marketRes = await doFetch(`${base(cfg)}/api/v1/markets/${encodeURIComponent(launch.chain ?? "solana")}/${encodeURIComponent(launch.mint)}`, {
758
+ const marketRes = await doFetch(`${base(cfg)}/api/v1/markets/${encodeURIComponent(chain)}/${encodeURIComponent(mint)}`, {
744
759
  method: "GET",
745
760
  headers: headers()
746
761
  });
747
- if (marketRes.ok) {
748
- market = JSON.parse(await marketRes.text()).market ?? null;
749
- } else {
750
- note = "launch confirmed; the follow-up market read failed, read it via candle_get_market";
751
- }
762
+ if (!marketRes.ok)
763
+ return { market: null, note };
764
+ return { market: JSON.parse(await marketRes.text()).market ?? null };
752
765
  } catch {
753
- note = "launch confirmed; the follow-up market read failed, read it via candle_get_market";
766
+ return { market: null, note };
767
+ }
768
+ }
769
+ var SOLANA_SEED_CEILINGS = {
770
+ sol: { raw: 500000000n, unit: "lamports", expRaw: 500000000n },
771
+ usdc: { raw: 500000000n, unit: "USDC raw units" },
772
+ cndl: { raw: 2000000000000n, unit: "CNDL raw units", expRaw: 33000000000n }
773
+ };
774
+ function seedCeilingRefusal(quoteAsset, mode, buyAmount, clientLaunchId) {
775
+ const quote = quoteAsset.toLowerCase();
776
+ const ceiling = SOLANA_SEED_CEILINGS[quote];
777
+ if (!ceiling)
778
+ return;
779
+ const expCap = ceiling.expRaw;
780
+ const preset = mode === "exp-a" || mode === "exp-b" || mode === "exp-c";
781
+ const effective = preset && expCap !== undefined && expCap < ceiling.raw ? expCap : ceiling.raw;
782
+ if (BigInt(buyAmount) <= effective)
783
+ return;
784
+ if (preset && expCap !== undefined) {
785
+ return {
786
+ text: JSON.stringify({
787
+ clientLaunchId,
788
+ success: false,
789
+ error: {
790
+ code: "DEV_BUY_TOO_HIGH",
791
+ message: `buyAmount exceeds this launch's dev-buy ceiling of ${effective} ${ceiling.unit}: at most ${ceiling.raw} ${ceiling.unit}, or 10% of the threshold when that is lower`,
792
+ field: "devBuy",
793
+ retryable: false
794
+ }
795
+ }),
796
+ isError: true
797
+ };
754
798
  }
755
799
  return {
756
- text: JSON.stringify({ clientLaunchId, ...uploaded, launch, market, ...note ? { note } : {} })
800
+ text: JSON.stringify({
801
+ clientLaunchId,
802
+ success: false,
803
+ error: {
804
+ code: "DEV_BUY_TOO_HIGH",
805
+ message: `buyAmount exceeds the per-launch dev-buy ceiling of ${effective} ${ceiling.unit} for ${quote} on solana`,
806
+ field: "devBuy",
807
+ retryable: false
808
+ }
809
+ }),
810
+ isError: true
811
+ };
812
+ }
813
+ var MAIN_PAYER = { type: "main" };
814
+ async function launchAndSeedAtomic(launchBody, buyAmount, ctx, cfg, doFetch) {
815
+ const apiKey = requireApiKey(cfg);
816
+ const { clientLaunchId, uploaded } = ctx;
817
+ const failed = (api, extra = {}) => ({
818
+ text: JSON.stringify({ clientLaunchId, ...uploaded, ...extra, api }),
819
+ isError: true
820
+ });
821
+ const body = { ...launchBody, payer: MAIN_PAYER, firstBuys: [{ payer: MAIN_PAYER, amountRaw: buyAmount }] };
822
+ const built = await postJson(`${base(cfg)}/api/v1/launch/atomic/${ctx.dryRun ? "dry-run" : "build"}`, apiKey, body, doFetch);
823
+ if ("thrown" in built)
824
+ return ctx.transportFailure(built.thrown);
825
+ if (!built.ok)
826
+ return failed(built.body);
827
+ if (ctx.dryRun)
828
+ return { text: JSON.stringify({ clientLaunchId, dryRun: true, ...uploaded, api: built.body }) };
829
+ const bundle = built.body;
830
+ if (typeof bundle.bundleId !== "string" || !Array.isArray(bundle.legs) || bundle.legs.length !== 2 || bundle.legs.some((leg) => leg?.signer !== "server"))
831
+ return failed(built.body, {
832
+ success: false,
833
+ error: {
834
+ code: "MCP_INVALID_RESPONSE",
835
+ message: "Candle did not build this launch and its seed as two legs it signs from the embedded wallet; nothing was sent. Retry with the same clientLaunchId to build it again."
836
+ }
837
+ });
838
+ const bundleId = bundle.bundleId;
839
+ const submitted = await postJson(`${base(cfg)}/api/v1/launch/atomic/submit`, apiKey, { bundleId, signedTxsBase64: [] }, doFetch);
840
+ if ("thrown" in submitted)
841
+ return ctx.transportFailure(submitted.thrown);
842
+ const outcome = submitted.body;
843
+ if (!submitted.ok || outcome.status !== "landed") {
844
+ const next = outcome.status === "failed" ? "Neither the launch nor the seed is on chain. Retry with the same clientLaunchId: Candle starts a clean attempt under it." : outcome.status === "timeout" ? "The bundle is unresolved and may still land. Call candle_get_operation with this clientLaunchId and kind launch; never relaunch under a new id, which would be a second token." : undefined;
845
+ return failed(submitted.body, { bundleId, ...next ? { next } : {} });
846
+ }
847
+ const { market, note } = await readLaunchedMarket("solana", outcome.mint, cfg, doFetch);
848
+ return {
849
+ text: JSON.stringify({
850
+ clientLaunchId,
851
+ bundleId,
852
+ ...uploaded,
853
+ launch: submitted.body,
854
+ market,
855
+ ...note ? { note } : {}
856
+ })
757
857
  };
758
858
  }
759
859
  var SWEEP_BASE_ASSETS = {
@@ -2474,18 +2574,55 @@ function perpsCandleWindow(opts, now) {
2474
2574
  return usage(`interval must be one of ${PERPS_CANDLE_INTERVALS.join(" ")}.`);
2475
2575
  const count = bounded(opts.count, 100, 1, 5000, "count");
2476
2576
  const raw = opts.end;
2477
- const endTime = raw === undefined ? now : typeof raw === "number" || /^\d+$/.test(raw) ? Number(raw) : /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})$/.test(raw) ? Date.parse(raw) : NaN;
2577
+ const endTime = raw === undefined ? now : epoch(raw);
2478
2578
  const startTime = endTime - count * at(INTERVAL_MS, index);
2479
2579
  if (!Number.isSafeInteger(endTime) || endTime < 0 || endTime > now || startTime < 0)
2480
2580
  return usage("end must be an ISO 8601 time or epoch milliseconds, no later than now.");
2481
2581
  return { interval, count, startTime, endTime };
2482
2582
  }
2583
+ function epoch(raw) {
2584
+ return typeof raw === "number" || /^\d+$/.test(raw) ? Number(raw) : /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})$/.test(raw) ? Date.parse(raw) : NaN;
2585
+ }
2586
+ var PERPS_FUNDING_HISTORY_MAX_MS = 30 * 86400000;
2587
+ var PERPS_FUNDING_HISTORY_MAX_ROWS = 500;
2588
+ function perpsFundingWindow(opts, now) {
2589
+ const endTime = opts.end === undefined ? now : epoch(opts.end);
2590
+ if (!Number.isSafeInteger(endTime) || endTime < 0 || endTime > now)
2591
+ return usage("end must be an ISO 8601 time or epoch milliseconds, no later than now.");
2592
+ const startTime = opts.start === undefined ? endTime - 86400000 : epoch(opts.start);
2593
+ if (!Number.isSafeInteger(startTime) || startTime < 0 || startTime > endTime)
2594
+ return usage("start must be an ISO 8601 time or epoch milliseconds, no later than end.");
2595
+ if (endTime - startTime > PERPS_FUNDING_HISTORY_MAX_MS)
2596
+ return usage("start must be at most 30 days before end.");
2597
+ return { startTime, endTime };
2598
+ }
2483
2599
  function bounded(value, fallback, min, max, name) {
2484
2600
  const n = value ?? fallback;
2485
2601
  if (!Number.isInteger(n) || n < min || n > max)
2486
2602
  return usage(`${name} must be an integer from ${min} to ${max}.`);
2487
2603
  return n;
2488
2604
  }
2605
+ function parsePerpsFundingHistory(value, coin, window) {
2606
+ if (!Array.isArray(value))
2607
+ return unavailable();
2608
+ const funding = [];
2609
+ for (const row of value) {
2610
+ if (!object(row) || row.coin !== coin || !integer(row.time) || !decimal2(row.fundingRate) || !decimal2(row.premium))
2611
+ return unavailable();
2612
+ if (row.time >= window.startTime && row.time <= window.endTime)
2613
+ funding.push({
2614
+ time: row.time,
2615
+ fundingRate: row.fundingRate,
2616
+ fundingAprPct: fixed(scaled(row.fundingRate) * 876000n, 2),
2617
+ premium: row.premium
2618
+ });
2619
+ }
2620
+ funding.sort((a, b) => a.time - b.time);
2621
+ return {
2622
+ funding: funding.slice(0, PERPS_FUNDING_HISTORY_MAX_ROWS),
2623
+ truncated: funding.length > PERPS_FUNDING_HISTORY_MAX_ROWS
2624
+ };
2625
+ }
2489
2626
 
2490
2627
  class PerpsMarketReader {
2491
2628
  opts;
@@ -2763,6 +2900,44 @@ class PerpsMarketReader {
2763
2900
  candles: candles.slice(-window.count)
2764
2901
  };
2765
2902
  }
2903
+ async fundingHistory(coin, opts = {}) {
2904
+ this.key();
2905
+ const window = perpsFundingWindow(opts, this.now());
2906
+ const [config, core] = await Promise.all([this.config(), this.core()]);
2907
+ const canonical2 = at(core.universe, resolve(core, coin)).name;
2908
+ const value = await info(this.opts.fetcher, this.opts.network, {
2909
+ type: "fundingHistory",
2910
+ coin: canonical2,
2911
+ startTime: window.startTime,
2912
+ endTime: window.endTime
2913
+ });
2914
+ return {
2915
+ ...this.envelope(config),
2916
+ coin: canonical2,
2917
+ startTime: window.startTime,
2918
+ endTime: window.endTime,
2919
+ ...parsePerpsFundingHistory(value, canonical2, window)
2920
+ };
2921
+ }
2922
+ async predictedFunding() {
2923
+ this.key();
2924
+ const [config, core, extras] = await Promise.all([this.config(), this.core(), this.extras()]);
2925
+ if (extras.predicted === null)
2926
+ return unavailable();
2927
+ const predicted = [];
2928
+ for (const meta of core.universe) {
2929
+ const row = extras.predicted.get(meta.name);
2930
+ if (row)
2931
+ predicted.push({
2932
+ coin: meta.name,
2933
+ fundingRate: row.fundingRate,
2934
+ fundingAprPct: fixed(scaled(row.fundingRate) * 876000n, 2),
2935
+ nextFundingTime: row.nextFundingTime
2936
+ });
2937
+ }
2938
+ predicted.sort((a, b) => a.coin < b.coin ? -1 : a.coin > b.coin ? 1 : 0);
2939
+ return { ...this.envelope(config, extras.warnings, this.extrasCache?.at ?? this.now()), predicted };
2940
+ }
2766
2941
  }
2767
2942
 
2768
2943
  // src/perps-history.ts
@@ -4570,7 +4745,7 @@ function planTableMarkdown(table) {
4570
4745
 
4571
4746
  // src/tools.ts
4572
4747
  var VAMP_CANDIDATES_DESCRIPTION = "Lists Solana tokens that are close to graduating or graduated in the last 6 hours whose ticker other tokens " + "are copying, most-copied in the last 24 hours first. Each entry carries a launch draft (name, ticker, image URL) " + "for `candle_launch_token` or `candle_launch_and_seed`; draft notes identify fields you must supply or adjust. " + "Upload your own image through the agent image upload when the image is unavailable. Use `dryRun: true` first. " + "Counts exclude the candidate itself and cover both Solana and Hood launches at the venues returned in " + "`coverage`, not Candle's own. `coverage.since` is the returned Solana coverage date; Hood history may differ.";
4573
- var CUSTOM_CURVE_RULE = 'mode "exp-custom" with curve { virtualQuote, threshold } launches a Solana curve with your own V (the virtual ' + "quote it starts with) and G (the quote it raises before graduating), in whole SOL or CNDL as decimal strings, " + "accepted when V is 1 to 100 SOL and G 1 to 250 SOL (66,000 to 6,600,000 and 66,000 to 16,500,000 CNDL), when " + "every price up to 20 x G stays under the ceiling (V + 20G)^2 < V x X at this stakerAllocationBps, when neither " + "is finer than the quote's decimals, and when the dev buy is at most 10% of G; a curve past a limit is " + "CURVE_OUT_OF_RANGE, and at the ceiling it carries maxThresholdRaw, the largest G that fits. " + "Dry-run it first (dryRun: true): the dry run spends nothing and returns curveTerms, the market cap at start and " + "at graduation and the supply sold by graduation. Only where the API has EXP_CUSTOM_CURVES on; " + "elsewhere exp-custom is refused as an unknown mode.";
4748
+ var CUSTOM_CURVE_RULE = 'mode "exp-custom" with curve { virtualQuote, threshold } launches a Solana curve with your own V (the virtual ' + "quote it starts with) and G (the quote it raises before graduating), in whole SOL or CNDL as decimal strings, " + "accepted when V is 1 to 100 SOL and G 1 to 250 SOL (66,000 to 6,600,000 and 66,000 to 16,500,000 CNDL), when " + "every price up to 20 x G stays under the ceiling (V + 20G)^2 < V x X at this stakerAllocationBps, when neither " + "is finer than the quote's decimals, and when the dev buy is at most 0.5 SOL (2,000,000 CNDL), or 10% of G when " + "that is lower (every exp- mode; an operator may set the 0.5 SOL lower); a curve past a limit is " + "CURVE_OUT_OF_RANGE, and at the ceiling it carries maxThresholdRaw, the largest G that fits. " + "Dry-run it first (dryRun: true): the dry run spends nothing and returns curveTerms, the market cap at start and " + "at graduation, the supply sold by graduation, the share your own buyAmount takes, the fees and the migration " + "terms, and devBuyMax, the dev-buy ceiling that applies. Only where the API has EXP_CUSTOM_CURVES on; " + "elsewhere exp-custom is refused as an unknown mode.";
4574
4749
  var localProfileTools = new WeakMap;
4575
4750
  var STAKING_DISCLOSURE = "No instant unstake. Read pool.withdrawalTimelockSeconds for the wait. Solana rewards accrue to the holding wallet. sCNDL in any eligible account wallet counts toward Pro and earns the account's Hood staking reward, except a wallet another account also claims, wallets past the 200-wallet cap, and an account with no EVM wallet. CNDL in a pending withdrawal counts toward nothing for Pro.";
4576
4751
  var stakingWriteShape = {
@@ -5003,12 +5178,23 @@ var launchTokenShape = {
5003
5178
  bannerUrl: z4.string().optional().describe("Optional https URL to WIDE artwork for the token page's banner strip (wider than 1.5:1, " + "e.g. 1200x630). This is where a share card or OG image belongs. A square image here is " + "rejected with BANNER_WRONG_SHAPE: send the wide original, not a cropped copy. Omit it and the strip " + "falls back to imageUrl"),
5004
5179
  chain: z4.string().optional().describe('"solana" or "hood"; defaults to solana'),
5005
5180
  quoteAsset: z4.string().optional().describe("Quote asset symbol; defaults per chain"),
5006
- mode: z4.string().optional().describe('"open" or "exclusive"; defaults to open. "test-open" / "test-exclusive" launch the ' + "low-threshold test curves (~1/80 economics) where the API has ENABLE_TEST_CURVES on. " + '"exp-custom" launches a Solana curve with your own V and G, given in curve, where the API has it ' + "switched on"),
5181
+ mode: z4.string().optional().describe('"open" or "exclusive"; defaults to open. "test-open" / "test-exclusive" launch the ' + "low-threshold test curves (~1/80 economics) where the API has ENABLE_TEST_CURVES on. " + '"exp-a", "exp-b" and "exp-c" are the experimental fast Solana curves (sol or cndl). ' + '"exp-custom" launches a Solana curve with your own V and G, given in curve, where the API has it ' + "switched on"),
5007
5182
  curve: z4.object({
5008
5183
  virtualQuote: z4.string().describe('V: the virtual quote the curve starts with, in whole quote units, e.g. "12" SOL'),
5009
5184
  threshold: z4.string().describe('G: the quote the curve raises before it graduates, in whole quote units, e.g. "40" SOL')
5010
5185
  }).strict().optional().describe('mode "exp-custom" only, and required there. Decimal strings in whole units of the quote asset (sol or ' + "cndl), never raw units. Dry-run first: the API checks the limits and returns the curve's terms"),
5186
+ creatorFeeBps: z4.union([z4.literal(0), z4.literal(100), z4.literal(200)]).optional().describe("An exp- mode only: an extra creator fee of 100 or 200 basis points on top of the curve's 1% and the " + "pool's 0.5%, paid to the creator in full. 0 is the same as leaving it out"),
5011
5187
  stakerAllocationBps: z4.number().optional().describe("Staker allocation in basis points"),
5188
+ poolFee: z4.union([
5189
+ z4.object({ tier: z4.union([z4.literal(25), z4.literal(50), z4.literal(100)]) }).strict(),
5190
+ z4.object({
5191
+ startBps: z4.number().int().max(2500),
5192
+ endBps: z4.union([z4.literal(25), z4.literal(50), z4.literal(100)]),
5193
+ minutes: z4.number().positive().max(30)
5194
+ }).strict()
5195
+ ]).optional().describe("Solana only: the graduated pool's fee. { tier: 25 | 50 | 100 } is a flat fee in bps (default 50), or " + "{ startBps, endBps, minutes } is an anti-snipe fall from startBps (at most 2500, at least endBps) to " + "endBps (25, 50 or 100) over minutes (at most 30). Cannot be combined with creatorFeeBps unless the tier is 50. " + "Omit for the standard 50 bps pool"),
5196
+ poolOpen: z4.enum(["legacy", "curve_price"]).optional().describe(`Solana preset tiers only. "curve_price" opens the pool at the curve's last price (the whole reserve is ` + "seeded and the remaining base burned); stakerAllocationBps plus the team allocation must fit the tier's limit, " + "which the dry run returns as graduation.maxStakerCarveBps. Omitted defaults to curve_price when those " + "allocations fit, otherwise falls back to legacy without refusal. Set legacy explicitly to opt out. " + "Explicit curve_price is refused when the allocations exceed the limit"),
5197
+ expPoolShape: z4.enum(["floor", "full_range"]).optional().describe('Solana exp- curves only. "full_range" opens the pool across the whole price range. Omit for floor'),
5012
5198
  dexVersion: z4.string().nullable().optional().describe('"v3" or "v4"; required for hood launches'),
5013
5199
  buyAmount: z4.union([z4.string(), z4.number()]).optional().describe("Initial buy, in quote base units (string or number)"),
5014
5200
  description: z4.string().optional(),
@@ -5125,7 +5311,7 @@ var tradeShape = {
5125
5311
  var { buyAmount: _rawBuyAmount, ...seedableLaunchShape } = launchTokenShape;
5126
5312
  var seedShape = {
5127
5313
  clientLaunchId: z4.string().optional().describe("Idempotency key. Auto-generated when omitted and echoed in the result."),
5128
- devBuy: z4.string().optional().describe('Seed buy in DECIMAL units of the quote asset this launch selects (e.g. "0.25" SOL, or ' + "ETH on hood), bundled into the launch transaction itself on solana and sent as a follow-up " + "transaction on hood. Paid from the account's embedded wallet. Follows quoteAsset, which " + "defaults to sol on solana and eth on hood. Capped by the platform dev-buy ceiling; for " + "a larger seed, launch then follow with candle_trade.")
5314
+ devBuy: z4.string().optional().describe('Seed buy in DECIMAL units of the quote asset this launch selects (e.g. "0.25" SOL, or ' + "ETH on hood), paid from the account's embedded wallet; the tool's description says how " + "each chain sends it. Follows quoteAsset, which defaults to sol on solana and eth on hood. " + "Capped by the platform dev-buy ceiling; for a larger seed, launch then follow with candle_trade.")
5129
5315
  };
5130
5316
  var launchAndSeedShape = { ...seedableLaunchShape, ...seedShape };
5131
5317
  var localImageShape = {
@@ -5441,17 +5627,19 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
5441
5627
  });
5442
5628
  register("candle_launch_and_seed", {
5443
5629
  title: "Launch a token and seed it",
5444
- description: "Launch a new token from the account's embedded wallet, with an optional dev-buy seed (in the " + "launch transaction on solana, a best-effort follow-up on hood). Works on every plan, Free included (this is not the atomic launch). Then " + "return the fresh market state and token links in one result. MOVES REAL FUNDS unless " + "dryRun. Seeds above the platform dev-buy ceiling are rejected (DEV_BUY_TOO_HIGH); " + "launch, then top up with candle_trade. " + CUSTOM_CURVE_RULE + (imageDir ? IMAGE_PATH_DISCLOSURE : ""),
5630
+ description: "Launch a new token from the account's embedded wallet, with an optional dev-buy seed, then " + "return the fresh market state and token links in one result. On solana a seed makes this " + (options.headlessSeed === true ? "the seed is a separate first-buy transaction in the same Jito bundle as the launch, so " + "both land or neither does. That works on every plan, Free included, and needs no " + "swap:write. On hood the seed is " : "the atomic launch: the launch and the seed are two transactions Candle signs and sends as " + "one Jito bundle, so both land or neither does. That needs Pro or Max (TIER_REQUIRED " + "otherwise) and swap:write on the key, and the result carries bundleId. On hood the seed is ") + "a best-effort follow-up. A launch with no seed works on every plan, Free included. MOVES " + "REAL FUNDS unless dryRun; dry-run first, which spends nothing. Seeds above the platform " + "dev-buy ceiling are rejected (DEV_BUY_TOO_HIGH): at most 0.5 SOL / 500 USDC / 2,000,000 " + "CNDL, or on an exp- curve 10% of the threshold when that is lower; the server holds the " + "creator's own seed to the effective ceiling, so launch with the largest devBuy it allows " + "and top up with candle_trade. " + CUSTOM_CURVE_RULE + (imageDir ? IMAGE_PATH_DISCLOSURE : ""),
5445
5631
  inputSchema: imageDir ? localLaunchAndSeedShape : launchAndSeedShape
5446
5632
  }, async (args) => {
5447
- const result = await executeLaunchAndSeed(args, getConfig(), doFetch, imageDir);
5633
+ const result = await executeLaunchAndSeed(args, getConfig(), doFetch, imageDir, undefined, {
5634
+ headlessSeed: options.headlessSeed === true
5635
+ });
5448
5636
  return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
5449
5637
  });
5450
5638
  const marketRead = createPerpsMarketExecutor(getConfig, env, doFetch);
5451
5639
  for (const name of PERPS_MARKET_TOOL_NAMES) {
5452
5640
  register(name, {
5453
5641
  title: name.replace("candle_perps_", "Hyperliquid "),
5454
- description: "Read live market data directly from Hyperliquid's public API on this machine. Needs a Candle agent key of any scope, no signer. candle.tradeable is Candle's build gate at read time, not a promise; OI caps may still refuse an order. The hosted connector does not carry these tools.",
5642
+ description: "Read live market data directly from Hyperliquid's public API on this machine. Needs a Candle agent key of any scope, no signer. candle.tradeable is Candle's build gate at read time, not a promise; OI caps may still refuse an order. The hosted connector offers the same four reads at its Read level, read by Candle's server instead of this machine.",
5455
5643
  inputSchema: perpsMarketShapes[name],
5456
5644
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
5457
5645
  }, async (args) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@candledottv/mcp",
3
- "version": "0.10.20",
3
+ "version": "0.10.22",
4
4
  "description": "MCP server for the Candle agent rail: launch tokens, read markets, report activity",
5
5
  "type": "module",
6
6
  "license": "MIT",