@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.
- package/README.md +14 -2
- package/dist/index.js +394 -39
- 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.
|
|
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 = {
|
|
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.
|
|
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
|
|
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:
|
|
61569
|
+
ready: unreadable2.length === 0 ? true : undefined,
|
|
61567
61570
|
notice: tierBody?.maxExpired ? tierBody.maxExpiredNotice : undefined,
|
|
61568
61571
|
updateAvailable: updateAvailable() ?? undefined,
|
|
61569
|
-
unreadable:
|
|
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
|
-
...
|
|
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.
|
|
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
|
-
{
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
70405
|
-
|
|
70406
|
-
|
|
70407
|
-
|
|
70408
|
-
|
|
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
|
|
70411
|
-
|
|
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
|
|
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, {
|
|
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:
|
|
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
|
|
71923
|
-
out.write(`Open on Hyperliquid (${
|
|
72275
|
+
const open5 = result.open ?? [];
|
|
72276
|
+
out.write(`Open on Hyperliquid (${open5.length}):
|
|
71924
72277
|
`);
|
|
71925
|
-
for (const o of
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
75768
|
+
const amountText2 = isMax ? `the full spendable balance of ${label}` : `${flags["--amount"]} ${label}`;
|
|
75416
75769
|
const confirmed = await confirmTransfer(ctx, [
|
|
75417
|
-
`Transfer ${
|
|
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
|
|
75834
|
+
const amountText2 = isMax ? `the full spendable balance of ${label}` : `${flags["--amount"]} ${label}`;
|
|
75482
75835
|
const confirmed = await confirmTransfer(ctx, [
|
|
75483
|
-
`Transfer ${
|
|
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