@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.
- package/README.md +5 -3
- package/dist/index.js +212 -24
- 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.
|
|
61
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
739
|
-
|
|
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 (
|
|
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(
|
|
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
|
-
|
|
749
|
-
|
|
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
|
-
|
|
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({
|
|
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 :
|
|
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
|
|
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),
|
|
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
|
|
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
|
|
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) => {
|