@candledottv/mcp 0.10.28 → 0.10.30

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 +74 -19
  2. package/dist/index.js +338 -37
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,20 +1,22 @@
1
1
  # @candledottv/mcp
2
2
 
3
3
  An MCP (Model Context Protocol) server for the Candle agent rail. The current source exposes Candle's REST API as
4
- eighty-two tools over stdio, plus four Hyperliquid market reads (eighty-six in all), so an MCP-capable agent can launch tokens (optionally seeded with a dev
4
+ eighty-seven tools over stdio, plus four Hyperliquid market reads (eighty-eight in all), so an MCP-capable agent can launch tokens (optionally seeded with a dev
5
5
  buy in the same call), trade, convert between base assets (including across chains), read market
6
6
  and feed data, read one token's signals and tracked-wallet trades and its card data, report on-chain activity,
7
7
  check an agent profile, and backtest a strategy in hacc's strategy lab without hand-rolling HTTP calls. The five
8
8
  strategy lab tools are unreleased source.
9
9
 
10
+ Not sure this is the right tool? [How to choose: SDK, MCP or CLI](https://docs.candle.tv/developers/choose-a-surface).
11
+
10
12
  ## Try it without a key
11
13
 
12
- Eighteen tools are read-only and need nothing but `CANDLE_API_URL` (which already defaults to
13
- production): `candle_get_market`, `candle_get_feed`, `candle_token_events`, `candle_token_card`, `candle_token_history`,
14
+ Nineteen tools are read-only and need nothing but `CANDLE_API_URL` (which already defaults to
15
+ production): `candle_get_market`, `candle_get_price_history`, `candle_get_feed`, `candle_token_events`, `candle_token_card`, `candle_token_history`,
14
16
  `candle_token_page_about`, `candle_token_page_burns`, `candle_token_page_history`, `candle_token_page_updates`, `candle_token_page_comments`, `candle_token_forensics`, `candle_token_clusters`,
15
17
  `candle_get_agent_profile`, `candle_list_agents`, `candle_list_agent_posts`, `candle_get_agent_positions`, `candle_resolve_token` and `candle_get_plans`.
16
- The five token-page reads, `candle_token_clusters` and `candle_token_history` are unreleased source; published 0.10.25 has the original ten, and `candle_get_agent_positions` is new in 0.10.27.
17
- With a source build and token-page reads enabled on the deployment, those eighteen work immediately without a key.
18
+ All of them but `candle_get_price_history` (new in source, not yet published) are in the published 0.10.29; `candle_get_agent_positions` is new since 0.10.27.
19
+ With token-page reads enabled on the deployment, those nineteen work immediately without a key.
18
20
  Install the released server with no `env` block at all and its ten public reads work immediately:
19
21
 
20
22
  ```json
@@ -34,8 +36,10 @@ Environment below -- or skip env editing entirely: install the Candle CLI
34
36
  `candle auth login`, and `candle mcp` launches this server with the stored key and API URL in its
35
37
  environment.
36
38
 
39
+ To mint a key for another agent without keeping it as the CLI profile's working key, run `candle keys create --access write --label agent-one --no-store` after device login. The key is printed once; put it in the intended agent's secret store. `--json` reports `stored: false` and exits 0. The profile's existing working key, prefix and scopes stay unchanged, even when it has no working key. Add `--embedded-wallet allow` at a terminal if this key needs the account's embedded wallet. This is a CLI storage option; it adds no MCP tool and does not change tool counts.
40
+
37
41
  `CANDLE_MCP_TOOLS` (optional) is a comma-separated allowlist of tool names; only those register.
38
- Unset means all eighty-two. An unknown name fails startup with the valid names in the message, rather
42
+ Unset means all eighty-seven. An unknown name fails startup with the valid names in the message, rather
39
43
  than silently registering the wrong surface. `candle mcp --read-only` / `--tools` set this for
40
44
  you.
41
45
 
@@ -52,8 +56,8 @@ Both take decimal amounts, never raw base units, and resolve the scale themselve
52
56
  - A `candle_trade` **buy** is denominated in the token's OWN quote asset, whatever it was launched
53
57
  against (SOL for a SOL-launched token, USDC or CNDL for those quote pairs). The tool reads the
54
58
  market first and converts against its `quoteDecimals`; the `quoteAsset` field applies only to a
55
- mint Candle never launched, the path traded through Jupiter (buying needs Pro or Max; selling
56
- works on any plan). A
59
+ mint Candle never launched, the path traded through Jupiter (buying and selling work on any
60
+ plan). A
57
61
  **sell** is denominated in tokens and converts against the market's own `decimals`.
58
62
  - A `candle_launch_and_seed` `devBuy` is denominated in the quote asset that launch selects, since
59
63
  there the caller genuinely picks the new token's quote pair: `quoteAsset` if given, otherwise
@@ -71,6 +75,13 @@ rest, and omitted uses `curve_price` when allocations fit, otherwise `legacy`. `
71
75
  `discord`. See
72
76
  [Custom curves](https://docs.candle.tv/developers/headless-launch#custom-curves-choose-v-and-g-switched-off).
73
77
 
78
+ On Solana, where the API has `CURVE_LAUNCH_RULES_ENABLED` on, both launch tools take
79
+ `launchRules`, the request's nested object in seconds and raw units like their other amounts:
80
+ `openDelaySecs`, `maxBuyPerWalletRaw` with `capWindowSecs`, `allowlist` with
81
+ `allowlistWindowSecs`, and `devBuyMaxRaw`. The API checks the bounds and answers
82
+ `LAUNCH_RULES_INVALID` with the field and the reason. See
83
+ [Launch rules](https://docs.candle.tv/developers/headless-launch#launch-rules-solana).
84
+
74
85
  For a full walkthrough of `candle_launch_and_seed` and `candle_trade`, including the keyless read
75
86
  tools, getting a key, funding the embedded wallet, and idempotent retries, see
76
87
  [Candle MCP server](https://docs.candle.tv/developers/mcp-server).
@@ -85,7 +96,7 @@ tools, getting a key, funding the embedded wallet, and idempotent retries, see
85
96
  non-loopback host. For a trusted local endpoint that is not loopback, such as a devcontainer
86
97
  reaching its host; not for anything that leaves the machine.
87
98
  - `CANDLE_AGENT_API_KEY` -- an agent API key (`cndl_live_...` / `cndl_test_...`), issued from a
88
- Candle account's agent settings page. Required by every tool except the eighteen read-only ones
99
+ Candle account's agent settings page. Required by every tool except the nineteen read-only ones
89
100
  listed above, which includes the account-scoped reads (`candle_get_wallets`,
90
101
  `candle_execution_status`, `candle_get_operation`) as well as the writes; those eighteen
91
102
  work without it, so the server is useful the moment it is installed and only asks for a key when
@@ -114,6 +125,7 @@ tools, getting a key, funding the embedded wallet, and idempotent retries, see
114
125
  | --- | --- | --- | --- |
115
126
  | `candle_launch_token` | Launch a token on Candle | `POST /api/v1/launch/headless` (or `/dry-run` when `dryRun: true`); with `imagePath`, `POST /api/v1/uploads/agent-image` first | `CANDLE_AGENT_API_KEY` |
116
127
  | `candle_get_market` | Get market state | `GET /api/v1/markets/:chain/:mint` | none |
128
+ | `candle_get_price_history` | One Solana token's OHLC bars in USD per token, oldest first (interval 1m-1d, limit 1-500, `to` unix seconds); `source` and `volumeUnit` say whose bars. Hood is refused with its REST route | `GET /api/v1/markets/solana/:mint/candles` | none |
117
129
  | `candle_get_feed` | Get a token feed | `GET /api/v1/markets/feed?bucket=...` | none |
118
130
  | `candle_token_edit` | As the token's creator, set Candle's copy of its description and links; an unknown `socials` key is refused locally. Needs a `launch:write` key from the launching account; reuse `idempotencyKey` to retry. Off until the deployment switches it on (503 `METADATA_EDIT_DISABLED`) | `PATCH /api/v1/tokens/:chain/:mint/metadata` | `x-api-key` |
119
131
  | `candle_token_history` | A token's metadata edits, newest first, with the previous values | `GET /api/v1/tokens/:chain/:mint/metadata/history?limit=...&cursor=...` | none |
@@ -157,12 +169,16 @@ tools, getting a key, funding the embedded wallet, and idempotent retries, see
157
169
  | `candle_get_wallets` | The account's embedded wallets, one per chain, with delegation state | `GET /api/v1/agent/wallets/embedded` | `CANDLE_AGENT_API_KEY` |
158
170
  | `candle_get_profile_wallets` | Which wallets an agent profile may spend from, and whether it is scoped | `GET /api/v1/agent/keys/:prefix/wallets` | `CANDLE_AGENT_API_KEY` |
159
171
  | `candle_set_profile_wallets` | Replace the wallets an agent profile may spend from | `PUT /api/v1/agent/keys/:prefix/wallets` | `CANDLE_AGENT_API_KEY` |
160
- | `candle_get_profile_pnl` | An agent profile's share of the account's P&L, the console's figures to the cent: realized on its sales, fees, and the open positions that belong to it at cost basis, marked at current prices (unpriced positions counted, not zeroed), with `totalUsd`, the closed positions as made, lost and partial sells (`closed`), dust flagged and counted apart (`openPositionsExDust`), split by wallet (`pnl.byWallet`, summing to the total) and by chain (`pnl.byChain`), with `chain` and `wallet` on every position | `GET /api/v1/agent/keys/:prefix/pnl` | `CANDLE_AGENT_API_KEY` |
161
- | `candle_get_profile_trades` | An agent profile's orders, fills, fees and transaction hashes | `GET /api/v1/agent/keys/:prefix/trades` | `CANDLE_AGENT_API_KEY` |
172
+ | `candle_get_profile_pnl` | An agent profile's share of the account's P&L, the console's figures to the cent: realized on its sales, fees, and the open positions that belong to it at cost basis, marked at current prices (unpriced positions counted, not zeroed), with `totalUsd`, the closed positions as made, lost and partial sells (`closed`), dust flagged and counted apart (`openPositionsExDust`), split by wallet (`pnl.byWallet`, summing to the total) and by chain (`pnl.byChain`), with `chain` and `wallet` on every position; `sinceMs` adds a `window` of realized and open mark changes, including older holdings (see "P&L since a time") | `GET /api/v1/agent/keys/:prefix/pnl` | `CANDLE_AGENT_API_KEY` |
173
+ | `candle_get_profile_trades` | An agent profile's orders, fills, fees and transaction hashes; page by time with `since`, `until` and `cursor` | `GET /api/v1/agent/keys/:prefix/trades` | `CANDLE_AGENT_API_KEY` |
174
+ | `candle_get_profile_paper` | An agent profile's PAPER P&L and 50 latest paper trades, from quotes (`basis: "quote"`), never live figures | `GET /api/v1/agent/keys/:prefix/paper` | `CANDLE_AGENT_API_KEY` |
175
+ | `candle_get_paper_inventory` | This key's paper positions in raw units (needs `swap:write`) | `GET /api/v1/trade/agent/paper/inventory` | `CANDLE_AGENT_API_KEY` |
176
+ | `candle_get_account_pnl` | P&L for every agent profile on the account in one read (`candle pnl` without `--profile`) | `GET /api/v1/agent/keys/pnl` | `CANDLE_AGENT_API_KEY` |
162
177
  | `candle_get_portfolio` | What the account's embedded and TEE wallets hold on Solana and Hood (Hood in `hood`), with prices; unpriced holdings say why, never zero | `GET /api/v1/agent/portfolio` | `CANDLE_AGENT_API_KEY` (`account:read`) |
163
- | `candle_execution_status` | One call before trading: wallets to spend from, tier, and whether trading is possible | Composes the wallet and tier reads | `CANDLE_AGENT_API_KEY` |
164
- | `candle_get_operation` | Look up a trade or launch by the id its write used, and whether it landed | `GET /api/v1/trade/agent/jobs/:clientId` or `/api/v1/launch/headless/jobs/:clientId` | `CANDLE_AGENT_API_KEY` |
178
+ | `candle_execution_status` | One call before trading: wallets to spend from, tier, this key's limits (`limits.txLimitUse` has its USD limit use and when it resets), and its loss limits and stop state (a stopped key is `ready: false` with a top-level `stopped`) | Composes the wallet, tier, `GET /api/v1/agent/keys/self/limits` and `GET /api/v1/agent/keys/self/risk` reads | `CANDLE_AGENT_API_KEY` |
179
+ | `candle_get_operation` | Look up a trade, swap or launch by the id its write used, and whether it landed. `kind: "swap"` reads a `candle_swap` by its `clientSwapId` and answers `job.outcome` (`in_flight`, `confirmed`, `failed`, `partial` or `indeterminate`) | `GET /api/v1/trade/agent/jobs/:clientId`, `/api/v1/agent/swap/by-client-id/:clientId` or `/api/v1/launch/headless/jobs/:clientId` | `CANDLE_AGENT_API_KEY` |
165
180
  | `candle_get_fees` | The creator fees the account's launched tokens can pay: raw amounts from chain reads, and the reason a token cannot be claimed | `GET /api/v1/agent/fees` | `CANDLE_AGENT_API_KEY` (`account:read`) |
181
+ | `candle_atomic_launch_dry_run` | Check a Solana launch with first buys and price each buy on the curve it would deploy; builds, reserves and writes nothing | `POST /api/v1/launch/atomic/dry-run` | `CANDLE_AGENT_API_KEY` (`launch:write`, `swap:write`, Pro or Max) |
166
182
  | `candle_claim_fees` | Collect one token's creator fees to the wallet fixed at launch (no destination argument); `dryRun` sends nothing | `POST /api/v1/agent/fees/:mint/claim` | `CANDLE_AGENT_API_KEY` (`launch:write`) |
167
183
  | `candle_get_staking` | Read CNDL/sCNDL, withdrawals, rewards, pool wait and wallet eligibility notes | `GET /api/v1/agent/staking` |
168
184
  | `candle_stake` | Stake raw CNDL; stable clientStakeId, dryRun first | `POST /api/v1/agent/staking/stake/build` |
@@ -239,7 +255,7 @@ explicitly. Assets with nothing spendable report `empty`; a failed asset never s
239
255
  ## Errors
240
256
 
241
257
  This package never reinterprets an error body, and that body is not one uniform shape across all
242
- eighty-two tools:
258
+ eighty-seven tools:
243
259
 
244
260
  - `candle_token_events`, called with `kind`, is the one narrowing of a SUCCESS body, never an
245
261
  error one: the relayed `events` are filtered to that kind and the body gains `filteredByKind`.
@@ -409,10 +425,9 @@ Every JSON result carries `network`, epoch-ms `readAt`, `gate: { minOpenInterest
409
425
 
410
426
  SDK and local MCP cache market reads for five seconds; book and candle snapshots are never cached. CLI/MCP select testnet with `CANDLE_HYPERLIQUID_NETWORK=testnet`; SDK uses `hyperliquidNetwork: "testnet"`. These four tools are local-only and are absent from hosted MCP tool sets. MCP uses the existing `CANDLE_API_KEY` or `CANDLE_AGENT_API_KEY` configuration, with no signer PEM required. No signing, relay or `/exchange` request occurs.
411
427
 
412
- ## Token page tools (unreleased local source)
428
+ ## Token page tools (local clients)
413
429
 
414
- The sixteen `candle_token_page_*` tools below are available in local MCP source and the bundled
415
- `candle mcp` source. They require a human-owned package release before npm installs receive them.
430
+ The sixteen `candle_token_page_*` tools below are in the published 0.10.29 and the bundled `candle mcp`.
416
431
  They are not offered by the hosted OAuth connector. Existing OAuth consent and key grants are
417
432
  unchanged; local keys need explicit `token-pages:read` or `token-pages:write` scopes. All token-page
418
433
  switches default off: `404`/`feature_off` is a refusal, not an empty page or permission grant.
@@ -447,7 +462,7 @@ removed or unsafe versions cannot be restored. The four reads have read-only MCP
447
462
  and content mutations have mutation hints and approval classifications; content approvals retain
448
463
  `idempotencyKey` as their operation ID. The server never enables a deployment switch.
449
464
 
450
- Token update tools in unreleased source:
465
+ Token update tools:
451
466
 
452
467
  | Tool | Behavior |
453
468
  | --- | --- |
@@ -461,7 +476,7 @@ All three mutations require `token-pages:write` and a durable `idempotencyKey`.
461
476
  Update bodies cannot be edited. Reporting and moderation apply to their exact post IDs.
462
477
  These tools grant no notification delivery or activation permission.
463
478
 
464
- Token comment tools in unreleased source:
479
+ Token comment tools:
465
480
 
466
481
  | Tool | Behavior |
467
482
  | --- | --- |
@@ -471,3 +486,43 @@ Token comment tools in unreleased source:
471
486
  A comment uses the same eligibility, restriction and idempotency rules as a section edit. It is
472
487
  never pushed to holders and cannot be edited. `candle_token_page_post_delete` removes the key
473
488
  account's own comment, including after selling; no other account can. Flags apply to its exact ID.
489
+
490
+ ## Rate-limit cost per tool
491
+
492
+ Every tool spends from the key's per-minute limit (`tierCaps.rateLimitPerMin` on `GET /api/v1/agent/tier`; Free 30, Pro 300, Max 600). Over `candle mcp` (stdio) a tool costs one per REST request it makes: `candle_execution_status` makes 4, and `candle_get_portfolio`, `candle_get_profile_pnl` and `candle_get_wallets` make 1 each. The hosted connector charges one per tool call at admission, and adds a separate 60 a minute per IP. A refusal is `429 RATE_LIMITED` with `Retry-After: 60`. Details: the developer docs, "Rate limits and what a call costs".
493
+
494
+
495
+ ## P&L since a time
496
+
497
+ `GET /api/v1/agent/keys/{prefix}/pnl?sinceMs=<unix ms>` (SDK `getProfilePnl(prefix, { sinceMs })`, MCP
498
+ `candle_get_profile_pnl` with `sinceMs`) adds a spot trading `window` beside the unchanged all-time
499
+ `pnl`. LP, Hyperliquid and creator fee income are separate from this window.
500
+
501
+ Holdings present immediately before T start at the last completed historical minute candle's close,
502
+ not their lifetime purchase cost. The candle containing T is excluded to avoid using future trades.
503
+ `boundary.marks` gives each price, `source: "candle-close"` and `updatedAt` (the candle's end, Unix ms);
504
+ a quiet token's last candle can be older than T. These are historical observed marks, not interpolated
505
+ prices at exactly T. Later buys enter at their actual trade cost. Partial sells, additional buys and
506
+ moves between wallets use the account engine's average-cost rules on this opening book.
507
+
508
+ `realizedGrossUsd` is sales' profit against window basis; `feesUsd` charges all in-window buy and sell
509
+ fees exactly once, including buys still open. Fees are debited separately and never added to cost
510
+ basis. Fees follow the key that placed each fill; a manual sale credits the original opener's gross
511
+ but its fee stays with Manual.
512
+ `realizedNetUsd = realizedGrossUsd - feesUsd`;
513
+ `unrealizedUsd` is open holdings' change against window basis; `windowedUsd` is their sum. `closed`
514
+ and `closedRounds` describe rounds closed during this replay, using window basis and only window
515
+ fees. A pre-T buy fee is excluded even when that holding is sold after T. Fills exactly at T are included.
516
+
517
+ `complete: false` and `incompleteReasons` explicitly identify missing boundary/current marks,
518
+ truncated history, unvalued fills or unknown/incomplete cost. In that case **`windowedUsd` is null**;
519
+ component figures are partial, never a complete zero. `boundary.missingMints` names missing opening
520
+ prices, including holdings since fully closed. Boundary coverage is checked account-wide because
521
+ other wallets' and unplaced holdings can fund this key's sales. Price history uses the existing
522
+ Candle candle store; tokens without historical candles have incomplete coverage. There are no new
523
+ vendor requests or fabricated opening prices. The window requires the Postgres account ledger
524
+ (`API_DATABASE_URL`); an unavailable ledger/history read returns an error, not an all-time fallback.
525
+
526
+ `sinceMs` must be a non-negative integer Unix timestamp in milliseconds at or before now; invalid,
527
+ blank, fractional or future inputs return `VALIDATION_FAILED` (400). Omitting it returns the existing
528
+ all-time response without `window`.
package/dist/index.js CHANGED
@@ -189,7 +189,7 @@ function readClusterResult(value, mint) {
189
189
  }
190
190
 
191
191
  // src/version.ts
192
- var SERVER_VERSION = "0.10.28";
192
+ var SERVER_VERSION = "0.10.30";
193
193
 
194
194
  // src/update-notice.ts
195
195
  var PLAIN_VERSION = /^\d+\.\d+\.\d+$/;
@@ -1445,7 +1445,7 @@ var LAUNCH_DEFINITION = {
1445
1445
  {
1446
1446
  key: "feeWallet",
1447
1447
  type: "string",
1448
- help: "Solana only: the wallet this launch's curve and pool fees earn to, an on-curve wallet or a Squads v4 vault; defaults to the account wallet.",
1448
+ help: "Solana only: the wallet this launch's curve and pool fees earn to, an on-curve wallet or a Squads v4 vault; defaults to the wallet that launches the token (the account wallet on a server without LAUNCH_FEE_WALLET_FROM_LAUNCHER, except an embedded launch).",
1449
1449
  chains: [
1450
1450
  "solana"
1451
1451
  ],
@@ -1536,6 +1536,171 @@ var LAUNCH_DEFINITION = {
1536
1536
  }
1537
1537
  }
1538
1538
  },
1539
+ {
1540
+ key: "launchRules.openDelaySecs",
1541
+ type: "duration",
1542
+ help: "Launch rules, Solana only, where the server has CURVE_LAUNCH_RULES_ENABLED on: how long after the launch buys open, at most 7 days (604800 seconds); before then only the launching wallet may buy. The CLI takes a duration (90m, 6h, 2d, or seconds); the request field is whole seconds.",
1543
+ wireType: "number",
1544
+ range: {
1545
+ min: "0",
1546
+ max: "604800",
1547
+ unit: "seconds"
1548
+ },
1549
+ chains: [
1550
+ "solana"
1551
+ ],
1552
+ surfaces: {
1553
+ cli: {
1554
+ flag: "--open-in",
1555
+ value: "<duration>"
1556
+ },
1557
+ mcp: {
1558
+ key: "launchRules.openDelaySecs"
1559
+ },
1560
+ sdk: {
1561
+ key: "launchRules.openDelaySecs"
1562
+ }
1563
+ }
1564
+ },
1565
+ {
1566
+ key: "launchRules.maxBuyPerWalletRaw",
1567
+ type: "decimal",
1568
+ help: "The most quote one wallet may spend on the curve during --cap-window, curve fee included and Candle's fee not; needs --cap-window. The CLI takes whole quote units; the request field is raw units as a decimal string.",
1569
+ range: {
1570
+ unit: "whole quote units"
1571
+ },
1572
+ chains: [
1573
+ "solana"
1574
+ ],
1575
+ requires: [
1576
+ "launchRules.capWindowSecs"
1577
+ ],
1578
+ surfaces: {
1579
+ cli: {
1580
+ flag: "--max-buy-per-wallet",
1581
+ value: "<amount>"
1582
+ },
1583
+ mcp: {
1584
+ key: "launchRules.maxBuyPerWalletRaw"
1585
+ },
1586
+ sdk: {
1587
+ key: "launchRules.maxBuyPerWalletRaw"
1588
+ }
1589
+ }
1590
+ },
1591
+ {
1592
+ key: "launchRules.capWindowSecs",
1593
+ type: "duration",
1594
+ help: "How long the per-wallet cap holds, counted from the open: 1 second to 24 hours (86400 seconds); needs --max-buy-per-wallet.",
1595
+ wireType: "number",
1596
+ range: {
1597
+ min: "1",
1598
+ max: "86400",
1599
+ unit: "seconds"
1600
+ },
1601
+ chains: [
1602
+ "solana"
1603
+ ],
1604
+ requires: [
1605
+ "launchRules.maxBuyPerWalletRaw"
1606
+ ],
1607
+ surfaces: {
1608
+ cli: {
1609
+ flag: "--cap-window",
1610
+ value: "<duration>"
1611
+ },
1612
+ mcp: {
1613
+ key: "launchRules.capWindowSecs"
1614
+ },
1615
+ sdk: {
1616
+ key: "launchRules.capWindowSecs"
1617
+ }
1618
+ }
1619
+ },
1620
+ {
1621
+ key: "launchRules.allowlist",
1622
+ type: "file",
1623
+ help: "Only these wallets may buy during --allowlist-window: 1 to 4,096 distinct Solana addresses; needs --allowlist-window. The CLI reads a file, one address per line or comma separated; the request field is the list.",
1624
+ wireType: "string[]",
1625
+ range: {
1626
+ min: "1",
1627
+ max: "4096",
1628
+ unit: "wallets"
1629
+ },
1630
+ chains: [
1631
+ "solana"
1632
+ ],
1633
+ requires: [
1634
+ "launchRules.allowlistWindowSecs"
1635
+ ],
1636
+ surfaces: {
1637
+ cli: {
1638
+ flag: "--allowlist",
1639
+ value: "<path>"
1640
+ },
1641
+ mcp: {
1642
+ key: "launchRules.allowlist"
1643
+ },
1644
+ sdk: {
1645
+ key: "launchRules.allowlist"
1646
+ }
1647
+ }
1648
+ },
1649
+ {
1650
+ key: "launchRules.allowlistWindowSecs",
1651
+ type: "duration",
1652
+ help: "How long the allowlist phase holds, counted from the open: 1 second to 24 hours (86400 seconds); needs --allowlist.",
1653
+ wireType: "number",
1654
+ range: {
1655
+ min: "1",
1656
+ max: "86400",
1657
+ unit: "seconds"
1658
+ },
1659
+ chains: [
1660
+ "solana"
1661
+ ],
1662
+ requires: [
1663
+ "launchRules.allowlist"
1664
+ ],
1665
+ surfaces: {
1666
+ cli: {
1667
+ flag: "--allowlist-window",
1668
+ value: "<duration>"
1669
+ },
1670
+ mcp: {
1671
+ key: "launchRules.allowlistWindowSecs"
1672
+ },
1673
+ sdk: {
1674
+ key: "launchRules.allowlistWindowSecs"
1675
+ }
1676
+ }
1677
+ },
1678
+ {
1679
+ key: "launchRules.devBuyMaxRaw",
1680
+ type: "decimal",
1681
+ help: "The most the launching wallet may buy before the open, only with --open-in: at least the launch's own buy, at most the dev-buy ceiling, and the buy by default. That spend counts toward the per-wallet cap after the open. The CLI takes whole quote units; the request field is raw units as a decimal string.",
1682
+ range: {
1683
+ unit: "whole quote units"
1684
+ },
1685
+ chains: [
1686
+ "solana"
1687
+ ],
1688
+ requires: [
1689
+ "launchRules.openDelaySecs"
1690
+ ],
1691
+ surfaces: {
1692
+ cli: {
1693
+ flag: "--dev-buy-max",
1694
+ value: "<amount>"
1695
+ },
1696
+ mcp: {
1697
+ key: "launchRules.devBuyMaxRaw"
1698
+ },
1699
+ sdk: {
1700
+ key: "launchRules.devBuyMaxRaw"
1701
+ }
1702
+ }
1703
+ },
1539
1704
  {
1540
1705
  key: "dryRun",
1541
1706
  type: "boolean",
@@ -1558,7 +1723,7 @@ var LAUNCH_DEFINITION = {
1558
1723
 
1559
1724
  // src/launch-schema.ts
1560
1725
  var M2_TOP_KEYS = new Set(["wallet", "buy", "feeWallet", "feeWalletMultisig"]);
1561
- var GROUPED_TOP_KEYS = new Set(["socials", "curve", "poolFee"]);
1726
+ var GROUPED_TOP_KEYS = new Set(["socials", "curve", "poolFee", "launchRules"]);
1562
1727
  function mcpKey(field) {
1563
1728
  const surface = field.surfaces.mcp;
1564
1729
  return "key" in surface ? surface.key : undefined;
@@ -1683,6 +1848,20 @@ function poolFeeSchema() {
1683
1848
  }).strict()
1684
1849
  ]).optional().describe([tierHelp, startHelp, endHelp, minutesHelp].join(" "));
1685
1850
  }
1851
+ function launchRulesSchema() {
1852
+ const helps = [];
1853
+ const shape = {};
1854
+ for (const field of mcpLaunchFields()) {
1855
+ const key = mcpKey(field);
1856
+ if (!key?.startsWith("launchRules."))
1857
+ continue;
1858
+ const wire = "wireType" in field ? field.wireType : undefined;
1859
+ const schema = wire === "number" ? z3.number().int() : wire === "string[]" ? z3.array(z3.string()) : z3.string();
1860
+ shape[key.slice("launchRules.".length)] = schema.optional().describe(field.help);
1861
+ helps.push(field.help);
1862
+ }
1863
+ return z3.object(shape).passthrough().optional().describe(helps.join(" "));
1864
+ }
1686
1865
  function scalarSchema(field) {
1687
1866
  const values = "values" in field ? field.values : undefined;
1688
1867
  let schema;
@@ -1714,6 +1893,7 @@ function buildLaunchTokenShape() {
1714
1893
  shape.socials = socialsSchema();
1715
1894
  shape.curve = curveSchema();
1716
1895
  shape.poolFee = poolFeeSchema();
1896
+ shape.launchRules = launchRulesSchema();
1717
1897
  shape.buyAmount = z3.union([z3.string(), z3.number()]).optional().describe(BUY_AMOUNT_DESCRIPTION);
1718
1898
  return shape;
1719
1899
  }
@@ -2219,6 +2399,7 @@ async function resolveToken(args, cfg, doFetch) {
2219
2399
  return read.err;
2220
2400
  return { text: JSON.stringify({ success: true, chain, mint: args.mint, market: read.market }, null, 2) };
2221
2401
  }
2402
+ var STOP_CLEARED_BY = "An account owner reviews the profile and resumes it from the agent console; an API key cannot resume it.";
2222
2403
  async function executionStatus(cfg, doFetch) {
2223
2404
  const apiKey = requireApiKey(cfg);
2224
2405
  const get = async (path) => {
@@ -2233,27 +2414,33 @@ async function executionStatus(cfg, doFetch) {
2233
2414
  }
2234
2415
  return { ok: res.ok, status: res.status, body };
2235
2416
  };
2236
- const [wallets, tier, limits] = await Promise.all([
2417
+ const [wallets, tier, limits, risk] = await Promise.all([
2237
2418
  get("/api/v1/agent/wallets/embedded"),
2238
2419
  get("/api/v1/agent/tier"),
2239
- get("/api/v1/agent/keys/self/limits")
2420
+ get("/api/v1/agent/keys/self/limits"),
2421
+ get("/api/v1/agent/keys/self/risk")
2240
2422
  ]);
2241
2423
  const unreadable = [
2242
2424
  ["wallets", wallets],
2243
2425
  ["tier", tier],
2244
- ["limits", limits]
2426
+ ["limits", limits],
2427
+ ["risk", risk]
2245
2428
  ].filter(([, r]) => !r.ok);
2246
2429
  const tierBody = tier.ok ? tier.body : undefined;
2430
+ const riskBody = risk.ok ? risk.body : undefined;
2431
+ const stopped = riskBody?.stopped === true;
2247
2432
  return {
2248
2433
  text: JSON.stringify({
2249
2434
  success: true,
2250
- ready: unreadable.length === 0 ? true : undefined,
2435
+ ready: stopped ? false : unreadable.length === 0 ? true : undefined,
2436
+ stopped: stopped ? { tripped: riskBody?.tripped ?? null, clearedBy: STOP_CLEARED_BY } : undefined,
2251
2437
  notice: tierBody?.maxExpired ? tierBody.maxExpiredNotice : undefined,
2252
2438
  updateAvailable: updateAvailable() ?? undefined,
2253
2439
  unreadable: unreadable.length > 0 ? unreadable.map(([name]) => name) : undefined,
2254
2440
  wallets: wallets.body,
2255
2441
  tier: tier.body,
2256
- limits: limits.body
2442
+ limits: limits.body,
2443
+ risk: risk.body
2257
2444
  }, null, 2),
2258
2445
  ...unreadable.length > 0 ? { isError: true } : {}
2259
2446
  };
@@ -6306,6 +6493,7 @@ var TOOL_NAMES = [
6306
6493
  "candle_vamp_candidates",
6307
6494
  "candle_token_events",
6308
6495
  "candle_token_card",
6496
+ "candle_get_price_history",
6309
6497
  "candle_token_edit",
6310
6498
  "candle_token_history",
6311
6499
  "candle_token_page_about",
@@ -6338,6 +6526,7 @@ var TOOL_NAMES = [
6338
6526
  "candle_get_plans",
6339
6527
  "candle_trade",
6340
6528
  "candle_launch_and_seed",
6529
+ "candle_atomic_launch_dry_run",
6341
6530
  "candle_swap",
6342
6531
  "candle_transfer",
6343
6532
  "candle_sweep",
@@ -6346,6 +6535,9 @@ var TOOL_NAMES = [
6346
6535
  "candle_set_profile_wallets",
6347
6536
  "candle_get_profile_pnl",
6348
6537
  "candle_get_profile_trades",
6538
+ "candle_get_paper_inventory",
6539
+ "candle_get_profile_paper",
6540
+ "candle_get_account_pnl",
6349
6541
  "candle_get_portfolio",
6350
6542
  "candle_resolve_token",
6351
6543
  "candle_execution_status",
@@ -6472,6 +6664,18 @@ function buildRequest(name, args, cfg) {
6472
6664
  init: { method: "POST", headers: jsonHeaders(apiKey), body: JSON.stringify(body) }
6473
6665
  };
6474
6666
  }
6667
+ case "candle_atomic_launch_dry_run": {
6668
+ const apiKey = requireApiKey2(cfg);
6669
+ const { payer, chain: _chain, buyAmount: _buyAmount, ...rest } = args;
6670
+ return {
6671
+ url: `${base2}/api/v1/launch/atomic/dry-run`,
6672
+ init: {
6673
+ method: "POST",
6674
+ headers: jsonHeaders(apiKey),
6675
+ body: JSON.stringify({ ...rest, chain: "solana", payer: payer ?? { type: "main" } })
6676
+ }
6677
+ };
6678
+ }
6475
6679
  case "candle_get_market": {
6476
6680
  const { chain, mint } = args;
6477
6681
  return {
@@ -6479,6 +6683,21 @@ function buildRequest(name, args, cfg) {
6479
6683
  init: { method: "GET", headers: jsonHeaders() }
6480
6684
  };
6481
6685
  }
6686
+ case "candle_get_price_history": {
6687
+ const { chain, mint, interval, limit, to } = args;
6688
+ const query2 = new URLSearchParams;
6689
+ if (interval !== undefined)
6690
+ query2.set("interval", interval);
6691
+ if (limit !== undefined)
6692
+ query2.set("limit", String(limit));
6693
+ if (to !== undefined)
6694
+ query2.set("to", String(to));
6695
+ const qs = query2.size > 0 ? `?${query2}` : "";
6696
+ return {
6697
+ url: `${base2}/api/v1/markets/${encodeURIComponent(chain ?? "solana")}/${encodeURIComponent(mint)}/candles${qs}`,
6698
+ init: { method: "GET", headers: jsonHeaders() }
6699
+ };
6700
+ }
6482
6701
  case "candle_token_events": {
6483
6702
  const { chain, mint, since, limit, before } = args;
6484
6703
  const params = new URLSearchParams;
@@ -6636,7 +6855,8 @@ function buildRequest(name, args, cfg) {
6636
6855
  case "candle_get_operation": {
6637
6856
  const apiKey = requireApiKey2(cfg);
6638
6857
  const { clientId: clientId2, kind } = args;
6639
- const path = kind === "launch" ? `/api/v1/launch/headless/jobs/${encodeURIComponent(clientId2)}` : `/api/v1/trade/agent/jobs/${encodeURIComponent(clientId2)}`;
6858
+ const id = encodeURIComponent(clientId2);
6859
+ const path = kind === "launch" ? `/api/v1/launch/headless/jobs/${id}` : kind === "swap" ? `/api/v1/agent/swap/by-client-id/${id}` : `/api/v1/trade/agent/jobs/${id}`;
6640
6860
  return { url: `${base2}${path}`, init: { method: "GET", headers: jsonHeaders(apiKey) } };
6641
6861
  }
6642
6862
  case "candle_get_staking": {
@@ -6711,9 +6931,10 @@ function buildRequest(name, args, cfg) {
6711
6931
  }
6712
6932
  case "candle_get_profile_pnl": {
6713
6933
  const apiKey = requireApiKey2(cfg);
6714
- const { keyPrefix } = args;
6934
+ const { keyPrefix, sinceMs } = args;
6935
+ const query2 = sinceMs !== undefined ? `?sinceMs=${encodeURIComponent(String(sinceMs))}` : "";
6715
6936
  return {
6716
- url: `${base2}/api/v1/agent/keys/${encodeURIComponent(keyPrefix)}/pnl`,
6937
+ url: `${base2}/api/v1/agent/keys/${encodeURIComponent(keyPrefix)}/pnl${query2}`,
6717
6938
  init: { method: "GET", headers: jsonHeaders(apiKey) }
6718
6939
  };
6719
6940
  }
@@ -6726,13 +6947,40 @@ function buildRequest(name, args, cfg) {
6726
6947
  }
6727
6948
  case "candle_get_profile_trades": {
6728
6949
  const apiKey = requireApiKey2(cfg);
6729
- const { keyPrefix, limit } = args;
6730
- const query2 = limit === undefined ? "" : `?limit=${encodeURIComponent(String(limit))}`;
6950
+ const { keyPrefix, limit, since, until, cursor } = args;
6951
+ const params = new URLSearchParams;
6952
+ if (limit !== undefined)
6953
+ params.set("limit", String(limit));
6954
+ if (since !== undefined)
6955
+ params.set("since", since);
6956
+ if (until !== undefined)
6957
+ params.set("until", until);
6958
+ if (cursor !== undefined)
6959
+ params.set("cursor", cursor);
6731
6960
  return {
6732
- url: `${base2}/api/v1/agent/keys/${encodeURIComponent(keyPrefix)}/trades${query2}`,
6961
+ url: `${base2}/api/v1/agent/keys/${encodeURIComponent(keyPrefix)}/trades${params.size ? `?${params}` : ""}`,
6733
6962
  init: { method: "GET", headers: jsonHeaders(apiKey) }
6734
6963
  };
6735
6964
  }
6965
+ case "candle_get_paper_inventory": {
6966
+ return {
6967
+ url: `${base2}/api/v1/trade/agent/paper/inventory`,
6968
+ init: { method: "GET", headers: jsonHeaders(requireApiKey2(cfg)) }
6969
+ };
6970
+ }
6971
+ case "candle_get_profile_paper": {
6972
+ const { keyPrefix } = args;
6973
+ return {
6974
+ url: `${base2}/api/v1/agent/keys/${encodeURIComponent(keyPrefix)}/paper`,
6975
+ init: { method: "GET", headers: jsonHeaders(requireApiKey2(cfg)) }
6976
+ };
6977
+ }
6978
+ case "candle_get_account_pnl": {
6979
+ return {
6980
+ url: `${base2}/api/v1/agent/keys/pnl`,
6981
+ init: { method: "GET", headers: jsonHeaders(requireApiKey2(cfg)) }
6982
+ };
6983
+ }
6736
6984
  case "candle_transfer": {
6737
6985
  const apiKey = requireApiKey2(cfg);
6738
6986
  return {
@@ -6768,10 +7016,10 @@ async function callAndRelay(name, args, cfg, doFetch) {
6768
7016
  ...res.ok ? {} : { isError: true }
6769
7017
  };
6770
7018
  }
6771
- var OPERATION_NOT_FOUND_RULE = "No record exists under this id in the ledger `kind` asked. That does not prove the write did " + "not run: a write that is still starting has no record yet. If a write was sent with this id, " + "send the SAME request again with the SAME id. It returns the stored outcome, or reports the one " + "still running. It never executes a second time. A refusal of that resend (a conflict or a " + "limit) is an answer about the resend, not about the write: read candle_get_operation again " + "before reporting an outcome. Never send it under a new id: a new id is a second trade or " + "launch. Check `kind`: a swap id is not read here, so replay the swap with its `clientSwapId`.";
7019
+ var OPERATION_NOT_FOUND_RULE = "No record exists under this id in the ledger `kind` asked. That does not prove the write did " + "not run: a write that is still starting has no record yet. If a write was sent with this id, " + "send the SAME request again with the SAME id. It returns the stored outcome, or reports the one " + "still running. It never executes a second time. A refusal of that resend (a conflict or a " + "limit) is an answer about the resend, not about the write: read candle_get_operation again " + "before reporting an outcome. Never send it under a new id: a new id is a second trade, swap or " + "launch. Check `kind`: an id read under the wrong kind has no record there. A swap sent without " + "a `clientSwapId` has no record at all.";
6772
7020
  function operationNotFoundNext(clientId2, kind) {
6773
- const other = kind === "launch" ? "trade" : "launch";
6774
- return `No ${kind} record under id "${clientId2}" yet. That does not prove the write did not run: a ` + "write that is still starting has no record yet. If a write was sent with this id, send the " + `SAME request again with the SAME id ("${clientId2}"). It returns the stored outcome, or reports ` + "the one still running. It never executes a second time. A refusal of that resend (a conflict " + "or a limit) is an answer about the resend, not about the write: read candle_get_operation " + `again before reporting an outcome. Never send it under a new id: a new id is a second ${kind}. ` + `This read asked kind "${kind}": a ${other} id has no record there, and a swap id is not read by ` + "this tool at all, so replay the swap with its clientSwapId.";
7021
+ const others = ["trade", "swap", "launch"].filter((k) => k !== kind).join(" or ");
7022
+ return `No ${kind} record under id "${clientId2}" yet. That does not prove the write did not run: a ` + "write that is still starting has no record yet. If a write was sent with this id, send the " + `SAME request again with the SAME id ("${clientId2}"). It returns the stored outcome, or reports ` + "the one still running. It never executes a second time. A refusal of that resend (a conflict " + "or a limit) is an answer about the resend, not about the write: read candle_get_operation " + `again before reporting an outcome. Never send it under a new id: a new id is a second ${kind}. ` + `This read asked kind "${kind}": a ${others} id has no record there.`;
6775
7023
  }
6776
7024
  async function getOperation(args, cfg, doFetch) {
6777
7025
  const { url, init } = buildRequest("candle_get_operation", args, cfg);
@@ -6786,7 +7034,7 @@ async function getOperation(args, cfg, doFetch) {
6786
7034
  const code = body?.error?.code;
6787
7035
  if (code === "JOB_NOT_FOUND") {
6788
7036
  const { clientId: clientId2, kind } = args;
6789
- const next = operationNotFoundNext(clientId2, kind === "launch" ? "launch" : "trade");
7037
+ const next = operationNotFoundNext(clientId2, kind === "launch" || kind === "swap" ? kind : "trade");
6790
7038
  text = JSON.stringify({ ...body, outcome: "unknown", next });
6791
7039
  }
6792
7040
  }
@@ -6821,6 +7069,13 @@ var getMarketShape = {
6821
7069
  chain: z9.string().describe('"solana" or "hood"'),
6822
7070
  mint: z9.string().describe("Token mint (solana) or contract address (hood)")
6823
7071
  };
7072
+ var priceHistoryShape = {
7073
+ chain: z9.string().optional().describe('"solana" (the default). Hood is refused with the REST route that serves it'),
7074
+ mint: z9.string().describe("Token mint"),
7075
+ interval: z9.enum(["1m", "5m", "15m", "1h", "4h", "1d"]).optional().describe("Bar length. Default 1h"),
7076
+ limit: z9.number().int().min(1).max(500).optional().describe("How many bars, 1-500. Default 100"),
7077
+ to: z9.number().int().positive().optional().describe("Unix seconds: the newest bar opens at or before this. Default now; page back with the oldest bar's time")
7078
+ };
6824
7079
  var TOKEN_EVENTS_LIMIT_MESSAGE = "limit must be a whole number from 1 to 1000";
6825
7080
  var tokenEventsLimit = z9.preprocess((value) => {
6826
7081
  if (typeof value !== "string")
@@ -6899,8 +7154,8 @@ var getAgentPositionsShape = {
6899
7154
  kind: z9.enum(["spot", "perp"]).optional().describe("Only spot tokens or only Hyperliquid perps; omit for both")
6900
7155
  };
6901
7156
  var getOperationShape = {
6902
- clientId: z9.string().describe("The clientTradeId or clientLaunchId the write used, or that the tool echoed back to you"),
6903
- kind: z9.enum(["trade", "launch"]).describe("Which rail the id belongs to. Required: ids are caller-chosen strings with no shape to dispatch on")
7157
+ clientId: z9.string().describe("The clientTradeId, clientSwapId or clientLaunchId the write used, or that the tool echoed back to you"),
7158
+ kind: z9.enum(["trade", "swap", "launch"]).describe("Which rail the id belongs to. Required: ids are caller-chosen strings with no shape to dispatch on")
6904
7159
  };
6905
7160
  var vampCandidatesShape = {
6906
7161
  limit: z9.number().optional().describe("Entries to return, 1 to 50 (default 20)"),
@@ -6911,6 +7166,22 @@ var getFeesShape = {
6911
7166
  cursor: z9.string().optional().describe("The previous page's continueCursor, verbatim"),
6912
7167
  limit: z9.number().optional().describe("Tokens per page, 1 to 50 (default 20). Each one costs chain reads")
6913
7168
  };
7169
+ var atomicPayerShape = z9.union([
7170
+ z9.object({ type: z9.literal("main") }),
7171
+ z9.object({ type: z9.literal("linked"), linkedWalletId: z9.string().min(1) })
7172
+ ]);
7173
+ var atomicLaunchDryRunShape = (() => {
7174
+ const { buyAmount: _buyAmount, chain: _chain, ...launch } = launchTokenShape;
7175
+ return {
7176
+ ...launch,
7177
+ clientLaunchId: z9.string().optional().describe("Not used by a dry run; send the id you will launch with"),
7178
+ payer: atomicPayerShape.optional().describe("Who pays for the launch: {type:'main'} (default) or {type:'linked', linkedWalletId}"),
7179
+ firstBuys: z9.array(z9.object({
7180
+ payer: atomicPayerShape,
7181
+ amountRaw: z9.string().regex(/^[1-9][0-9]*$/).describe("Quote-asset raw units")
7182
+ })).min(1).max(4).describe("One to four first buys, each priced on the curve this launch would deploy")
7183
+ };
7184
+ })();
6914
7185
  var claimFeesShape = {
6915
7186
  mint: z9.string().describe("The launched token: a Solana mint or a Hood token address, as candle_get_fees lists it"),
6916
7187
  dryRun: z9.boolean().optional().describe("Check and read only. Sends nothing and writes nothing. Use it first to see what would be claimed")
@@ -6922,12 +7193,17 @@ var profileWalletsShape = {
6922
7193
  keyPrefix: z9.string().describe("The profile's API key prefix, as listed by candle keys list or in the dashboard")
6923
7194
  };
6924
7195
  var profilePnlShape = {
6925
- keyPrefix: z9.string().describe("The profile's API key prefix")
7196
+ keyPrefix: z9.string().describe("The profile's API key prefix"),
7197
+ sinceMs: z9.number().int().nonnegative().optional().describe("Unix time in milliseconds. Adds a `window` block: realized and open mark changes since then, including older holdings. Buy and sell fees are charged once. Read complete and incompleteReasons; windowedUsd is null when incomplete. The all-time `pnl` is unchanged.")
6926
7198
  };
6927
7199
  var profileTradesShape = {
6928
7200
  keyPrefix: z9.string().describe("The profile's API key prefix"),
6929
- limit: z9.number().optional().describe("How many of the most recent trades to return. Default 200, max 1000.")
7201
+ limit: z9.number().optional().describe("How many of the most recent trades to return. Default 200, max 1000."),
7202
+ since: z9.string().optional().describe("Inclusive lower bound on the trade's createdAt: an ISO 8601 time or epoch milliseconds."),
7203
+ until: z9.string().optional().describe("Exclusive upper bound on createdAt, in the same forms as `since`."),
7204
+ cursor: z9.string().optional().describe("The `nextCursor` of the previous page, to continue the same window.")
6930
7205
  };
7206
+ var noArgsShape = {};
6931
7207
  var setProfileWalletsShape = {
6932
7208
  keyPrefix: z9.string().describe("The profile's API key prefix"),
6933
7209
  walletIds: z9.array(z9.string()).describe("The linked-wallet ids the profile may spend from -- ids, not addresses. This REPLACES the " + "whole set: any wallet omitted loses access. An empty array assigns none.")
@@ -6945,7 +7221,7 @@ var tradeShape = {
6945
7221
  side: z9.enum(["buy", "sell"]),
6946
7222
  amount: z9.string().optional().describe("Decimal amount. Buys: how much of THIS TOKEN'S OWN quote asset to spend (SOL for a " + 'SOL-launched token, USDC for a USDC-quoted one, and so on: e.g. "0.5"). Sells: how many ' + "TOKENS to sell. Pass exactly one of amount or percent."),
6947
7223
  percent: z9.number().optional().describe("Sells only: sell this percent (integer 1-100) of the holding. Live trades size against the " + "embedded wallet. Paper trades (`paper: true`) size against this key's paper inventory -- " + "the position a previous paper buy credited -- because paper never moves the live wallet."),
6948
- quoteAsset: z9.string().optional().describe('What the wallet spends on a buy or receives on a sell: "sol", "usdc" or "cndl" on Solana, ' + '"eth" or "usdg" on Hood. Echoing the `quoteAsset` a quote (POST /api/v1/trade/agent/quote) returned is safe. On Solana it applies only ' + "to an arbitrary mint Candle never launched (a buy needs Pro or Max; a sell works on any plan) and is ignored for a Candle token, " + "whose quote comes from the token itself. On Hood it is the settlement asset of a DEX " + "trade; a USDG buy adds an approval transaction an ETH buy does not. It is not the route: " + "the cheapest path to the asset is chosen separately. Defaults to sol / ETH settlement."),
7224
+ quoteAsset: z9.string().optional().describe('What the wallet spends on a buy or receives on a sell: "sol", "usdc" or "cndl" on Solana, ' + '"eth" or "usdg" on Hood. Echoing the `quoteAsset` a quote (POST /api/v1/trade/agent/quote) returned is safe. On Solana it applies only ' + "to an arbitrary mint Candle never launched (a buy or sell works on any plan) and is ignored for a Candle token, " + "whose quote comes from the token itself. On Hood it is the settlement asset of a DEX " + "trade; a USDG buy adds an approval transaction an ETH buy does not. It is not the route: " + "the cheapest path to the asset is chosen separately. Defaults to sol / ETH settlement."),
6949
7225
  maxSlippageBps: z9.number().int().min(0).max(5000).optional().describe("Slippage bound in bps, 0-5000 (50%). Server defaults to 100 (1%)"),
6950
7226
  clientTradeId: z9.string().optional().describe("Idempotency key. Auto-generated when omitted and echoed in the result. Retrying with the " + "SAME id is safe (idempotent replay); a new id is a SECOND trade."),
6951
7227
  paper: z9.preprocess((value) => value === "true" || value === 1 || value === "1" ? true : value === "false" || value === 0 || value === "0" ? false : value, z9.boolean().optional().describe("Rehearse instead of trading. The request passes every admission rule a live trade passes " + "-- the same planner, spend gate, key cap and loss limits -- and records the quote, but " + "nothing is ever broadcast and no funds move. Use it to check that a strategy is admitted " + "before risking anything on it. A paper fill is optimistic by construction: it books the " + "quoted price, so the gap between a paper arm and a live one IS the execution cost. " + "A sell of a mint this key already paper-bought also closes that paper book when the " + "live wallet is empty, even if this flag is omitted."))
@@ -7131,9 +7407,7 @@ function registerToolSubset(server, options) {
7131
7407
  }, async (args) => callAndRelay("candle_get_market", args, getConfig(), doFetch));
7132
7408
  register("candle_token_forensics", {
7133
7409
  title: "Token forensics",
7134
- description: `Gate a buy before making it: who launched it (resolved on-chain; pump.fun's shared updateAuthority is never the developer; when no developer is on chain, deployer.attribution names the launchpad or issuer instead, e.g. launched via stonk.fun or issued by xStocks), 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.
7135
-
7136
- 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, and any Solana mint Jupiter's index knows, now answers with a partial report instead. On Hood, a token Candle did not launch names its launch account (deployer.method cvc_launch_account) for pons.family and pools.trade launches, with no record of earlier coins; other Hood launchpads return no developer.`,
7410
+ description: "Gate a buy before making it: who launched it (resolved on-chain; pump.fun's shared updateAuthority is never the developer; when no developer is on chain, deployer.attribution names the launchpad or issuer instead, e.g. launched via stonk.fun or issued by xStocks), 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. deployer.priorLaunches is counted (pump.fun curves for a curve creator; record.count says the source); deployer.launcher is the wallet that sent the launch, often a launch tool's signer, never the developer; launch.deployerLaunches is that wallet's inclusive informational count, never a warning. Every measurement carries a coverage note -- 'unavailable' is not 'clean'. No key needed.\n\nMARKET_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, and any Solana mint Jupiter's index knows, now answers with a partial report instead. On Hood, a token Candle did not launch names its launch account (deployer.method cvc_launch_account) for pons.family and pools.trade launches, with no record of earlier coins; other Hood launchpads return no developer.",
7137
7411
  inputSchema: tokenForensicsShape
7138
7412
  }, async (args) => callAndRelay("candle_token_forensics", args, getConfig(), doFetch));
7139
7413
  register("candle_token_clusters", {
@@ -7168,6 +7442,11 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
7168
7442
  inputSchema: tokenHistoryShape,
7169
7443
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
7170
7444
  }, async (args) => callAndRelay("candle_token_history", args, getConfig(), doFetch));
7445
+ register("candle_get_price_history", {
7446
+ title: "Get price history",
7447
+ description: "One Solana token's price history: OHLC bars in USD per token, oldest first, the same bars as the token's chart on Candle. interval 1m, 5m, 15m, 1h, 4h or 1d (default 1h); limit 1-500 (default 100); to is unix seconds (default now). The answer is up to limit bars at or before to (limit is a maximum). Coverage is bounded by source: Candle's own bars come from the latest 1,500 active minutes and a bar whose start cannot be established is left out; the indexer is searched only (limit + 1) x interval seconds back from to. A quiet token can therefore return fewer bars than limit, and a minute with no trades has no bar, so read each bar's time rather than assuming a gapless series. source says which answered: candle (Candle's own curve bars, volumeUnit usd) or birdeye (the indexer's, volumeUnit token). An empty candles list means no bars in the searched coverage, not no history and not a zero price; page back with to. No key needed and none is sent. Hood is refused (400 VALIDATION_FAILED naming GET /api/v1/hood/token/:address/candles). For the current price and 5m/1h/24h change, read candle_get_market.",
7448
+ inputSchema: priceHistoryShape
7449
+ }, async (args) => callAndRelay("candle_get_price_history", args, getConfig(), doFetch));
7171
7450
  register("candle_token_events", {
7172
7451
  title: "Token events",
7173
7452
  description: `One token's event list, newest first: every signal (pattern and summary, new_ticker included), every callout, and every tracked-wallet trade (buys and sells, W or L on a sell whose outcome is known), plus trades Candle's own agents and users made. Each wallet carries its display name or short address, its kind (dev, kol, smart, agent, tracked) and its public group names. No key needed; none is sent even when one is configured, so group names follow the public tier. Off on a deployment until it switches TOKEN_EVENTS on: off, the call answers 503 TOKEN_EVENTS_DISABLED (retryable: false) and that is the server's answer, not a broken tool.
@@ -7265,13 +7544,13 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
7265
7544
  });
7266
7545
  register("candle_get_operation", {
7267
7546
  title: "What happened to a write",
7268
- description: "Look up a trade or launch by the id its write used, and find out whether it landed. " + `Reads only; moves nothing.
7547
+ description: "Look up a trade, swap or launch by the id its write used, and find out whether it landed. " + `Reads only; moves nothing.
7269
7548
 
7270
7549
  ` + "Call this after a timeout, after a restart, or any time you hold an id and do not know " + `the outcome. Do NOT re-send the write to find out.
7271
7550
 
7272
- ` + `Four answers, four different next moves:
7551
+ ` + `The answers, and the next move for each:
7273
7552
  ` + "- `confirmed`/`failed`: it is over. `signature` proves the first, `errorCode` explains " + `the second.
7274
- ` + "- `built`/`submitted`: still in flight. Wait and ask again.\n" + `- 404 (\`JOB_NOT_FOUND\`): unknown, not an outcome. ${OPERATION_NOT_FOUND_RULE} The result ` + 'says the same in `outcome: "unknown"` and `next`.\n\n' + "Amounts come back RAW, deliberately: the answer you need after a timeout is the outcome, " + "and you already know what you asked for.",
7553
+ ` + "- `built`/`submitted`: still in flight. Wait and ask again.\n" + "- kind `swap` (candle_swap's `clientSwapId`) answers `job.outcome` instead: `in_flight` " + "(ask again), `confirmed` (`job.hashes`), `failed` (refused before any broadcast; the SAME " + "request with the SAME id starts over), `partial` (`hashes[0]` landed, " + "a later leg did not) or `indeterminate` (broadcast, outcome unknown; check " + "`error.message`). `partial` and `indeterminate` are final for that id.\n" + `- 404 (\`JOB_NOT_FOUND\`): unknown, not an outcome. ${OPERATION_NOT_FOUND_RULE} The result ` + 'says the same in `outcome: "unknown"` and `next`.\n\n' + "Amounts come back RAW, deliberately: the answer you need after a timeout is the outcome, " + "and you already know what you asked for.",
7275
7554
  inputSchema: getOperationShape
7276
7555
  }, async (args) => getOperation(args, getConfig(), doFetch));
7277
7556
  register("candle_get_staking", {
@@ -7303,12 +7582,12 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
7303
7582
  }, async (args) => callAndRelay("candle_claim_staking_reward", args, getConfig(), doFetch));
7304
7583
  register("candle_get_fees", {
7305
7584
  title: "Read the creator fees this account's tokens can pay",
7306
- description: "The tokens launched by the account's own wallets, with what each could pay now in creator fees " + "(raw units from direct chain reads) and `claimable: true` or the `reason` it is not. Reads only; " + "moves nothing. It returns no earned or paid-out totals. An unreadable token is marked, never " + "shown as zero. Paged: pass `continueCursor` back as `cursor`.",
7585
+ description: "The tokens launched by the account's own wallets, with what each could pay now in creator fees " + "(raw units from direct chain reads) and a `reason` saying who can claim: `claimable_by_key` on a " + "claimable Solana row, and otherwise why this key cannot (including `claimable_via_web_session` " + "and `fee_wallet_designated`, which name whose authority collects). Reads only; " + "moves nothing. It returns no earned or paid-out totals. An unreadable token is marked, never " + "shown as zero. Paged: pass `continueCursor` back as `cursor`.",
7307
7586
  inputSchema: getFeesShape
7308
7587
  }, async (args) => callAndRelay("candle_get_fees", args, getConfig(), doFetch));
7309
7588
  register("candle_claim_fees", {
7310
7589
  title: "Claim a token's creator fees",
7311
- description: "Collect one token's creator fees and pay them to the wallet fixed at launch. There is no destination " + "argument and the tool cannot send funds anywhere else. Candle signs with the embedded wallet, which " + "pays the network fee. Needs launch:write. " + "Call it with dryRun: true first; a real claim moves real funds into the recipient wallet, which may be " + "the wallet this agent trades from. Not idempotent by id: if the result is lost, read candle_get_fees " + "before claiming again.",
7590
+ description: "Collect one token's creator fees and pay them to the wallet fixed at launch. There is no destination " + "argument and the tool cannot send funds anywhere else. Candle signs with the embedded wallet, which " + "pays the network fee. A fee recipient that is a TEE wallet is refused here: claim it with the candle CLI " + "(`candle fees claim`). The dry run reports the same refusal (`claimable: false`, `reason: " + "wallet_not_signable`) and counts only what this tool could claim. Needs launch:write. " + "Call it with dryRun: true first; a real claim moves real funds into the recipient wallet, which may be " + "the wallet this agent trades from. Not idempotent by id: if the result is lost, read candle_get_fees " + "before claiming again.",
7312
7591
  inputSchema: claimFeesShape
7313
7592
  }, async (args) => callAndRelay("candle_claim_fees", args, getConfig(), doFetch));
7314
7593
  register("candle_get_wallets", {
@@ -7328,14 +7607,29 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
7328
7607
  }, async (args) => callAndRelay("candle_set_profile_wallets", args, getConfig(), doFetch));
7329
7608
  register("candle_get_profile_pnl", {
7330
7609
  title: "Read an agent profile's P&L",
7331
- description: "Optional `board` beside `pnl` carries profileId, creatorFeesUsd, feeClaimsUnvalued, feeClaimsPartial and " + "totalPnlUsd (absent before a board entry exists): the all-time public board total, including fee claims " + "at their value when claimed, credited to the agent that launched the token when the claiming account " + "is the launching account. `feesUsd` is fees paid; `creatorFeesUsd` is fees earned. Check feeClaimsPartial " + "and feeClaimsUnvalued. `board` is absent on an older backend or a failed board read; it does not change " + "ledger pnl.totalUsd. Staking rewards are held at zero cost; claim value is informational, never added again. " + "This profile's share of the account's P&L: realized profit on the sales it made and on positions its buy opened that closed through a sale made by hand in the web app or an exit outside Candle (the fee and trade count stay under Manual), the Candle fees " + "charged against it, and the positions that belong to it (the wallet's bound key, else the key " + "whose buy opened them) with their cost basis, each MARKED at Candle's current price where one " + "exists: `markPriceUsd`, `marketValueUsd` and `unrealizedUsd` per position, and `unrealizedUsd` " + "overall. A position with no price is counted in `unmarkedPositions` and left out of unrealized, " + "never valued at zero; `oldestMarkAt` says how old the marks are. Deposits and withdrawals are " + "excluded: funding a wallet is not profit. Check `unvalued` and `truncated` before quoting the " + "number; they mean the total is partial. The figures come from the account's one P&L read, the " + "same as the console's, to the cent: `pnl.totalUsd` (realized net plus unrealized) is the " + "console's P&L, plus the Hyperliquid net when `perpsInTotal` is true. Positions live in wallets: each wallet is its own average-cost pool, the total " + "is the sum of `pnl.byWallet` (one row per wallet, main or linked, with the linked wallet's " + "label; `unknown:solana` or `unknown:hood` is the row for fills no record places in a wallet), " + "and one token held in two wallets is two positions, each with its `wallet`. A recorded move " + "between the account's wallets carries its cost and realizes nothing. Moved-in positions and " + "`closedPositions` carry additive `transferredIn`, `basisSource` and `basisComplete`; " + "`basisSource` says whether cost comes from Candle history, on-chain trades or the arrival price; " + "`zero-cost` (with `zeroCostQuantity`) is Solana CNDL with no purchase found, carried at $0. " + "Public `realizedFromTransfersUsd` and `unrealizedFromTransfersUsd` are shares already included in " + "total P&L and rank; do not add them again. Moved-in closes count toward W/L and eligibility. Each position carries " + "`agent` (the key it belongs to) and `dust` when it is worth under one cent; dust stays listed " + "and counted, and `openPositionsExDust` leaves it out. `closed` gives the closed positions as " + "`madeUsd` + `lostUsd` + `partialSellsUsd` = `realizedNetUsd`, with `wins` and `losses`. " + "`lookback` and `truncated` are the account's activity bound, and `tradesConsidered` counts this " + "key's ledger fills. Since 2026-10-02 the total is the sum of wallets rather than one pool across " + "them, so realized figures can differ from earlier reads; a server that predates it omits " + "`totalUsd`, `closed`, `openPositionsExDust`, `wallet`, `agent` and `dust`. " + "Every open position and every `byWallet` row (and its positions) carries `chain` ('solana' or " + "'hood'), and `pnl.byChain` has the total's figures over each chain's fills alone, both chains " + "always present, with `openPositions` there a count (the positions are in `pnl.openPositions`, by " + "`chain`). The two chains sum to the total. A " + "server that predates per-chain P&L omits `chain` and `byChain`. " + PROFILE_PNL_HYPERLIQUID + " Reads only.",
7610
+ description: "Optional `board` beside `pnl` carries profileId, creatorFeesUsd, feeClaimsUnvalued, feeClaimsPartial and " + "totalPnlUsd (absent before a board entry exists): the all-time public board total, including fee claims " + "at their value when claimed, credited to the agent that launched the token when the claiming account " + "is the launching account. `feesUsd` is fees paid; `creatorFeesUsd` is fees earned. Check feeClaimsPartial " + "and feeClaimsUnvalued. `board` is absent on an older backend or a failed board read; it does not change " + "ledger pnl.totalUsd. Staking rewards are held at zero cost; claim value is informational, never added again. " + "This profile's share of the account's P&L: realized profit on the sales it made and on positions its buy opened that closed through a sale made by hand in the web app or an exit outside Candle (the fee and trade count stay under Manual), the Candle fees " + "charged against it, and the positions that belong to it (the wallet's bound key, else the key " + "whose buy opened them) with their cost basis, each MARKED at Candle's current price where one " + "exists: `markPriceUsd`, `marketValueUsd` and `unrealizedUsd` per position, and `unrealizedUsd` " + "overall. A position with no price is counted in `unmarkedPositions` and left out of unrealized, " + "never valued at zero; `oldestMarkAt` says how old the marks are. Deposits and withdrawals are " + "excluded: funding a wallet is not profit. Check `unvalued` and `truncated` before quoting the " + "number; they mean the total is partial. The figures come from the account's one P&L read, the " + "same as the console's, to the cent: `pnl.totalUsd` (realized net plus unrealized) is the " + "console's P&L, plus the Hyperliquid net when `perpsInTotal` is true. Positions live in wallets: each wallet is its own average-cost pool, the total " + "is the sum of `pnl.byWallet` (one row per wallet, main or linked, with the linked wallet's " + "label; `unknown:solana` or `unknown:hood` is the row for fills no record places in a wallet), " + "and one token held in two wallets is two positions, each with its `wallet`. A recorded move " + "between the account's wallets carries its cost and realizes nothing. Moved-in positions and " + "`closedPositions` carry additive `transferredIn`, `basisSource` and `basisComplete`; " + "`basisSource` says whether cost comes from Candle history, on-chain trades or the arrival price; " + "`zero-cost` (with `zeroCostQuantity`) is Solana CNDL with no purchase found, carried at $0. " + "Public `realizedFromTransfersUsd` and `unrealizedFromTransfersUsd` are shares already included in " + "total P&L and rank; do not add them again. Moved-in closes count toward W/L and eligibility. Each position carries " + "`agent` (the key it belongs to) and `dust` when it is worth under one cent; dust stays listed " + "and counted, and `openPositionsExDust` leaves it out. `closed` gives the closed positions as " + "`madeUsd` + `lostUsd` + `partialSellsUsd` = `realizedNetUsd`, with `wins` and `losses`. " + "`lookback` and `truncated` are the account's activity bound, and `tradesConsidered` counts this " + "key's ledger fills. Since 2026-10-02 the total is the sum of wallets rather than one pool across " + "them, so realized figures can differ from earlier reads; a server that predates it omits " + "`totalUsd`, `closed`, `openPositionsExDust`, `wallet`, `agent` and `dust`. " + "Every open position and every `byWallet` row (and its positions) carries `chain` ('solana' or " + "'hood'), and `pnl.byChain` has the total's figures over each chain's fills alone, both chains " + "always present, with `openPositions` there a count (the positions are in `pnl.openPositions`, by " + "`chain`). The two chains sum to the total. A " + "server that predates per-chain P&L omits `chain` and `byChain`. " + PROFILE_PNL_HYPERLIQUID + " Pass `sinceMs` for a `window` block beside all-time `pnl`: existing holdings start at the last " + "completed historical minute candle's close, returned in `boundary.marks` with its timestamp. " + "Sales and open positions use that window basis, with later buys at trade cost. `feesUsd` includes " + "both buy and sell fees once, separately from basis. `windowedUsd` is " + "realizedNetUsd plus unrealizedUsd. Missing boundary/current marks or incomplete history return " + "complete:false, incompleteReasons and windowedUsd:null; component sums are partial then. " + "This is spot trading P&L; LP, perps and fee income remain separate. Reads only.",
7332
7611
  inputSchema: profilePnlShape
7333
7612
  }, async (args) => callAndRelay("candle_get_profile_pnl", args, getConfig(), doFetch));
7334
7613
  register("candle_get_profile_trades", {
7335
7614
  title: "Read an agent profile's trade history",
7336
- description: "Orders, actual fills, fees, timestamps and transaction hashes for this profile. Includes FAILED " + "trades, with an errorCode saying why each did not go through, so this answers 'what happened to " + "my order' as well as 'what did I trade'. Reads only; moves nothing.",
7615
+ description: "Orders, actual fills, fees, timestamps and transaction hashes for this profile. Page by time with " + "`since`, `until` and `cursor` (continue with the response's `nextCursor`). Includes FAILED " + "trades, with an errorCode saying why each did not go through, so this answers 'what happened to " + "my order' as well as 'what did I trade'. Reads only; moves nothing.",
7337
7616
  inputSchema: profileTradesShape
7338
7617
  }, async (args) => callAndRelay("candle_get_profile_trades", args, getConfig(), doFetch));
7618
+ register("candle_get_paper_inventory", {
7619
+ title: "Read this key's paper inventory",
7620
+ description: "This key's PAPER book in raw units: the positions that paper buys have credited and a paper " + "percent sell sizes itself from. Derived from admitted quotes; no money moved and the live " + "wallet is untouched. Never mix it with live holdings (candle_get_portfolio). Fails retryably " + "when the book cannot be read completely. Needs swap:write on the key. Reads only.",
7621
+ inputSchema: noArgsShape
7622
+ }, async (args) => callAndRelay("candle_get_paper_inventory", args, getConfig(), doFetch));
7623
+ register("candle_get_profile_paper", {
7624
+ title: "Read an agent profile's paper P&L",
7625
+ description: "One profile's PAPER results: the same accounting as candle_get_profile_pnl, from trades that " + "never happened. `pnl` is the paper P&L and `trades` its 50 most recent paper trades. Carries " + '`basis: "quote"`: the figures come from quotes at admission and are optimistic by the ' + "slippage and fees real execution pays. Never add them to live P&L. Reads only.",
7626
+ inputSchema: profilePnlShape
7627
+ }, async (args) => callAndRelay("candle_get_profile_paper", args, getConfig(), doFetch));
7628
+ register("candle_get_account_pnl", {
7629
+ title: "Read P&L across all of an account's agent keys",
7630
+ description: "The account's P&L split by agent profile in one read, as `candle pnl` without `--profile` " + "does: one row per profile with its ledger figures and, where it has a board entry, `board` " + "(all-time public total with creator-fee claims credited to the launching agent, so a profile's " + "board total can differ from its per-key ledger P&L). A wallet shared by several keys books a " + "round to its bound key, else the key whose buy opened it. Use candle_get_profile_pnl for one " + "profile's positions. Reads only.",
7631
+ inputSchema: noArgsShape
7632
+ }, async (args) => callAndRelay("candle_get_account_pnl", args, getConfig(), doFetch));
7339
7633
  register("candle_get_portfolio", {
7340
7634
  title: "Read what the account holds, on Solana and Hood",
7341
7635
  description: "Balances and prices for the wallets Candle already knows on this account, on both chains: the " + "embedded wallet and every TEE wallet. Solana wallets are in `embedded` and `tee` (SOL as raw " + "`lamports`, tokens as raw `amountRaw` with `decimals`). Hood wallets are in `hood.embedded` and " + "`hood.tee` (ETH as raw `wei`, ERC-20s including USDG), never in the top-level arrays. Every " + "wallet and holding carries `chain`. Prices are in `prices`: a Solana mint under its own " + "address, Hood ETH under `hood:native`, and a Hood token under `hood:<contract lowercased>`, so " + "lowercase the address before looking it up. An unpriced entry is `priceUsd: null`, never zero; " + "a Hood one says why in `unpricedReason` (no-market-row, unusable-price, stale-mark, " + "source-unavailable), and `hood.unpriced` / `hood.unpricedByReason` count them. A Hood token " + "mark older than six hours is not used, so many Hood tokens read unpriced. A wallet whose read " + "failed has null balances, never zero, and is listed in `unavailable` (Solana) or " + "`hood.unavailable`; check `complete` before quoting a total. `walletsComplete` is false only " + "when the wallet list itself was cut off, and is absent on a server that predates it: then " + "`complete: false` may be a cut-off list or a failed read. Vault and external wallets are " + "not included: Candle does not know their addresses (the Candle CLI's `candle portfolio` reads " + "them over your own RPC). Needs a key with the account:read scope; without it the answer is " + "SCOPE_MISSING. A server that predates Hood in the portfolio omits `hood` and `chain`. Reads " + "only; moves nothing." + (options.localHyperliquid ? " This local server adds `hyperliquid`: each Hood TEE wallet's Hyperliquid account (account value, " + "unrealized P&L, positions, risk level at the default thresholds) read live from Hyperliquid, with a " + "subtotal. It is not in the ledger and is never part of any total. `read: false` with a reason, or " + "entries in `unread`, means those accounts are unknown, not empty." : ""),
@@ -7357,7 +7651,7 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
7357
7651
  });
7358
7652
  register("candle_execution_status", {
7359
7653
  title: "Can this key execute right now",
7360
- description: "One call before trading: the embedded wallets to spend from, the tier that decides what " + "may be traded, and this key's own spend limits. Reads only; moves nothing. Call it when " + "a run starts, or after an authorization error, rather than inferring readiness from a " + "failed trade. If a read could not be completed the tool says which one and does NOT " + "claim the account is unready: an unreachable endpoint and a missing tier are different " + "problems with different fixes.",
7654
+ description: "One call before trading: the embedded wallets to spend from, the tier that decides what " + "may be traded, this key's own spend limits, and its loss limits and stop state (risk: " + "lossLimits, state, stopped, tripped). A stopped key reports ready: false and a top-level " + "stopped naming the bound that broke; every trade answers RISK_LIMIT_REACHED until an " + "account owner resumes it, so stop trading and say why rather than retrying. Any scope " + "can read its own. Reads only; moves nothing. Call it when " + "a run starts, or after an authorization error, rather than inferring readiness from a " + "failed trade. If a read could not be completed the tool says which one and does NOT " + "claim the account is unready: an unreachable endpoint and a missing tier are different " + "problems with different fixes.",
7361
7655
  inputSchema: {}
7362
7656
  }, async () => {
7363
7657
  const result = await executionStatus(getConfig(), doFetch);
@@ -7404,7 +7698,7 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
7404
7698
 
7405
7699
  ` + "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.
7406
7700
 
7407
- ` + "Plans: every plan can buy and sell Candle-launched tokens and base assets, and SELL a token it holds that " + "Candle did not launch. BUYING such a token needs Pro or Max (TIER_REQUIRED otherwise). Each trade pays the " + `plan's agent fee on top (feeBps in candle_get_plans).
7701
+ ` + "Plans: every plan can buy and sell Candle-launched tokens, base assets, and a token " + "Candle did not launch. Each trade pays the " + `plan's agent fee on top (feeBps in candle_get_plans).
7408
7702
 
7409
7703
  ` + "Pass `paper: true` to rehearse: every admission rule runs and the quote is recorded, but " + "nothing broadcasts and no funds move. A paper buy credits this key's paper inventory, " + "including for external Solana mints routed through Jupiter. A later sell by amount or " + "percent closes that book without reading the live wallet and without MARKET_NOT_FOUND " + "-- including when `paper` is omitted on the exit, as long as the paper position exists. " + "Do this before the first live trade of a new strategy, and whenever you are unsure a " + `trade would be admitted at all.
7410
7704
 
@@ -7416,6 +7710,12 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
7416
7710
  const result = await executeTrade(args, getConfig(), doFetch);
7417
7711
  return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
7418
7712
  });
7713
+ register("candle_atomic_launch_dry_run", {
7714
+ title: "Dry-run an atomic Solana launch",
7715
+ description: "Check a Solana launch bundled with first buys and price each buy, without building anything. Runs the " + "same checks as the atomic build, in the same order (payers, Believer check, fee and spend limits, the " + "key's transaction cap), then returns the launch preview (curveTerms) and one fill per first buy: the " + "tokens it would receive and the floor the build would sign it at. Reads only; moves nothing, reserves " + "nothing, and the clientLaunchId stays unused. It does not check balances, the daily cap, or the " + "transaction's size, so the real launch can still refuse. Needs launch:write, swap:write and Pro or Max.",
7716
+ inputSchema: atomicLaunchDryRunShape,
7717
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
7718
+ }, async (args) => callAndRelay("candle_atomic_launch_dry_run", args, getConfig(), doFetch));
7419
7719
  register("candle_launch_and_seed", {
7420
7720
  title: "Launch a token and seed it",
7421
7721
  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. " + LAUNCH_SHARED_SENTENCES + (imageDir ? IMAGE_PATH_DISCLOSURE : ""),
@@ -7622,8 +7922,9 @@ async function registerEligibleProfileTool(server, env = process.env, doFetch =
7622
7922
  // src/server.ts
7623
7923
  var INSTRUCTIONS = `Candle is a trading and token-launch rail for agents. You hold a scoped API key, never a private key; signing and funding stay with the key owner's wallet.
7624
7924
 
7625
- START HERE — fourteen tools need NO credential. Call these first to confirm the server is wired before asking anyone for anything:
7925
+ START HERE — fifteen tools need NO credential. Call these first to confirm the server is wired before asking anyone for anything:
7626
7926
  candle_get_market price, market cap, volume, curve state for one token
7927
+ candle_get_price_history one Solana token's OHLC bars in USD (1m to 1d, up to 500, page back with to); source and volumeUnit say whose bars
7627
7928
  candle_get_feed the roster: hot streak, new pairs, graduated, final stretch, blue chip (final stretch is refused where it is not switched on; use another bucket)
7628
7929
  candle_token_page_about public About summary, switches default off
7629
7930
  candle_token_page_burns completed CNDL burn history
@@ -7638,7 +7939,7 @@ START HERE — fourteen tools need NO credential. Call these first to confirm th
7638
7939
  candle_token_events one token's signals, callouts and tracked-wallet trades, newest first, W or L on sells. kind filters the page on this server; page with nextCursor/before until null. Off until the deployment switches it on (503 TOKEN_EVENTS_DISABLED)
7639
7940
  candle_token_card one token's card data: market, all-time high since launch, safety and same-name facts, tracked wallets in the last hour, each block with its source; null means no source, not zero. Off until the deployment switches it on (503 TOKEN_CARD_DISABLED)
7640
7941
  candle_resolve_token a ticker or partial name -> mint address + chain
7641
- candle_token_forensics call this before quoting or buying. Returns the on-chain developer (never a launchpad shared authority; deployer.attribution names the launchpad or issuer when no developer is on chain), 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
7942
+ candle_token_forensics call this before quoting or buying. Returns the on-chain developer (never a launchpad shared authority; deployer.attribution names the launchpad or issuer when no developer is on chain), 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. deployer.priorLaunches is counted (pump.fun curves for a curve creator; record.count says the source); deployer.launcher is the wallet that sent the launch, often a launch tool's signer, never the developer; launch.deployerLaunches is that wallet's inclusive informational count, never a warning
7642
7943
  candle_token_clusters optional third-party linked-wallet estimates for a Solana token, always a result: usually status unavailable with a reason, which is not zero clustered supply or clearance. Report its caveat with any figure; keep it separate from forensics
7643
7944
  candle_get_agent_profile your own tier, caps and verified activity
7644
7945
  candle_list_agents the agent directory: every published agent with its strategy, declared models, all-time P&L, trades and launches; sort newest, active or pnl
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@candledottv/mcp",
3
- "version": "0.10.28",
3
+ "version": "0.10.30",
4
4
  "description": "MCP server for the Candle agent rail: launch tokens, read markets, report activity",
5
5
  "type": "module",
6
6
  "license": "MIT",