@candledottv/cli 0.11.22 → 0.11.24

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 +14 -2
  2. package/dist/index.js +394 -39
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @candledottv/cli
2
2
 
3
- The Candle CLI (current version 0.11.22): authorize a device from your browser, keep your own keys
3
+ The Candle CLI (current version 0.11.24): authorize a device from your browser, keep your own keys
4
4
  in an encrypted vault on this machine, hand an agent a TEE wallet it can trade, and manage API
5
5
  keys, wallets, and setup health from the terminal. Zero runtime dependencies; the whole thing is
6
6
  one self-contained `dist/index.js` that runs under plain Node.
@@ -122,7 +122,8 @@ emergency procedure, rebinding, trusted wallets and API key access levels are al
122
122
  | `candle swap <from> <to> --amount <n>\|--percent <n> --wallet <tee> [--to <tee>] [--wait]` | Quote, confirm and swap through the TEE wallet's bound key; the first buy after a launch is this command, unless `candle launch --buy` made it. The assets decide the chain (ETH, USDG or a 0x token is Hood; SOL, USDC, CNDL or a mint is Solana). SOL or USDC to ETH or USDG, or back, is a bridge through Relay into this key's own TEE wallet on the other chain (`--to` names it when there are several); `--wait` follows it for up to ten minutes. |
123
123
  | `candle swap status <id> [--kind trade\|swap\|launch] [--wait]` | Read an operation without resending it; `--wait` follows a bridge until it ends. |
124
124
  | `candle transfer --to <address\|wallet name\|vault> --asset <SOL\|USDC\|CNDL\|ETH\|USDG>\|--mint <mint>\|--token <0x...> --amount <decimal\|max> [--wallet <tee>] [--rpc-url <url>] [--yes]` | Move funds out of a TEE wallet through its bound Withdraw key, to the wallet's own pinned vault (`--to vault`, any token, `max` allowed, one SOL transfer at most the server's backstop ceiling, 50 SOL by default) or to another of the account's wallets you linked while signed in or marked trusted (a base asset, counted against the key's caps, which must include a per-asset cap and a USD `--tx-limit`). The asset decides the chain: ETH, USDG or `--token` moves from a Hood TEE wallet. The destination and its kind are shown before you confirm; Candle builds, the relay signs, Candle broadcasts. |
125
- | `candle launch --name <name> --symbol <symbol> --image-url <url> --wallet <tee> [--buy <amount>] [--quote-asset <asset>] [--dex-version v3\|v4]` | Create a token from a TEE wallet. Without `--buy` it makes no first buy. `--buy <amount>` adds one from the same wallet, in whole units of the quote asset, reserved against the key's spend window before signing: in the launch transaction on Solana, as a last leg on Hood. It needs the server's `TEE_LAUNCH_BUYS_ENABLED`, which is off by default; while it is off Candle refuses the buy and nothing is built. Needs `launch:write`, `allowLaunch` turned on for the wallet by the account owner, and the Pro or Max plan. On Free, launch from the embedded wallet instead, with an optional dev buy (same transaction on Solana; best-effort follow-up on Hood): the MCP tool `candle_launch_and_seed` or the SDK's `launch()`. The wallet decides the chain: a Hood TEE wallet launches on Hood and needs `--dex-version`. `--quote-asset` is sol, usdc or cndl on Solana, eth or usdg on Hood. |
125
+ | `candle launch --name <name> --symbol <symbol> --image <path>\|--image-url <url> --wallet <tee> [--buy <amount>] [--quote-asset <asset>] [--dex-version v3\|v4]` | Create a token from a TEE wallet. The image is `--image-url` (an `https://` URL) or `--image <path>` (a PNG, JPEG, GIF or WebP file on this machine, at most 10MB), never both; `--image` uploads the file before the confirm prompt, which makes it public and permanent, and launches with the URL that comes back. Without `--buy` it makes no first buy. `--buy <amount>` adds one from the same wallet, in whole units of the quote asset, reserved against the key's spend window before signing: in the launch transaction on Solana, as a last leg on Hood. It needs the server's `TEE_LAUNCH_BUYS_ENABLED`, which is off by default; while it is off Candle refuses the buy and nothing is built. Needs `launch:write`, `allowLaunch` turned on for the wallet by the account owner, and the Pro or Max plan. On Free, launch from the embedded wallet instead, with an optional dev buy (same transaction on Solana; best-effort follow-up on Hood): the MCP tool `candle_launch_and_seed` or the SDK's `launch()`. The wallet decides the chain: a Hood TEE wallet launches on Hood and needs `--dex-version`. `--quote-asset` is sol, usdc or cndl on Solana, eth or usdg on Hood. |
126
+ | `candle image upload <path>` | Uploads a PNG, JPEG, GIF or WebP file, at most 10MB, and prints the `https://` URL Candle hosts it at, ready to be a launch's `imageUrl`. The type is read from the file's first bytes. The file becomes public and permanent. Needs `launch:write`; spends nothing and does not count against the launch cap. `--json` adds the content type and the size. |
126
127
  | `candle pnl [--profile <name>]` | P&L: realized net, fees, unrealized and total, then open positions, the same figures the console shows. Without `--profile`, the account's books (every profile, the web app and the CLI, one ledger; needs the Read scope), with a By agent table (each key, then Manual, then the account total). With `--profile <name>`, that profile's key's share of the same P&L: realized on its sales and the open positions that belong to it, with a row per wallet that sums to the total. Each wallet is its own average-cost pool, and a recorded move between the account's wallets carries its cost. Both show a Solana block, a Hood block, then the combined figure, the closed positions as money made, minus money lost, plus partial sells, and a `CHAIN` column (and `WALLET`, where the API names it) on positions. Unpriced positions are never valued at zero; dust (worth under one cent) is marked and still counted. A bridge between Solana and Hood is not a trade, and bridge and Relay costs are not netted. DAMM v2 LP positions are included: realized (withdrawals against the cost basis at the add, plus claimed fees), unrealized (open positions at their share of the pool plus unclaimed fees, against that basis) and vs holding (what the deposited tokens would be worth held); `Total` stays the console's token figure and `Total with LP` covers tokens and LP. |
127
128
  | `candle portfolio [<filter>] [--chain solana\|hood] [--rpc-url <url>] [--evm-rpc-url <url>]` | Vault, TEE and embedded wallets on Solana and Hood in one table with chain, amount, price and value, subtotals per group and per chain, and a total. Solana vault and external balances are read over your own Solana RPC; Candle is sent only the mints, for prices. EVM vault keys are read over your own Hood RPC (`--evm-rpc-url`, else `CANDLE_EVM_RPC_URL`, else the built-in Hood RPC) for ETH and USDG only, priced by asset, never by address; an RPC on another chain is refused. An unpriced Hood token shows why (`no-market-row`, `stale-mark`, ...). `--chain` shows one chain. Each TEE wallet's DAMM v2 LP positions follow the tokens, valued by Candle at the same marks, and count in the total; one whose pool could not be read is shown as not read and the total says it is partial. |
128
129
  | `candle plans` | The plans the API serves (`GET /api/v1/agent/plans`): each plan's monthly price, agent trade fee, Hyperliquid perps builder fee, the limits a new key gets, and one row per capability (buying and selling tokens not launched on Candle, self-launch, atomic launch, linked wallets, perps, limit orders). Every number is the server's; the CLI bundles none. Needs no key and sends none. `--json` is the server's body. Your own plan is `candle doctor`'s Plan row. |
@@ -412,10 +413,21 @@ candle swap SOL USDC --amount 0.1 --wallet trading --client-trade-id lunch-1
412
413
  candle swap <mint> SOL --percent 25 --wallet trading --rpc-url https://your-solana-rpc --yes --json
413
414
  candle launch --name Example --symbol EX --image-url https://example.com/token.png \
414
415
  --wallet trading --rpc-url https://your-solana-rpc --client-trade-id example-launch
416
+ candle launch --name Example --symbol EX --image ./token.png --wallet trading
417
+ candle image upload ./token.png
415
418
  candle swap status lunch-1
416
419
  candle swap status example-launch --kind launch --json
417
420
  ```
418
421
 
422
+ `--image <path>` launches with an image file on this machine: the CLI checks it (a PNG, JPEG,
423
+ GIF or WebP by its first bytes, at most 10MB) before any request, uploads it to Candle, and
424
+ builds the launch with the URL that comes back. The upload happens once, before the confirm
425
+ prompt and before the launch id is claimed, and it is a publication: the file is pinned to public
426
+ storage and stays there, with whatever metadata it carries, even if you decline or the launch
427
+ fails. A failed upload leaves the launch id unused, and re-running a started launch uploads
428
+ nothing. `candle image upload <path>` does the upload alone and prints the URL, for a launch made
429
+ somewhere else. `--image-url` takes only an `https://` URL.
430
+
419
431
  `candle lp` (Meteora DAMM v2) follows the same shape: the server builds and quotes, you confirm,
420
432
  the relay signs, and this machine broadcasts over `--rpc-url` before `/confirm` writes the ledger.
421
433
  The bound key needs the opt-in `lp:write` scope. An open
package/dist/index.js CHANGED
@@ -98,7 +98,10 @@ function resolveApiUrl(configuredApiUrl, env = process.env) {
98
98
  return trimTrailingSlashes(resolved);
99
99
  }
100
100
  function buildHeaders(opts) {
101
- const headers = { "content-type": "application/json", accept: "application/json" };
101
+ const headers = {
102
+ "content-type": opts.raw?.contentType ?? "application/json",
103
+ accept: "application/json"
104
+ };
102
105
  if (opts.auth === "device" && opts.credentials.deviceToken) {
103
106
  headers.authorization = `Bearer ${opts.credentials.deviceToken}`;
104
107
  } else if (opts.auth === "key" && opts.credentials.apiKey) {
@@ -156,7 +159,7 @@ async function apiRequest(path, opts) {
156
159
  return { ok: false, status: 0, code: "INSECURE_API_URL", message: insecure, raw: undefined };
157
160
  }
158
161
  const headers = buildHeaders(opts);
159
- const body = opts.body === undefined ? undefined : JSON.stringify(opts.body);
162
+ const body = opts.raw ? opts.raw.bytes : opts.body === undefined ? undefined : JSON.stringify(opts.body);
160
163
  const doFetch = opts.fetch ?? fetch;
161
164
  let response;
162
165
  try {
@@ -61132,7 +61135,7 @@ var init_convert = __esm(() => {
61132
61135
  });
61133
61136
 
61134
61137
  // ../mcp/src/version.ts
61135
- var SERVER_VERSION = "0.10.10";
61138
+ var SERVER_VERSION = "0.10.11";
61136
61139
 
61137
61140
  // ../mcp/src/update-notice.ts
61138
61141
  function newer(a, b) {
@@ -61554,7 +61557,7 @@ async function executionStatus(cfg, doFetch) {
61554
61557
  get("/api/v1/agent/tier"),
61555
61558
  get("/api/v1/agent/keys/self/limits")
61556
61559
  ]);
61557
- const unreadable = [
61560
+ const unreadable2 = [
61558
61561
  ["wallets", wallets2],
61559
61562
  ["tier", tier],
61560
61563
  ["limits", limits]
@@ -61563,15 +61566,15 @@ async function executionStatus(cfg, doFetch) {
61563
61566
  return {
61564
61567
  text: JSON.stringify({
61565
61568
  success: true,
61566
- ready: unreadable.length === 0 ? true : undefined,
61569
+ ready: unreadable2.length === 0 ? true : undefined,
61567
61570
  notice: tierBody?.maxExpired ? tierBody.maxExpiredNotice : undefined,
61568
61571
  updateAvailable: updateAvailable() ?? undefined,
61569
- unreadable: unreadable.length > 0 ? unreadable.map(([name2]) => name2) : undefined,
61572
+ unreadable: unreadable2.length > 0 ? unreadable2.map(([name2]) => name2) : undefined,
61570
61573
  wallets: wallets2.body,
61571
61574
  tier: tier.body,
61572
61575
  limits: limits.body
61573
61576
  }, null, 2),
61574
- ...unreadable.length > 0 ? { isError: true } : {}
61577
+ ...unreadable2.length > 0 ? { isError: true } : {}
61575
61578
  };
61576
61579
  }
61577
61580
  var SWEEP_BASE_ASSETS;
@@ -62570,6 +62573,27 @@ function buildRequest(name2, args, cfg) {
62570
62573
  const path = kind === "launch" ? `/api/v1/launch/headless/jobs/${encodeURIComponent(clientId)}` : `/api/v1/trade/agent/jobs/${encodeURIComponent(clientId)}`;
62571
62574
  return { url: `${base2}${path}`, init: { method: "GET", headers: jsonHeaders(apiKey) } };
62572
62575
  }
62576
+ case "candle_get_fees": {
62577
+ const apiKey = requireApiKey2(cfg);
62578
+ const { cursor, limit } = args;
62579
+ const params = new URLSearchParams;
62580
+ if (cursor !== undefined)
62581
+ params.set("cursor", cursor);
62582
+ if (limit !== undefined)
62583
+ params.set("limit", String(limit));
62584
+ return {
62585
+ url: `${base2}/api/v1/agent/fees${params.size ? `?${params}` : ""}`,
62586
+ init: { method: "GET", headers: jsonHeaders(apiKey) }
62587
+ };
62588
+ }
62589
+ case "candle_claim_fees": {
62590
+ const apiKey = requireApiKey2(cfg);
62591
+ const { mint, dryRun } = args;
62592
+ return {
62593
+ url: `${base2}/api/v1/agent/fees/${encodeURIComponent(mint)}/claim`,
62594
+ init: { method: "POST", headers: jsonHeaders(apiKey), body: JSON.stringify({ dryRun: dryRun === true }) }
62595
+ };
62596
+ }
62573
62597
  case "candle_get_wallets": {
62574
62598
  const apiKey = requireApiKey2(cfg);
62575
62599
  return {
@@ -62737,6 +62761,16 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
62737
62761
  ` + "Amounts come back RAW, deliberately: the answer you need after a timeout is the outcome, " + "and you already know what you asked for.",
62738
62762
  inputSchema: getOperationShape
62739
62763
  }, async (args) => callAndRelay("candle_get_operation", args, getConfig(), doFetch));
62764
+ register("candle_get_fees", {
62765
+ title: "Read the creator fees this account's tokens can pay",
62766
+ 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`.",
62767
+ inputSchema: getFeesShape
62768
+ }, async (args) => callAndRelay("candle_get_fees", args, getConfig(), doFetch));
62769
+ register("candle_claim_fees", {
62770
+ title: "Claim a token's creator fees",
62771
+ 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, and the server keeps it off until it is enabled (LAUNCH_DISABLED). " + "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.",
62772
+ inputSchema: claimFeesShape
62773
+ }, async (args) => callAndRelay("candle_claim_fees", args, getConfig(), doFetch));
62740
62774
  register("candle_get_wallets", {
62741
62775
  title: "List the wallets Candle executes with",
62742
62776
  description: "The account's EMBEDDED wallets, one per chain, with their delegation state. These are " + "the wallets candle_trade, candle_swap and candle_transfer spend from, so this is how an " + "agent finds its own funding addresses. Reads only; moves nothing. Not the same as the " + "account's LINKED wallets, which are the owner's own wallets and are not spent from here. " + "Balances are not included: read a specific one with the market and balance endpoints.",
@@ -62893,7 +62927,7 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
62893
62927
  return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
62894
62928
  });
62895
62929
  }
62896
- var TOOL_NAMES, launchTokenShape, getMarketShape, tokenForensicsShape, FEED_LIMIT_MESSAGE = "limit must be a whole number from 1 to 200", feedLimit, getFeedShape, reportActivityShape, getAgentProfileShape, getOperationShape, resolveTokenShape, profileWalletsShape, profilePnlShape, profileTradesShape, setProfileWalletsShape, swapShape, tradeShape, _rawBuyAmount, seedableLaunchShape, launchAndSeedShape, transferShape, sweepShape;
62930
+ var TOOL_NAMES, launchTokenShape, getMarketShape, tokenForensicsShape, FEED_LIMIT_MESSAGE = "limit must be a whole number from 1 to 200", feedLimit, getFeedShape, reportActivityShape, getAgentProfileShape, getOperationShape, getFeesShape, claimFeesShape, resolveTokenShape, profileWalletsShape, profilePnlShape, profileTradesShape, setProfileWalletsShape, swapShape, tradeShape, _rawBuyAmount, seedableLaunchShape, launchAndSeedShape, transferShape, sweepShape;
62897
62931
  var init_tools = __esm(() => {
62898
62932
  init_zod();
62899
62933
  init_convert();
@@ -62924,6 +62958,8 @@ var init_tools = __esm(() => {
62924
62958
  "candle_resolve_token",
62925
62959
  "candle_execution_status",
62926
62960
  "candle_get_operation",
62961
+ "candle_get_fees",
62962
+ "candle_claim_fees",
62927
62963
  "candle_perps_setup",
62928
62964
  "candle_perps_open",
62929
62965
  "candle_perps_close",
@@ -62985,6 +63021,14 @@ var init_tools = __esm(() => {
62985
63021
  clientId: exports_external.string().describe("The clientTradeId or clientLaunchId the write used, or that the tool echoed back to you"),
62986
63022
  kind: exports_external.enum(["trade", "launch"]).describe("Which rail the id belongs to. Required: ids are caller-chosen strings with no shape to dispatch on")
62987
63023
  };
63024
+ getFeesShape = {
63025
+ cursor: exports_external.string().optional().describe("The previous page's continueCursor, verbatim"),
63026
+ limit: exports_external.number().optional().describe("Tokens per page, 1 to 50 (default 20). Each one costs chain reads")
63027
+ };
63028
+ claimFeesShape = {
63029
+ mint: exports_external.string().describe("The launched token: a Solana mint or a Hood token address, as candle_get_fees lists it"),
63030
+ dryRun: exports_external.boolean().optional().describe("Check and read only. Sends nothing and writes nothing. Use it first to see what would be claimed")
63031
+ };
62988
63032
  resolveTokenShape = {
62989
63033
  mint: exports_external.string().describe("Token mint (Solana, base58) or contract address (Hood, 0x-prefixed)")
62990
63034
  };
@@ -63167,7 +63211,7 @@ init_render();
63167
63211
  init_secret_store();
63168
63212
 
63169
63213
  // src/version.ts
63170
- var CLI_VERSION = "0.11.22";
63214
+ var CLI_VERSION = "0.11.24";
63171
63215
 
63172
63216
  // src/commands/auth.ts
63173
63217
  init_keys_embedded_wallet();
@@ -63838,15 +63882,19 @@ var HELP = {
63838
63882
  launch: {
63839
63883
  group: "Trade",
63840
63884
  summary: "Create a token on Solana or Hood, with an optional first buy",
63841
- description: "Creates a token from a TEE wallet. Without --buy it makes no first buy, so the launch and the position are two decisions, and the buy is a separate candle swap. --buy <amount> adds a first buy from the same wallet, in whole units of the quote asset (SOL, USDC or CNDL on Solana, ETH on Hood; not USDG), within the dev-buy ceilings: on Solana it rides in the launch transaction, and on Hood it is a last leg after the curve and the fee. Candle reserves the buy against the key's spend window before anything is signed. --buy needs a server with TEE_LAUNCH_BUYS_ENABLED on, which is off by default; while it is off Candle refuses the buy and nothing is built. Needs the launch:write scope and allowLaunch on the wallet, which only the account owner can turn on (candle wallets allow-launch <wallet>, over the device token; an agent key cannot). The wallet decides the chain: a Hood TEE wallet launches on Hood, needs --dex-version, and signs one leg at a time (the curve, then the fee, then the buy with --buy), each only after the one before it landed. Launching from a TEE wallet needs Pro or Max (TIER_REQUIRED otherwise; candle plans). Every plan, Free included, can launch from its embedded wallet with an optional dev buy (same transaction on Solana; best-effort follow-up on Hood), through the MCP tool candle_launch_and_seed or the SDK's launch(); this command does not use that path.",
63885
+ description: "Creates a token from a TEE wallet. Without --buy it makes no first buy, so the launch and the position are two decisions, and the buy is a separate candle swap. --buy <amount> adds a first buy from the same wallet, in whole units of the quote asset (SOL, USDC or CNDL on Solana, ETH on Hood; not USDG), within the dev-buy ceilings: on Solana it rides in the launch transaction, and on Hood it is a last leg after the curve and the fee. Candle reserves the buy against the key's spend window before anything is signed. --buy needs a server with TEE_LAUNCH_BUYS_ENABLED on, which is off by default; while it is off Candle refuses the buy and nothing is built. Needs the launch:write scope and allowLaunch on the wallet, which only the account owner can turn on (candle wallets allow-launch <wallet>, over the device token; an agent key cannot). The wallet decides the chain: a Hood TEE wallet launches on Hood, needs --dex-version, and signs one leg at a time (the curve, then the fee, then the buy with --buy), each only after the one before it landed. Launching from a TEE wallet needs Pro or Max (TIER_REQUIRED otherwise; candle plans). Every plan, Free included, can launch from its embedded wallet with an optional dev buy (same transaction on Solana; best-effort follow-up on Hood), through the MCP tool candle_launch_and_seed or the SDK's launch(); this command does not use that path. The image is one of two flags, never both: --image-url <https-url> for one already hosted, or --image <path> for a PNG, JPEG, GIF or WebP file on this machine, at most 10MB. With --image the file is uploaded to Candle first and the launch is made with the URL that comes back, which --json reports as imageUrl. The upload happens before the confirm prompt and cannot be undone: the file becomes public and permanent even if you then decline or the launch fails. Re-running with the same --client-trade-id resumes the launch without uploading again. --image-url takes only an https:// URL.",
63842
63886
  usage: [
63843
- "candle launch --name <name> --symbol <symbol> --image-url <url> --wallet <tee> [--buy <amount>] [--quote-asset <asset>] [--dex-version v3|v4]"
63887
+ "candle launch --name <name> --symbol <symbol> --image <path>|--image-url <url> --wallet <tee> [--buy <amount>] [--quote-asset <asset>] [--dex-version v3|v4]"
63844
63888
  ],
63845
63889
  rows: [],
63846
63890
  flags: [
63847
63891
  { invocation: "--name <name>", description: "The token's name" },
63848
63892
  { invocation: "--symbol <symbol>", description: "The token's ticker" },
63849
- { invocation: "--image-url <url>", description: "The token image, already hosted" },
63893
+ {
63894
+ invocation: "--image <path>",
63895
+ description: "The token image as a file on this machine (PNG, JPEG, GIF or WebP, at most 10MB). Uploaded before the confirm prompt; it becomes public and permanent"
63896
+ },
63897
+ { invocation: "--image-url <url>", description: "The token image, already hosted at an https:// URL" },
63850
63898
  { invocation: "--wallet <tee>", description: "The TEE wallet that creates it" },
63851
63899
  {
63852
63900
  invocation: "--buy <amount>",
@@ -63859,12 +63907,27 @@ var HELP = {
63859
63907
  }
63860
63908
  ],
63861
63909
  examples: [
63910
+ "candle launch --name Demo --symbol DEMO --image ./logo.png --wallet AgentOne",
63862
63911
  "candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne",
63863
63912
  "candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet HoodAgent --dex-version v4",
63864
63913
  "candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne --buy 0.1"
63865
63914
  ],
63866
63915
  env: ENV_API
63867
63916
  },
63917
+ image: {
63918
+ group: "Trade",
63919
+ summary: "Host an image file from this machine and print its https URL",
63920
+ description: "Uploads a PNG, JPEG, GIF or WebP file, at most 10MB, to Candle and prints the https URL it is hosted at, ready to be a launch's image. The type is read from the file's first bytes, not its name, and a file that is none of the four or is too large is refused before any request. The upload is a publication: Candle pins the file to public storage, the URL cannot be withdrawn, and whatever the file carries (metadata included) goes with it, so upload only an image meant to be public. It spends no funds and does not count against the daily launch cap; the key's uploads-per-minute limit applies. Needs an API key with the launch:write scope. candle launch --image <path> does this upload itself; use this command for the link alone, or to launch through the hosted connector, which cannot read a file on your machine. Candle crops the image to a square, so this hosts a token image and not a banner.",
63921
+ usage: ["candle image upload <path> [--json]"],
63922
+ rows: [
63923
+ {
63924
+ invocation: "upload <path>",
63925
+ description: "Upload the file and print its URL; --json adds the content type and the size in bytes"
63926
+ }
63927
+ ],
63928
+ examples: ["candle image upload ./logo.png", "candle image upload /Users/me/art/logo.webp --json"],
63929
+ env: ENV_API
63930
+ },
63868
63931
  portfolio: {
63869
63932
  group: "Account",
63870
63933
  summary: "Every wallet's holdings, prices and value on Solana and Hood (vault read over your own RPCs)",
@@ -63896,6 +63959,24 @@ var HELP = {
63896
63959
  ],
63897
63960
  env: [...ENV_API, ...ENV_LOCAL_SIGNING]
63898
63961
  },
63962
+ fees: {
63963
+ group: "Account",
63964
+ summary: "Creator fees your launched tokens can pay, and the claim",
63965
+ description: "Lists the tokens launched by this account's wallets and what each could pay now, from direct chain reads, with the reason a token cannot be claimed. `fees claim` collects one token's creator fees and pays them to the wallet fixed at launch: there is no destination option, Candle signs with the embedded wallet, and that wallet pays the network fee. The key needs launch:write to claim and account:read to list. The server keeps the claim off until it is enabled, and while off even --dry-run answers LAUNCH_DISABLED. A Phantom-signup account's Solana fees are credited to the external wallet, which Candle cannot sign for, so its agent cannot claim them.",
63966
+ usage: ["candle fees [--cursor <cursor>] [--limit <1-50>]", "candle fees claim <mint> [--dry-run]"],
63967
+ rows: [
63968
+ {
63969
+ invocation: "claim <mint> [--dry-run]",
63970
+ description: "Claim one token's creator fees; --dry-run sends nothing"
63971
+ }
63972
+ ],
63973
+ flags: [
63974
+ { invocation: "--cursor <cursor>", description: "The previous page's continue cursor" },
63975
+ { invocation: "--limit <n>", description: "Tokens per page, 1 to 50 (default 20)" }
63976
+ ],
63977
+ examples: ["candle fees", "candle fees claim <mint> --dry-run", "candle fees claim <mint>"],
63978
+ env: ENV_API
63979
+ },
63899
63980
  pnl: {
63900
63981
  group: "Account",
63901
63982
  summary: "P&L per chain and combined: realized, fees, unrealized and open positions (--profile for one key's share)",
@@ -69757,6 +69838,122 @@ async function externalSweep(args, ctx) {
69757
69838
  });
69758
69839
  }
69759
69840
 
69841
+ // src/commands/fees.ts
69842
+ init_args();
69843
+ init_render();
69844
+ init_trading();
69845
+ init_swap();
69846
+ var LIST_USAGE = "Usage: candle fees [--cursor <cursor>] [--limit <1-50>] [--json]";
69847
+ var CLAIM_USAGE = "Usage: candle fees claim <mint> [--dry-run] [--json]";
69848
+ function amountText(row) {
69849
+ const raw = typeof row.amountRaw === "string" && /^\d+$/.test(row.amountRaw) ? row.amountRaw : null;
69850
+ if (raw === null)
69851
+ return "unknown";
69852
+ const decimals = Number.isInteger(row.decimals) ? row.decimals : 0;
69853
+ const label = row.symbol ? row.symbol : row.mint ?? "";
69854
+ const value = decimalAmount(raw, decimals);
69855
+ return `${value.includes(".") ? value.replace(/\.?0+$/, "") : value} ${safeText(label)}`.trim();
69856
+ }
69857
+ function amountsText(rows) {
69858
+ if (!Array.isArray(rows) || rows.length === 0)
69859
+ return "none";
69860
+ return rows.map((row) => amountText(row)).join(", ");
69861
+ }
69862
+ var REASON_WORDS = {
69863
+ below_dust: "under the dust floor",
69864
+ wallet_not_signable: "the recipient is a wallet Candle cannot sign for",
69865
+ delegation_off: "the embedded wallet is not delegated for agent access",
69866
+ embedded_wallet_not_permitted: "this key may not use the embedded wallet",
69867
+ not_recipient: "the fees go to someone else, or the token has no fee vault",
69868
+ insufficient_gas: "the recipient wallet cannot cover the network fee"
69869
+ };
69870
+ function reasonText(reason) {
69871
+ return typeof reason === "string" ? REASON_WORDS[reason] ?? safeText(reason) : "unknown";
69872
+ }
69873
+ async function feesList(args, ctx) {
69874
+ const parsed = parseArgs(args, { valueFlags: ["--cursor", "--limit"] });
69875
+ if ("error" in parsed) {
69876
+ writeUsageFailure(ctx.deps, parsed.error, ctx.json);
69877
+ return 2;
69878
+ }
69879
+ const limit = parsed.values["--limit"];
69880
+ if (parsed.positionals.length !== 0 || limit !== undefined && !/^(?:[1-9]|[1-4]\d|50)$/.test(limit)) {
69881
+ writeUsageFailure(ctx.deps, LIST_USAGE, ctx.json);
69882
+ return 2;
69883
+ }
69884
+ const cursor = parsed.values["--cursor"];
69885
+ try {
69886
+ const key = await tradingKey(ctx);
69887
+ const params = new URLSearchParams;
69888
+ if (cursor !== undefined)
69889
+ params.set("cursor", cursor);
69890
+ if (limit !== undefined)
69891
+ params.set("limit", limit);
69892
+ const body = await request(ctx, key, `/api/v1/agent/fees${params.size ? `?${params}` : ""}`);
69893
+ if (ctx.json)
69894
+ return printTradingResult(ctx, body);
69895
+ const page = Array.isArray(body.page) ? body.page : [];
69896
+ const out = ctx.deps.stdout;
69897
+ if (page.length === 0)
69898
+ out.write(`No launched tokens on this account.
69899
+ `);
69900
+ for (const row of page) {
69901
+ const name2 = safeText(String(row.symbol ?? row.name ?? row.mint));
69902
+ const status = row.unreadable ? "could not be read" : row.claimable ? `claimable: ${amountsText(row.claimableAmounts)}` : `not claimable: ${reasonText(row.reason)}`;
69903
+ out.write(`${name2} ${safeText(String(row.chain))} ${safeText(String(row.mint))} ${status}
69904
+ `);
69905
+ }
69906
+ if (body.isDone === false && typeof body.continueCursor === "string")
69907
+ out.write(`More: candle fees --cursor ${safeText(body.continueCursor)}
69908
+ `);
69909
+ return 0;
69910
+ } catch (error) {
69911
+ return tradingFailure(ctx, error);
69912
+ }
69913
+ }
69914
+ async function feesClaim(args, ctx) {
69915
+ const parsed = parseArgs(args, { booleanFlags: ["--dry-run"] });
69916
+ if ("error" in parsed) {
69917
+ writeUsageFailure(ctx.deps, parsed.error, ctx.json);
69918
+ return 2;
69919
+ }
69920
+ const mint = parsed.positionals[0];
69921
+ if (parsed.positionals.length !== 1 || !mint) {
69922
+ writeUsageFailure(ctx.deps, CLAIM_USAGE, ctx.json);
69923
+ return 2;
69924
+ }
69925
+ const dryRun = parsed.booleans.has("--dry-run");
69926
+ try {
69927
+ const key = await tradingKey(ctx);
69928
+ const body = await request(ctx, key, `/api/v1/agent/fees/${encodeURIComponent(mint)}/claim`, { dryRun });
69929
+ if (ctx.json)
69930
+ return printTradingResult(ctx, body);
69931
+ const out = ctx.deps.stdout;
69932
+ const claimed = Array.isArray(body.claimed) ? body.claimed : [];
69933
+ if (dryRun) {
69934
+ out.write(body.claimable === false ? `Dry run: would be refused: ${reasonText(body.reason)}. Nothing was sent.
69935
+ ` : `Dry run: would claim ${amountsText(claimed)} to ${safeText(String(body.recipient))}. Nothing was sent.
69936
+ `);
69937
+ } else {
69938
+ out.write(`Claimed ${amountsText(claimed)} to ${safeText(String(body.recipient))}
69939
+ `);
69940
+ for (const row of claimed)
69941
+ if (row.txHash)
69942
+ out.write(` ${safeText(row.txHash)}
69943
+ `);
69944
+ }
69945
+ out.write(`Still claimable: ${amountsText(body.remaining)}
69946
+ `);
69947
+ if (Array.isArray(body.warnings))
69948
+ for (const warning of body.warnings)
69949
+ ctx.deps.stderr.write(`Warning: ${safeText(String(warning))}
69950
+ `);
69951
+ return 0;
69952
+ } catch (error) {
69953
+ return tradingFailure(ctx, error);
69954
+ }
69955
+ }
69956
+
69760
69957
  // src/commands/help.ts
69761
69958
  async function help(args, ctx) {
69762
69959
  const word2 = args[0];
@@ -69775,6 +69972,121 @@ async function help(args, ctx) {
69775
69972
  return 0;
69776
69973
  }
69777
69974
 
69975
+ // src/commands/image.ts
69976
+ init_args();
69977
+ init_deps();
69978
+ init_render();
69979
+ init_trading();
69980
+ init_swap();
69981
+ import { open as open4 } from "node:fs/promises";
69982
+ var UPLOAD_USAGE = "Usage: candle image upload <path> [--json]";
69983
+ var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
69984
+ var IMAGE_TYPES = "PNG, JPEG, GIF or WebP";
69985
+ var NOTHING_UPLOADED = "Nothing was uploaded.";
69986
+ function sniffImageType(bytes) {
69987
+ const at = (offset, signature) => signature.every((byte, i) => bytes[offset + i] === byte);
69988
+ if (at(0, [137, 80, 78, 71, 13, 10, 26, 10]))
69989
+ return "image/png";
69990
+ if (at(0, [255, 216, 255]))
69991
+ return "image/jpeg";
69992
+ if (at(0, [71, 73, 70, 56]) && (bytes[4] === 55 || bytes[4] === 57) && bytes[5] === 97)
69993
+ return "image/gif";
69994
+ if (at(0, [82, 73, 70, 70]) && at(8, [87, 69, 66, 80]))
69995
+ return "image/webp";
69996
+ return;
69997
+ }
69998
+ function unreadable(path, error) {
69999
+ const code = error.code;
70000
+ const why = code === "ENOENT" || code === "ENOTDIR" ? "there is no file at that path" : code === "EACCES" || code === "EPERM" ? "this user may not read it" : code === "EISDIR" ? "it is not a file" : `the read failed${code ? ` (${code})` : ""}`;
70001
+ return new TradingError("IMAGE_UNREADABLE", `Could not read ${safeText(path)}: ${why}. ${NOTHING_UPLOADED}`);
70002
+ }
70003
+ async function readLocalImage(path) {
70004
+ const tooLarge = () => new TradingError("IMAGE_TOO_LARGE", `${safeText(path)} is over 10MB, the largest image Candle hosts. ${NOTHING_UPLOADED}`);
70005
+ let bytes;
70006
+ try {
70007
+ const file = await open4(path, "r");
70008
+ try {
70009
+ const stat4 = await file.stat();
70010
+ if (!stat4.isFile())
70011
+ throw Object.assign(new Error("not a regular file"), { code: "EISDIR" });
70012
+ if (stat4.size > MAX_IMAGE_BYTES)
70013
+ throw tooLarge();
70014
+ const buffer = new Uint8Array(MAX_IMAGE_BYTES + 1);
70015
+ let total = 0;
70016
+ while (total < buffer.length) {
70017
+ const { bytesRead } = await file.read(buffer, total, buffer.length - total, null);
70018
+ if (bytesRead === 0)
70019
+ break;
70020
+ total += bytesRead;
70021
+ }
70022
+ bytes = buffer.slice(0, total);
70023
+ } finally {
70024
+ await file.close();
70025
+ }
70026
+ } catch (error) {
70027
+ throw error instanceof TradingError ? error : unreadable(path, error);
70028
+ }
70029
+ if (bytes.length > MAX_IMAGE_BYTES)
70030
+ throw tooLarge();
70031
+ const contentType = sniffImageType(bytes);
70032
+ if (!contentType)
70033
+ throw new TradingError("IMAGE_TYPE_UNSUPPORTED", `${safeText(path)} is not a ${IMAGE_TYPES} image. The type is read from the file's first bytes, not its name. ${NOTHING_UPLOADED}`);
70034
+ return { path, bytes, contentType };
70035
+ }
70036
+ function uploadNotice(image) {
70037
+ return `Uploading ${safeText(image.path)} (${image.contentType}, ${image.bytes.length} bytes). The file becomes public and permanent: Candle pins it, and the URL cannot be withdrawn.
70038
+ `;
70039
+ }
70040
+ async function uploadImage(ctx, key, image) {
70041
+ const result = await apiRequest("/api/v1/uploads/agent-image", {
70042
+ apiUrl: ctx.apiUrl,
70043
+ credentials: { apiKey: key },
70044
+ auth: "key",
70045
+ method: "POST",
70046
+ raw: { bytes: image.bytes, contentType: image.contentType },
70047
+ fetch: ctx.deps.fetch,
70048
+ env: ctx.deps.env
70049
+ });
70050
+ if (!result.ok)
70051
+ throw new TradingError(result.code ?? "REQUEST_FAILED", result.message);
70052
+ const imageUrl = result.body?.imageUrl;
70053
+ if (typeof imageUrl !== "string" || !imageUrl.startsWith("https://"))
70054
+ throw new TradingError("INVALID_RESPONSE", "Candle did not return an https URL for the uploaded image.");
70055
+ return imageUrl;
70056
+ }
70057
+ async function imageUpload(args, ctx) {
70058
+ const parsed = parseArgs(args, { pathPositionals: ["<path>"] });
70059
+ if ("error" in parsed) {
70060
+ writeUsageFailure(ctx.deps, parsed.error, ctx.json);
70061
+ return 2;
70062
+ }
70063
+ const [path, ...rest] = parsed.positionals;
70064
+ if (path === undefined || rest.length !== 0) {
70065
+ writeUsageFailure(ctx.deps, UPLOAD_USAGE, ctx.json);
70066
+ return 2;
70067
+ }
70068
+ try {
70069
+ const image = await readLocalImage(path);
70070
+ const key = await resolveApiKey(ctx.deps, ctx.profile);
70071
+ if (!key) {
70072
+ writeLocalFailure(ctx.deps, {
70073
+ code: "NO_API_KEY",
70074
+ message: `No API key available. ${NOTHING_UPLOADED}`,
70075
+ suggestion: "Run: candle auth login, or set CANDLE_API_KEY. The key needs the launch:write scope."
70076
+ }, ctx.json);
70077
+ return 1;
70078
+ }
70079
+ ctx.deps.stderr.write(uploadNotice(image));
70080
+ const imageUrl = safeText(await uploadImage(ctx, key, image));
70081
+ ctx.deps.stdout.write(ctx.json ? `${JSON.stringify({ success: true, imageUrl, contentType: image.contentType, bytes: image.bytes.length })}
70082
+ ` : `${imageUrl}
70083
+ `);
70084
+ return 0;
70085
+ } catch (error) {
70086
+ return tradingFailure(ctx, error);
70087
+ }
70088
+ }
70089
+
69778
70090
  // src/index.ts
69779
70091
  init_keys();
69780
70092
 
@@ -70293,9 +70605,9 @@ init_render();
70293
70605
  init_solana_endpoint();
70294
70606
  init_solana_lite();
70295
70607
  init_trading();
70296
- init_swap();
70297
70608
  import { randomUUID as randomUUID2 } from "node:crypto";
70298
- var USAGE2 = "Usage: candle launch --name <name> --symbol <symbol> --image-url <https-url> --wallet <tee> [--buy <amount>] [--client-trade-id <id>] [--quote-asset sol|usdc|cndl|eth|usdg] [--dex-version v3|v4] [--mode <mode>] [--rpc-url <url>] [--yes]. A Hood TEE wallet launches on Hood and needs --dex-version.";
70609
+ init_swap();
70610
+ var USAGE2 = "Usage: candle launch --name <name> --symbol <symbol> --image <path>|--image-url <https-url> --wallet <tee> [--buy <amount>] [--client-trade-id <id>] [--quote-asset sol|usdc|cndl|eth|usdg] [--dex-version v3|v4] [--mode <mode>] [--rpc-url <url>] [--yes]. A Hood TEE wallet launches on Hood and needs --dex-version.";
70299
70611
  var QUOTE_CHAIN = {
70300
70612
  sol: "solana",
70301
70613
  usdc: "solana",
@@ -70354,6 +70666,7 @@ async function launch(args, ctx) {
70354
70666
  valueFlags: [
70355
70667
  "--name",
70356
70668
  "--symbol",
70669
+ "--image",
70357
70670
  "--image-url",
70358
70671
  "--description",
70359
70672
  "--wallet",
@@ -70364,7 +70677,8 @@ async function launch(args, ctx) {
70364
70677
  "--dex-version",
70365
70678
  "--buy"
70366
70679
  ],
70367
- booleanFlags: ["--yes"]
70680
+ booleanFlags: ["--yes"],
70681
+ pathFlags: ["--image"]
70368
70682
  });
70369
70683
  if ("error" in parsed) {
70370
70684
  writeUsageFailure(ctx.deps, parsed.error, ctx.json);
@@ -70372,7 +70686,15 @@ async function launch(args, ctx) {
70372
70686
  }
70373
70687
  const flags = parsed.values;
70374
70688
  const id = flags["--client-trade-id"] ?? `launch-${randomUUID2()}`;
70375
- if (parsed.positionals.length || !flags["--name"] || !flags["--symbol"] || !flags["--image-url"] || !flags["--wallet"] || !validClientId(id) || flags["--dex-version"] !== undefined && flags["--dex-version"] !== "v3" && flags["--dex-version"] !== "v4") {
70689
+ if (flags["--image"] !== undefined && flags["--image-url"] !== undefined) {
70690
+ writeUsageFailure(ctx.deps, `Pass --image <path> or --image-url <https-url>, not both: a launch has one image. ${USAGE2}`, ctx.json);
70691
+ return 2;
70692
+ }
70693
+ if (flags["--image-url"] !== undefined && !flags["--image-url"].startsWith("https://")) {
70694
+ writeUsageFailure(ctx.deps, "--image-url takes an https:// URL; for an image file on this machine, use --image <path> instead.", ctx.json);
70695
+ return 2;
70696
+ }
70697
+ if (parsed.positionals.length || !flags["--name"] || !flags["--symbol"] || flags["--image"] === undefined && flags["--image-url"] === undefined || !flags["--wallet"] || !validClientId(id) || flags["--dex-version"] !== undefined && flags["--dex-version"] !== "v3" && flags["--dex-version"] !== "v4") {
70376
70698
  writeUsageFailure(ctx.deps, USAGE2, ctx.json);
70377
70699
  return 2;
70378
70700
  }
@@ -70384,6 +70706,13 @@ async function launch(args, ctx) {
70384
70706
  writeUsageFailure(ctx.deps, `A Hood launch needs --dex-version v3|v4. ${USAGE2}`, ctx.json);
70385
70707
  return 2;
70386
70708
  }
70709
+ let image;
70710
+ try {
70711
+ if (flags["--image"] !== undefined)
70712
+ image = await readLocalImage(flags["--image"]);
70713
+ } catch (error) {
70714
+ return tradingFailure(ctx, error);
70715
+ }
70387
70716
  const key = await tradingKey(ctx);
70388
70717
  const prior = await lookupOperation(ctx, key, id, "launch");
70389
70718
  if (prior) {
@@ -70401,14 +70730,27 @@ async function launch(args, ctx) {
70401
70730
  return printTradingResult(ctx, prior);
70402
70731
  }
70403
70732
  const wallet = await tradingWallet(ctx, key, flags["--wallet"], "launch:write", hinted ?? "any");
70404
- if (wallet.chain === "evm") {
70405
- if (flags["--dex-version"] === undefined)
70406
- throw new TradingUsage(`${wallet.address} is a Hood TEE wallet, and a Hood launch needs --dex-version v3|v4.`);
70407
- const buy2 = launchBuy(flags, "hood");
70408
- return await hoodLaunch(ctx, key, { flags, id, wallet, buy: buy2, yes: parsed.booleans.has("--yes") });
70733
+ const chain2 = wallet.chain === "evm" ? "hood" : "solana";
70734
+ if (chain2 === "hood" && flags["--dex-version"] === undefined)
70735
+ throw new TradingUsage(`${wallet.address} is a Hood TEE wallet, and a Hood launch needs --dex-version v3|v4.`);
70736
+ const buy = launchBuy(flags, chain2);
70737
+ const solana = chain2 === "solana" ? await tradingSolanaClient(ctx, flags["--rpc-url"]) : undefined;
70738
+ let imageUrl = flags["--image-url"] ?? "";
70739
+ if (image) {
70740
+ ctx.deps.stderr.write(uploadNotice(image));
70741
+ try {
70742
+ imageUrl = await uploadImage(ctx, key, image);
70743
+ } catch (error) {
70744
+ ctx.deps.stderr.write(`The launch was not started, and its id ${id} is unused.
70745
+ `);
70746
+ return tradingFailure(ctx, error);
70747
+ }
70748
+ ctx.deps.stderr.write(`Image: ${safeText(imageUrl)}
70749
+ `);
70409
70750
  }
70410
- const buy = launchBuy(flags, "solana");
70411
- const solana = await tradingSolanaClient(ctx, flags["--rpc-url"]);
70751
+ const used = image ? { imageUrl: safeText(imageUrl) } : {};
70752
+ if (!solana)
70753
+ return await hoodLaunch(ctx, key, { flags, id, wallet, buy, imageUrl, used, yes: parsed.booleans.has("--yes") });
70412
70754
  if (!await claimOperation(ctx, key, id, "launch"))
70413
70755
  throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this launch id; no write was resent.");
70414
70756
  ctx.deps.stderr.write(`Operation: ${id}
@@ -70419,7 +70761,7 @@ async function launch(args, ctx) {
70419
70761
  buyAmount: buy ? buy.raw : 0,
70420
70762
  name: flags["--name"],
70421
70763
  symbol: flags["--symbol"],
70422
- imageUrl: flags["--image-url"],
70764
+ imageUrl,
70423
70765
  linkedWalletId: wallet.id,
70424
70766
  ...flags["--description"] ? { description: flags["--description"] } : {},
70425
70767
  ...flags["--quote-asset"] ? { quoteAsset: flags["--quote-asset"] } : {},
@@ -70441,7 +70783,14 @@ async function launch(args, ctx) {
70441
70783
  };
70442
70784
  ctx.deps.stderr.write(buyNotice(buy, "solana"));
70443
70785
  if (!await confirmQuote(ctx, quote, parsed.booleans.has("--yes")))
70444
- return printTradingResult(ctx, { success: true, status: "cancelled", clientTradeId: id, kind: "launch", quote });
70786
+ return printTradingResult(ctx, {
70787
+ success: true,
70788
+ status: "cancelled",
70789
+ clientTradeId: id,
70790
+ kind: "launch",
70791
+ quote,
70792
+ ...used
70793
+ });
70445
70794
  if (!Number.isFinite(built.expiresAt) || built.expiresAt <= ctx.deps.now())
70446
70795
  throw new TradingError("QUOTE_EXPIRED", "The launch build expired before signing.");
70447
70796
  const signed = await relaySign(ctx, key, wallet, built.transaction);
@@ -70459,7 +70808,7 @@ async function launch(args, ctx) {
70459
70808
  ctx.deps.stderr.write(`Launch signature: ${signature}
70460
70809
  `);
70461
70810
  const result = await request(ctx, key, "/api/v1/launch/self/confirm", { clientLaunchId: id, signature });
70462
- return printTradingResult(ctx, { ...result, clientTradeId: id, kind: "launch", quote });
70811
+ return printTradingResult(ctx, { ...result, clientTradeId: id, kind: "launch", quote, ...used });
70463
70812
  } catch (error) {
70464
70813
  return tradingFailure(ctx, error, id);
70465
70814
  }
@@ -70477,7 +70826,7 @@ async function hoodLaunch(ctx, key, args) {
70477
70826
  buyAmount: buy ? buy.raw : 0,
70478
70827
  name: flags["--name"],
70479
70828
  symbol: flags["--symbol"],
70480
- imageUrl: flags["--image-url"],
70829
+ imageUrl: args.imageUrl,
70481
70830
  linkedWalletId: wallet.id,
70482
70831
  dexVersion: flags["--dex-version"],
70483
70832
  ...flags["--description"] ? { description: flags["--description"] } : {},
@@ -70523,7 +70872,8 @@ async function hoodLaunch(ctx, key, args) {
70523
70872
  chain: "hood",
70524
70873
  quote,
70525
70874
  operationId: first.operationId,
70526
- walletHeldUntil: first.expiresAt
70875
+ walletHeldUntil: first.expiresAt,
70876
+ ...args.used
70527
70877
  });
70528
70878
  }
70529
70879
  const confirmedPlan = buy ? first.plannedLegCount === 3 ? ["createCurve", "feeTransfer", "trade"] : ["createCurve", "trade"] : undefined;
@@ -70556,7 +70906,8 @@ async function hoodLaunch(ctx, key, args) {
70556
70906
  quote,
70557
70907
  wallet: safeText(wallet.address),
70558
70908
  operationId: first.operationId,
70559
- landedLegs: run2.landed
70909
+ landedLegs: run2.landed,
70910
+ ...args.used
70560
70911
  });
70561
70912
  }
70562
70913
  function hoodLaterLegsGas(count, buys) {
@@ -70999,6 +71350,8 @@ var MCP_TOOL_NAMES = [
70999
71350
  "candle_resolve_token",
71000
71351
  "candle_execution_status",
71001
71352
  "candle_get_operation",
71353
+ "candle_get_fees",
71354
+ "candle_claim_fees",
71002
71355
  "candle_perps_setup",
71003
71356
  "candle_perps_open",
71004
71357
  "candle_perps_close",
@@ -71919,10 +72272,10 @@ async function perpsOrders(args, ctx) {
71919
72272
  if (ctx.json)
71920
72273
  return printTradingResult(ctx, result);
71921
72274
  const out = ctx.deps.stdout;
71922
- const open4 = result.open ?? [];
71923
- out.write(`Open on Hyperliquid (${open4.length}):
72275
+ const open5 = result.open ?? [];
72276
+ out.write(`Open on Hyperliquid (${open5.length}):
71924
72277
  `);
71925
- for (const o of open4) {
72278
+ for (const o of open5) {
71926
72279
  out.write(` ${safeText(o.coin)} ${o.side === "B" ? "buy" : "sell"} ${safeText(o.sz)} at ${safeText(o.limitPx)}${o.reduceOnly ? " reduce-only" : ""}${o.isTrigger ? ` trigger ${safeText(o.triggerPx)}` : ""}, cloid ${safeText(o.cloid ?? "none")}
71927
72280
  `);
71928
72281
  }
@@ -72994,13 +73347,13 @@ function writeWalletEvents(ctx, books) {
72994
73347
  function writeAgents(ctx, agents, all) {
72995
73348
  if (!agents || agents.length === 0)
72996
73349
  return;
72997
- const open4 = (count2, unmarked) => `${count2}${unmarked > 0 ? ` (${unmarked} unpriced)` : ""}`;
73350
+ const open5 = (count2, unmarked) => `${count2}${unmarked > 0 ? ` (${unmarked} unpriced)` : ""}`;
72998
73351
  const rows = agents.map((a) => [
72999
73352
  a.keyPrefix === null ? "Manual (web app)" : a.label && a.label !== a.keyPrefix ? `${a.label} (${a.keyPrefix})` : a.keyPrefix,
73000
73353
  formatUsd(a.realizedNetUsd),
73001
73354
  formatUsd(a.unrealizedUsd),
73002
73355
  formatUsd(totalOf(a)),
73003
- open4(a.openPositions, a.unmarked),
73356
+ open5(a.openPositions, a.unmarked),
73004
73357
  String(a.closedRounds)
73005
73358
  ]);
73006
73359
  rows.push([
@@ -73008,7 +73361,7 @@ function writeAgents(ctx, agents, all) {
73008
73361
  formatUsd(all.realizedNetUsd),
73009
73362
  formatUsd(all.unrealizedUsd),
73010
73363
  formatUsd(totalOf(all)),
73011
- open4(all.openPositions, all.unmarked),
73364
+ open5(all.openPositions, all.unmarked),
73012
73365
  all.closedRounds !== undefined ? String(all.closedRounds) : "-"
73013
73366
  ]);
73014
73367
  ctx.deps.stdout.write(`
@@ -75412,9 +75765,9 @@ async function transfer(args, ctx) {
75412
75765
  const decimals = asset ? BASES[asset]?.decimals ?? 9 : await decimalsFor(ctx, flags["--mint"], lazySolanaClient(ctx, flags["--rpc-url"]));
75413
75766
  amountRaw = rawAmount(flags["--amount"], decimals);
75414
75767
  }
75415
- const amountText = isMax ? `the full spendable balance of ${label}` : `${flags["--amount"]} ${label}`;
75768
+ const amountText2 = isMax ? `the full spendable balance of ${label}` : `${flags["--amount"]} ${label}`;
75416
75769
  const confirmed = await confirmTransfer(ctx, [
75417
- `Transfer ${amountText} from ${wallet.address} (${wallet.id}) to ${describeDestination(destination)}`,
75770
+ `Transfer ${amountText2} from ${wallet.address} (${wallet.id}) to ${describeDestination(destination)}`,
75418
75771
  destination.kind === "vault" ? "Destination: this wallet's own vault, pinned when it was imported." : destination.kind === "linked" ? "Destination: a linked wallet on this account. Candle allows it only if you linked it while signed in or marked it trusted, and the amount counts against this key's spend caps." : "Destination: an address Candle will classify at build; from a TEE wallet only its vault or a trusted linked wallet is allowed."
75419
75772
  ], parsed.booleans.has("--yes"));
75420
75773
  if (!confirmed)
@@ -75478,9 +75831,9 @@ async function hoodTransfer(ctx, args) {
75478
75831
  const destination = await resolveDestination(ctx, key, wallet, to, "hood");
75479
75832
  const decimals = asset ? HOOD_BASES[asset]?.decimals ?? 18 : await hoodDecimals({ chain: "hood", asset: tokenAddress }, lazyEvmRpc(ctx, flags["--rpc-url"]));
75480
75833
  const requested = isMax ? "max" : rawAmount(flags["--amount"], decimals);
75481
- const amountText = isMax ? `the full spendable balance of ${label}` : `${flags["--amount"]} ${label}`;
75834
+ const amountText2 = isMax ? `the full spendable balance of ${label}` : `${flags["--amount"]} ${label}`;
75482
75835
  const confirmed = await confirmTransfer(ctx, [
75483
- `Transfer ${amountText} on Hood from ${wallet.address} (${wallet.id}) to ${describeDestination(destination)}`,
75836
+ `Transfer ${amountText2} on Hood from ${wallet.address} (${wallet.id}) to ${describeDestination(destination)}`,
75484
75837
  destination.kind === "vault" ? "Destination: this wallet's own vault, pinned when it was imported." : destination.kind === "linked" ? "Destination: a linked wallet on this account. Candle allows it only if you linked it while signed in or marked it trusted, only for ETH or USDG, and the amount counts against this key's spend caps." : "Destination: an address Candle will classify at build; from a TEE wallet only its vault or a trusted linked wallet is allowed.",
75485
75838
  ...asset === "ETH" ? [
75486
75839
  "ETH keeps this send's gas and the sweep-home gas reserve in the wallet; Candle refuses an amount that would spend them."
@@ -83982,8 +84335,10 @@ var COMMANDS = {
83982
84335
  swap: { bare: swap, subcommands: { status: swapStatus } },
83983
84336
  transfer: { bare: transfer },
83984
84337
  launch: { bare: launch },
84338
+ image: { subcommands: { upload: imageUpload } },
83985
84339
  pnl: { bare: pnl },
83986
84340
  portfolio: { bare: portfolio },
84341
+ fees: { bare: feesList, subcommands: { claim: feesClaim } },
83987
84342
  plans: { bare: plans },
83988
84343
  lp: { subcommands: { pools: lpPools, add: lpAdd, positions: lpPositions, remove: lpRemove, claim: lpClaim } },
83989
84344
  perps: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@candledottv/cli",
3
- "version": "0.11.22",
3
+ "version": "0.11.24",
4
4
  "description": "The Candle CLI: authorize a device from your browser, then manage API keys, wallets, and setup health from the terminal",
5
5
  "type": "module",
6
6
  "bin": {