@candledottv/mcp 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +45 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ tools, getting a key, funding the embedded wallet, and idempotent retries, see
|
|
|
85
85
|
| `candle_launch_token` | Launch a token on Candle | `POST /api/v1/launch/headless` (or `/dry-run` when `dryRun: true`) | `CANDLE_AGENT_API_KEY` |
|
|
86
86
|
| `candle_get_market` | Get market state | `GET /api/v1/markets/:chain/:mint` | none |
|
|
87
87
|
| `candle_get_feed` | Get a token feed | `GET /api/v1/markets/feed?bucket=...` | none |
|
|
88
|
-
| `candle_token_forensics` | Deployer history, deploy-window buyers,
|
|
88
|
+
| `candle_token_forensics` | Deployer history, deploy-window buyers, sourced token safety flags | `GET /api/v1/markets/:chain/:mint/forensics` | none |
|
|
89
89
|
| `candle_report_activity` | Report on-chain activity | `POST /api/v1/activity/report` | `CANDLE_AGENT_API_KEY` |
|
|
90
90
|
| `candle_get_agent_profile` | Get an agent profile | `GET /api/v1/users/:idOrWallet/agent` | none |
|
|
91
91
|
| `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`) |
|
package/dist/index.js
CHANGED
|
@@ -106,7 +106,7 @@ function percentOfBalance(balanceRaw, percent) {
|
|
|
106
106
|
import { randomUUID } from "node:crypto";
|
|
107
107
|
|
|
108
108
|
// src/version.ts
|
|
109
|
-
var SERVER_VERSION = "0.
|
|
109
|
+
var SERVER_VERSION = "0.10.0";
|
|
110
110
|
|
|
111
111
|
// src/update-notice.ts
|
|
112
112
|
var PLAIN_VERSION = /^\d+\.\d+\.\d+$/;
|
|
@@ -220,15 +220,24 @@ async function readMarket(mint, cfg, doFetch, extra) {
|
|
|
220
220
|
headers: headers()
|
|
221
221
|
});
|
|
222
222
|
const text = await res.text();
|
|
223
|
-
if (!res.ok)
|
|
224
|
-
|
|
223
|
+
if (!res.ok) {
|
|
224
|
+
let reason;
|
|
225
|
+
try {
|
|
226
|
+
reason = JSON.parse(text)?.error?.routing?.reason;
|
|
227
|
+
} catch {}
|
|
228
|
+
return { status: res.status, err: relayRead(text, extra), reason };
|
|
229
|
+
}
|
|
225
230
|
return { market: JSON.parse(text).market ?? {} };
|
|
226
231
|
}
|
|
227
232
|
function isPaperFlag(value) {
|
|
228
233
|
return value === true || value === "true" || value === 1 || value === "1";
|
|
229
234
|
}
|
|
230
235
|
function heldPaperPosition(positions, mint) {
|
|
231
|
-
|
|
236
|
+
const key = (value) => {
|
|
237
|
+
const trimmed = value.trim();
|
|
238
|
+
return /^0x[0-9a-fA-F]{40}$/.test(trimmed) ? trimmed.toLowerCase() : trimmed;
|
|
239
|
+
};
|
|
240
|
+
return positions.find((p) => key(p.mint) === key(mint) && p.amountRaw !== "0");
|
|
232
241
|
}
|
|
233
242
|
async function readPaperInventory(cfg, doFetch, extra) {
|
|
234
243
|
const res = await doFetch(`${base(cfg)}/api/v1/trade/agent/paper/inventory`, {
|
|
@@ -261,13 +270,18 @@ async function executeTrade(args, cfg, doFetch) {
|
|
|
261
270
|
const amount = args.amount;
|
|
262
271
|
const read = await readMarket(args.mint, cfg, doFetch, { clientTradeId });
|
|
263
272
|
let decimals;
|
|
264
|
-
if ("market" in read) {
|
|
273
|
+
if ("market" in read && (read.market.external === true || read.market.candleLaunched === false)) {
|
|
274
|
+
const q = quoteIdDecimals(args.quoteAsset, chainForMint(args.mint), { clientTradeId });
|
|
275
|
+
if ("err" in q)
|
|
276
|
+
return q.err;
|
|
277
|
+
decimals = q.decimals;
|
|
278
|
+
} else if ("market" in read) {
|
|
265
279
|
const quoteDecimals = read.market.quoteDecimals;
|
|
266
280
|
if (typeof quoteDecimals !== "number") {
|
|
267
281
|
return errText(`could not resolve the quote decimals for mint ${args.mint}; a buy is denominated in that token's own quote asset and this market does not report its scale. Read the market with candle_get_market, then trade a raw amount via the SDK instead`, { clientTradeId });
|
|
268
282
|
}
|
|
269
283
|
decimals = quoteDecimals;
|
|
270
|
-
} else if (read.status === 404) {
|
|
284
|
+
} else if (read.status === 404 && read.reason !== "chain_mismatch") {
|
|
271
285
|
const q = quoteIdDecimals(args.quoteAsset, chainForMint(args.mint), { clientTradeId });
|
|
272
286
|
if ("err" in q)
|
|
273
287
|
return q.err;
|
|
@@ -288,7 +302,7 @@ async function executeTrade(args, cfg, doFetch) {
|
|
|
288
302
|
return errText(`could not resolve decimals for mint ${args.mint}; pass a raw-ready amount via the SDK instead`, { clientTradeId });
|
|
289
303
|
}
|
|
290
304
|
decimals = read.market.decimals;
|
|
291
|
-
} else if (read.status === 404) {
|
|
305
|
+
} else if (read.status === 404 && read.reason !== "chain_mismatch") {
|
|
292
306
|
const inventory = await readPaperInventory(cfg, doFetch, { clientTradeId });
|
|
293
307
|
if ("err" in inventory) {
|
|
294
308
|
return paper ? inventory.err : read.err;
|
|
@@ -841,14 +855,12 @@ function registerTools(server, env = process.env) {
|
|
|
841
855
|
}, async (args) => callAndRelay("candle_launch_token", args, cfg));
|
|
842
856
|
register("candle_get_market", {
|
|
843
857
|
title: "Get market state",
|
|
844
|
-
description: "Read
|
|
845
|
-
|
|
846
|
-
` + "COVERAGE: this answers for tokens that have a CANDLE market. candle_get_feed indexes " + "the wider market too (pump.fun, pons.family), so a mint that feed just returned can " + "still come back MARKET_NOT_FOUND here. That is a coverage boundary, not a fault and " + "not a reason to retry or re-authenticate -- say Candle has no market for it and move on.",
|
|
858
|
+
description: "Read Candle and indexed external markets, including indexed-but-not-routable tokens. No key needed. " + "Read candleLaunched, launchpad, venue and trade.routable; jupiterOk and discovery flags are distinct. " + "Routability is stored eligibility, not a quote or permission. General quotes use POST /api/v1/trade/agent/quote. " + "Curve quotes and lifecycle describe Candle launches. MARKET_NOT_FOUND is a legacy code: read " + "error.routing.reason, error.discovery and sibling error.retryable. A curve-only 404 does not mean untradeable.",
|
|
847
859
|
inputSchema: getMarketShape
|
|
848
860
|
}, async (args) => callAndRelay("candle_get_market", args, cfg));
|
|
849
861
|
register("candle_token_forensics", {
|
|
850
862
|
title: "Token forensics",
|
|
851
|
-
description: `Gate a buy before making it: who launched it (resolved on-chain; pump.fun's shared updateAuthority is never the developer), their went-to-zero rate and last coins, who bought in the deploy window (the creator's own wallets are marked disclosed; strangers in the same slot are the bundle signal),
|
|
863
|
+
description: `Gate a buy before making it: who launched it (resolved on-chain; pump.fun's shared updateAuthority is never the developer), their went-to-zero rate and last coins, who bought in the deploy window (the creator's own wallets are marked disclosed; strangers in the same slot are the bundle signal), same-funder insider share, same-funder deployer cluster, and safety.summary with six sourced flags (mintAuthority, freezeAuthority, tokenExtensions, lpLock, sellability, liquidityDrain). Refuse an unprompted buy when flagged; incomplete or unknown is not clearance. launch.deployerLaunches is an inclusive informational count, never a warning. Every measurement carries a coverage note -- 'unavailable' is not 'clean'. No key needed.
|
|
852
864
|
|
|
853
865
|
MARKET_NOT_FOUND means Candle has no market for that token and this could not run. That is also not 'clean': report that you could not check it, rather than reporting the token as safe. That refusal now carries error.coverage -- covered:false, a reason ('external_launchpad' when the token launched somewhere else, 'unknown_mint' when nobody has indexed it), the launchpad when known, and every check that consequently did not run. Read it instead of guessing. Most of the feed now answers with a partial report instead.`,
|
|
854
866
|
inputSchema: tokenForensicsShape
|
|
@@ -857,7 +869,7 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
|
|
|
857
869
|
title: "Get a token feed",
|
|
858
870
|
description: "Read one of the trade page's public feeds: new, graduated, onfire, or bluechip. Reads " + `only; moves nothing. No key needed. Start here when nobody has named a token.
|
|
859
871
|
|
|
860
|
-
` + "This indexes the WIDER market, not just Candle's own launches
|
|
872
|
+
` + "This indexes the WIDER market, not just Candle's own launches. Rows carry launchpad and " + "discovery flags (jupiterOk, externalTradeable, paperDiscoveryOk, organic0LiveOk). " + "candle_get_market resolves Jupiter-indexed rows; do not hard-skip organicScore=0 on live " + "when organic0LiveOk is true. liquidityDrawdownBps is a signal only — nothing here blocks " + `resolve or trade on drain; filter with where if you want to avoid draining pools.
|
|
861
873
|
|
|
862
874
|
` + "Filter, sort and pick fields SERVER-SIDE rather than reading the whole feed: an " + "unfiltered response is around 135KB and will not fit in a tool result. See `where`, " + "`sort` and `fields`.\n\n" + "One rule to know before screening on safety: a missing field is NOT a false one. " + "mintAuthorityDisabled and freezeAuthorityDisabled are absent on a real share of rows, " + "and absent means nobody checked, not that the authority is disabled. `where` never lets " + 'an absent field satisfy a comparison, so {"mintAuthorityDisabled":{"eq":true}} returns ' + "only tokens that actually say so.",
|
|
863
875
|
inputSchema: getFeedShape
|
|
@@ -960,7 +972,7 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
|
|
|
960
972
|
` + `Before the first trade of a run, once:
|
|
961
973
|
` + "1. candle_execution_status -- confirms the wallets, the tier and this key's spend " + "limits. Call it at the start, or after an auth error; do not infer readiness from a " + `failed trade.
|
|
962
974
|
` + "2. candle_resolve_token -- if a human handed you a bare address. It returns the chain, " + `so you never have to guess it.
|
|
963
|
-
` + "3. candle_token_forensics -- before you quote or buy anything. It returns
|
|
975
|
+
` + "3. candle_token_forensics -- before you quote or buy anything. It returns safety.summary and sourced flags " + "with timestamps and details. Unknown is not clean. MARKET_NOT_FOUND there means Candle has no market for the " + `token, NOT that the token is clean.
|
|
964
976
|
|
|
965
977
|
` + "Arguments: `mint` and `side` are required. Amounts are DECIMAL, never raw base units " + '(amount: "0.5", not lamports). Omitting the amount on a sell sells the whole ' + `position.
|
|
966
978
|
|
|
@@ -991,19 +1003,30 @@ START HERE — five tools need NO credential. Call these first to confirm the se
|
|
|
991
1003
|
candle_get_market price, market cap, volume, curve state for one token
|
|
992
1004
|
candle_get_feed the roster: hot streak, new pairs, graduated, blue chip
|
|
993
1005
|
candle_resolve_token a ticker or partial name -> mint address + chain
|
|
994
|
-
candle_token_forensics call this before quoting or buying. Returns the on-chain developer (never a launchpad shared authority), their went-to-zero rate and last coins, who bought in the deploy window (strangers in the same slot are the bundle signal),
|
|
1006
|
+
candle_token_forensics call this before quoting or buying. Returns the on-chain developer (never a launchpad shared authority), their went-to-zero rate and last coins, who bought in the deploy window (strangers in the same slot are the bundle signal), same-funder insider share, same-funder cluster, and safety.summary with six sourced flags. Refuse an unprompted buy when flagged; incomplete or unknown is not clearance. launch.deployerLaunches is an inclusive informational count, never a warning
|
|
995
1007
|
candle_get_agent_profile your own tier, caps and verified activity
|
|
996
1008
|
|
|
997
1009
|
COVERAGE — read this before you treat an error as a broken server.
|
|
998
1010
|
candle_get_feed indexes the wider market (pump.fun, pons.family and other external launchpads).
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1011
|
+
Feed rows carry jupiterOk / externalTradeable / paperDiscoveryOk / organic0LiveOk on external
|
|
1012
|
+
Solana mints. Use discovery=paper on candle_get_feed when rehearsing; on live screens do NOT
|
|
1013
|
+
hard-skip organicScore=0 when organic0LiveOk is true (visible m5 momentum). Hard-skip organic0
|
|
1014
|
+
only when organic0LiveOk is false.
|
|
1015
|
+
candle_get_market and candle_resolve_token answer for same-chain indexed external mints even
|
|
1016
|
+
when trade.routable is false. Read candleLaunched, launchpad, venue and trade; jupiterOk and
|
|
1017
|
+
paperDiscoveryOk are separate signals. Identity-only Solana reads have route_unverified.
|
|
1018
|
+
General quotes use POST /api/v1/trade/agent/quote; curve quotes/lifecycle describe Candle launches.
|
|
1019
|
+
Stored eligibility is not a successful quote or permission. MARKET_NOT_FOUND remains a legacy
|
|
1020
|
+
code: read error.routing.reason, error.discovery and sibling error.retryable. A curve-only
|
|
1021
|
+
404 is endpoint guidance, not a verdict that Candle cannot trade external tokens.
|
|
1022
|
+
candle_token_forensics also answers for Solana tokens the feed already knows, with a partial
|
|
1023
|
+
report: on-chain developer (never a launchpad shared authority), went-to-zero record, token
|
|
1024
|
+
safety flags, same-funder insiders and cluster. Deploy-window stays unavailable without a
|
|
1025
|
+
Candle launch record. Indexed external Hood tokens also answer, with unknown hacc flags.
|
|
1026
|
+
Unknown mints can still come back MARKET_NOT_FOUND. That remaining gap is a coverage boundary,
|
|
1027
|
+
not a fault and not a reason to retry, re-auth, or tell the human the integration is down.
|
|
1028
|
+
Report MARKET_NOT_FOUND as "Candle has no market for this token, so I could not run forensics
|
|
1029
|
+
on it" and let the human decide.
|
|
1007
1030
|
|
|
1008
1031
|
Never let a MARKET_NOT_FOUND stand in for a clean bill of health. The same rule governs the
|
|
1009
1032
|
coverage note on every forensics measurement: "unavailable" is NOT "clean" — say so rather than
|