@candledottv/cli 0.11.23 → 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 +210 -33
- 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 {
|
|
@@ -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;
|
|
@@ -63208,7 +63211,7 @@ init_render();
|
|
|
63208
63211
|
init_secret_store();
|
|
63209
63212
|
|
|
63210
63213
|
// src/version.ts
|
|
63211
|
-
var CLI_VERSION = "0.11.
|
|
63214
|
+
var CLI_VERSION = "0.11.24";
|
|
63212
63215
|
|
|
63213
63216
|
// src/commands/auth.ts
|
|
63214
63217
|
init_keys_embedded_wallet();
|
|
@@ -63879,15 +63882,19 @@ var HELP = {
|
|
|
63879
63882
|
launch: {
|
|
63880
63883
|
group: "Trade",
|
|
63881
63884
|
summary: "Create a token on Solana or Hood, with an optional first buy",
|
|
63882
|
-
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.",
|
|
63883
63886
|
usage: [
|
|
63884
|
-
"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]"
|
|
63885
63888
|
],
|
|
63886
63889
|
rows: [],
|
|
63887
63890
|
flags: [
|
|
63888
63891
|
{ invocation: "--name <name>", description: "The token's name" },
|
|
63889
63892
|
{ invocation: "--symbol <symbol>", description: "The token's ticker" },
|
|
63890
|
-
{
|
|
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" },
|
|
63891
63898
|
{ invocation: "--wallet <tee>", description: "The TEE wallet that creates it" },
|
|
63892
63899
|
{
|
|
63893
63900
|
invocation: "--buy <amount>",
|
|
@@ -63900,12 +63907,27 @@ var HELP = {
|
|
|
63900
63907
|
}
|
|
63901
63908
|
],
|
|
63902
63909
|
examples: [
|
|
63910
|
+
"candle launch --name Demo --symbol DEMO --image ./logo.png --wallet AgentOne",
|
|
63903
63911
|
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne",
|
|
63904
63912
|
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet HoodAgent --dex-version v4",
|
|
63905
63913
|
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne --buy 0.1"
|
|
63906
63914
|
],
|
|
63907
63915
|
env: ENV_API
|
|
63908
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
|
+
},
|
|
63909
63931
|
portfolio: {
|
|
63910
63932
|
group: "Account",
|
|
63911
63933
|
summary: "Every wallet's holdings, prices and value on Solana and Hood (vault read over your own RPCs)",
|
|
@@ -69950,6 +69972,121 @@ async function help(args, ctx) {
|
|
|
69950
69972
|
return 0;
|
|
69951
69973
|
}
|
|
69952
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
|
+
|
|
69953
70090
|
// src/index.ts
|
|
69954
70091
|
init_keys();
|
|
69955
70092
|
|
|
@@ -70468,9 +70605,9 @@ init_render();
|
|
|
70468
70605
|
init_solana_endpoint();
|
|
70469
70606
|
init_solana_lite();
|
|
70470
70607
|
init_trading();
|
|
70471
|
-
init_swap();
|
|
70472
70608
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
70473
|
-
|
|
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.";
|
|
70474
70611
|
var QUOTE_CHAIN = {
|
|
70475
70612
|
sol: "solana",
|
|
70476
70613
|
usdc: "solana",
|
|
@@ -70529,6 +70666,7 @@ async function launch(args, ctx) {
|
|
|
70529
70666
|
valueFlags: [
|
|
70530
70667
|
"--name",
|
|
70531
70668
|
"--symbol",
|
|
70669
|
+
"--image",
|
|
70532
70670
|
"--image-url",
|
|
70533
70671
|
"--description",
|
|
70534
70672
|
"--wallet",
|
|
@@ -70539,7 +70677,8 @@ async function launch(args, ctx) {
|
|
|
70539
70677
|
"--dex-version",
|
|
70540
70678
|
"--buy"
|
|
70541
70679
|
],
|
|
70542
|
-
booleanFlags: ["--yes"]
|
|
70680
|
+
booleanFlags: ["--yes"],
|
|
70681
|
+
pathFlags: ["--image"]
|
|
70543
70682
|
});
|
|
70544
70683
|
if ("error" in parsed) {
|
|
70545
70684
|
writeUsageFailure(ctx.deps, parsed.error, ctx.json);
|
|
@@ -70547,7 +70686,15 @@ async function launch(args, ctx) {
|
|
|
70547
70686
|
}
|
|
70548
70687
|
const flags = parsed.values;
|
|
70549
70688
|
const id = flags["--client-trade-id"] ?? `launch-${randomUUID2()}`;
|
|
70550
|
-
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") {
|
|
70551
70698
|
writeUsageFailure(ctx.deps, USAGE2, ctx.json);
|
|
70552
70699
|
return 2;
|
|
70553
70700
|
}
|
|
@@ -70559,6 +70706,13 @@ async function launch(args, ctx) {
|
|
|
70559
70706
|
writeUsageFailure(ctx.deps, `A Hood launch needs --dex-version v3|v4. ${USAGE2}`, ctx.json);
|
|
70560
70707
|
return 2;
|
|
70561
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
|
+
}
|
|
70562
70716
|
const key = await tradingKey(ctx);
|
|
70563
70717
|
const prior = await lookupOperation(ctx, key, id, "launch");
|
|
70564
70718
|
if (prior) {
|
|
@@ -70576,14 +70730,27 @@ async function launch(args, ctx) {
|
|
|
70576
70730
|
return printTradingResult(ctx, prior);
|
|
70577
70731
|
}
|
|
70578
70732
|
const wallet = await tradingWallet(ctx, key, flags["--wallet"], "launch:write", hinted ?? "any");
|
|
70579
|
-
|
|
70580
|
-
|
|
70581
|
-
|
|
70582
|
-
|
|
70583
|
-
|
|
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
|
+
`);
|
|
70584
70750
|
}
|
|
70585
|
-
const
|
|
70586
|
-
|
|
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") });
|
|
70587
70754
|
if (!await claimOperation(ctx, key, id, "launch"))
|
|
70588
70755
|
throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this launch id; no write was resent.");
|
|
70589
70756
|
ctx.deps.stderr.write(`Operation: ${id}
|
|
@@ -70594,7 +70761,7 @@ async function launch(args, ctx) {
|
|
|
70594
70761
|
buyAmount: buy ? buy.raw : 0,
|
|
70595
70762
|
name: flags["--name"],
|
|
70596
70763
|
symbol: flags["--symbol"],
|
|
70597
|
-
imageUrl
|
|
70764
|
+
imageUrl,
|
|
70598
70765
|
linkedWalletId: wallet.id,
|
|
70599
70766
|
...flags["--description"] ? { description: flags["--description"] } : {},
|
|
70600
70767
|
...flags["--quote-asset"] ? { quoteAsset: flags["--quote-asset"] } : {},
|
|
@@ -70616,7 +70783,14 @@ async function launch(args, ctx) {
|
|
|
70616
70783
|
};
|
|
70617
70784
|
ctx.deps.stderr.write(buyNotice(buy, "solana"));
|
|
70618
70785
|
if (!await confirmQuote(ctx, quote, parsed.booleans.has("--yes")))
|
|
70619
|
-
return printTradingResult(ctx, {
|
|
70786
|
+
return printTradingResult(ctx, {
|
|
70787
|
+
success: true,
|
|
70788
|
+
status: "cancelled",
|
|
70789
|
+
clientTradeId: id,
|
|
70790
|
+
kind: "launch",
|
|
70791
|
+
quote,
|
|
70792
|
+
...used
|
|
70793
|
+
});
|
|
70620
70794
|
if (!Number.isFinite(built.expiresAt) || built.expiresAt <= ctx.deps.now())
|
|
70621
70795
|
throw new TradingError("QUOTE_EXPIRED", "The launch build expired before signing.");
|
|
70622
70796
|
const signed = await relaySign(ctx, key, wallet, built.transaction);
|
|
@@ -70634,7 +70808,7 @@ async function launch(args, ctx) {
|
|
|
70634
70808
|
ctx.deps.stderr.write(`Launch signature: ${signature}
|
|
70635
70809
|
`);
|
|
70636
70810
|
const result = await request(ctx, key, "/api/v1/launch/self/confirm", { clientLaunchId: id, signature });
|
|
70637
|
-
return printTradingResult(ctx, { ...result, clientTradeId: id, kind: "launch", quote });
|
|
70811
|
+
return printTradingResult(ctx, { ...result, clientTradeId: id, kind: "launch", quote, ...used });
|
|
70638
70812
|
} catch (error) {
|
|
70639
70813
|
return tradingFailure(ctx, error, id);
|
|
70640
70814
|
}
|
|
@@ -70652,7 +70826,7 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
70652
70826
|
buyAmount: buy ? buy.raw : 0,
|
|
70653
70827
|
name: flags["--name"],
|
|
70654
70828
|
symbol: flags["--symbol"],
|
|
70655
|
-
imageUrl:
|
|
70829
|
+
imageUrl: args.imageUrl,
|
|
70656
70830
|
linkedWalletId: wallet.id,
|
|
70657
70831
|
dexVersion: flags["--dex-version"],
|
|
70658
70832
|
...flags["--description"] ? { description: flags["--description"] } : {},
|
|
@@ -70698,7 +70872,8 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
70698
70872
|
chain: "hood",
|
|
70699
70873
|
quote,
|
|
70700
70874
|
operationId: first.operationId,
|
|
70701
|
-
walletHeldUntil: first.expiresAt
|
|
70875
|
+
walletHeldUntil: first.expiresAt,
|
|
70876
|
+
...args.used
|
|
70702
70877
|
});
|
|
70703
70878
|
}
|
|
70704
70879
|
const confirmedPlan = buy ? first.plannedLegCount === 3 ? ["createCurve", "feeTransfer", "trade"] : ["createCurve", "trade"] : undefined;
|
|
@@ -70731,7 +70906,8 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
70731
70906
|
quote,
|
|
70732
70907
|
wallet: safeText(wallet.address),
|
|
70733
70908
|
operationId: first.operationId,
|
|
70734
|
-
landedLegs: run2.landed
|
|
70909
|
+
landedLegs: run2.landed,
|
|
70910
|
+
...args.used
|
|
70735
70911
|
});
|
|
70736
70912
|
}
|
|
70737
70913
|
function hoodLaterLegsGas(count, buys) {
|
|
@@ -72096,10 +72272,10 @@ async function perpsOrders(args, ctx) {
|
|
|
72096
72272
|
if (ctx.json)
|
|
72097
72273
|
return printTradingResult(ctx, result);
|
|
72098
72274
|
const out = ctx.deps.stdout;
|
|
72099
|
-
const
|
|
72100
|
-
out.write(`Open on Hyperliquid (${
|
|
72275
|
+
const open5 = result.open ?? [];
|
|
72276
|
+
out.write(`Open on Hyperliquid (${open5.length}):
|
|
72101
72277
|
`);
|
|
72102
|
-
for (const o of
|
|
72278
|
+
for (const o of open5) {
|
|
72103
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")}
|
|
72104
72280
|
`);
|
|
72105
72281
|
}
|
|
@@ -73171,13 +73347,13 @@ function writeWalletEvents(ctx, books) {
|
|
|
73171
73347
|
function writeAgents(ctx, agents, all) {
|
|
73172
73348
|
if (!agents || agents.length === 0)
|
|
73173
73349
|
return;
|
|
73174
|
-
const
|
|
73350
|
+
const open5 = (count2, unmarked) => `${count2}${unmarked > 0 ? ` (${unmarked} unpriced)` : ""}`;
|
|
73175
73351
|
const rows = agents.map((a) => [
|
|
73176
73352
|
a.keyPrefix === null ? "Manual (web app)" : a.label && a.label !== a.keyPrefix ? `${a.label} (${a.keyPrefix})` : a.keyPrefix,
|
|
73177
73353
|
formatUsd(a.realizedNetUsd),
|
|
73178
73354
|
formatUsd(a.unrealizedUsd),
|
|
73179
73355
|
formatUsd(totalOf(a)),
|
|
73180
|
-
|
|
73356
|
+
open5(a.openPositions, a.unmarked),
|
|
73181
73357
|
String(a.closedRounds)
|
|
73182
73358
|
]);
|
|
73183
73359
|
rows.push([
|
|
@@ -73185,7 +73361,7 @@ function writeAgents(ctx, agents, all) {
|
|
|
73185
73361
|
formatUsd(all.realizedNetUsd),
|
|
73186
73362
|
formatUsd(all.unrealizedUsd),
|
|
73187
73363
|
formatUsd(totalOf(all)),
|
|
73188
|
-
|
|
73364
|
+
open5(all.openPositions, all.unmarked),
|
|
73189
73365
|
all.closedRounds !== undefined ? String(all.closedRounds) : "-"
|
|
73190
73366
|
]);
|
|
73191
73367
|
ctx.deps.stdout.write(`
|
|
@@ -84159,6 +84335,7 @@ var COMMANDS = {
|
|
|
84159
84335
|
swap: { bare: swap, subcommands: { status: swapStatus } },
|
|
84160
84336
|
transfer: { bare: transfer },
|
|
84161
84337
|
launch: { bare: launch },
|
|
84338
|
+
image: { subcommands: { upload: imageUpload } },
|
|
84162
84339
|
pnl: { bare: pnl },
|
|
84163
84340
|
portfolio: { bare: portfolio },
|
|
84164
84341
|
fees: { bare: feesList, subcommands: { claim: feesClaim } },
|
package/package.json
CHANGED