@candledottv/cli 0.11.39 → 0.11.41
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 +3 -3
- package/dist/index.js +461 -93
- 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.41): 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,7 @@ 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 <path>\|--image-url <url> --wallet <tee
|
|
125
|
+
| `candle launch --name <name> --symbol <symbol> --image <path>\|--image-url <url> --wallet <tee>\|embedded [--banner <file\|URL>] [--buy <amount>] [--quote-asset <asset>] [--dex-version v3\|v4] [--mode <mode>] [--curve-v <V> --curve-g <G>] [--creator-fee-bps 0\|100\|200] [--pool-fee 25\|50\|100] [--pool-open legacy\|curve_price] [--exp-pool-shape floor\|full_range] [--dry-run]` | 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. `--banner <file|URL>` adds a wide banner from a local file or an https URL. Local banners use `kind=banner`, retain their original shape (wider than 1.5:1), and become public and permanent before confirmation; the result reports `bannerUrl`. 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: as its own leg in one Jito bundle with the launch 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. `--wallet embedded --buy <amount>` launches from the account's embedded wallet instead, on Solana, as an atomic launch: Candle's dry run first (nothing built or recorded), the quote to confirm, then one build and one `/launch/atomic/submit`, with Candle signing both legs into one Jito bundle. It needs Pro or Max, `launch:write` and `swap:write`, and does not depend on `TEE_LAUNCH_BUYS_ENABLED`. Without a first buy, every plan can launch from the embedded wallet with the MCP tool `candle_launch_token` 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. `--mode` picks the curve: `open` or `exclusive`, `test-open` or `test-exclusive` where the server has `ENABLE_TEST_CURVES` on, or on Solana with sol or cndl `exp-a`, `exp-b`, `exp-c` and `exp-custom`, which takes `--curve-v <V>` and `--curve-g <G>` in whole units of the quote asset where the server has `EXP_CUSTOM_CURVES` on. `--creator-fee-bps 100` or `200` adds an extra creator fee on an `exp-` mode. On an `exp-` curve the first buy is at most 0.5 SOL (2,000,000 CNDL), or 10% of the threshold when that is lower. Solana pool choices: `--pool-fee 25|50|100` (bps; add `--pool-fee-from <bps> --pool-fee-minutes <min>` for an anti-snipe fall to that rate, at most 2500 bps over 30 minutes), `--pool-open legacy|curve_price` (preset tiers) and `--exp-pool-shape full_range` (exp- curves). For Solana preset tiers, omitting `--pool-open` defaults to `curve_price` (opens at the curve's last price and burns the remaining base) when `stakerAllocationBps` plus the team allocation fit the tier's limit (`graduation.maxStakerCarveBps` in the dry run); otherwise it falls back to `legacy` without refusal. Use `--pool-open legacy` to opt out. Explicit `curve_price` is refused above the limit. Experimental curves and Hood are unchanged. `--dry-run` prints Candle's preview of the launch (the curve's terms, the first buy's share, the fees, how the pool opens at graduation, the dev-buy ceiling) and builds, signs and records nothing; with `--image` the upload still happens. |
|
|
126
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. |
|
|
127
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. |
|
|
128
128
|
| `candle portfolio [<filter>] [--chain solana\|hood] [--rpc-url <url>] [--evm-rpc-url <url>] [--no-perps]` | 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. A Hyperliquid section follows (each EVM TEE wallet's account, read from Hyperliquid directly, never in the total); `--no-perps` skips it. |
|
|
@@ -454,7 +454,7 @@ all returned token warnings. Missing price impact is explicitly **unavailable**,
|
|
|
454
454
|
JSON result containing the quote; the preview and operation id go to stderr. Launch creation
|
|
455
455
|
has no price impact or token receipt and shows its maximum SOL debit. Without `--buy` it makes
|
|
456
456
|
no first buy; use a separate swap for that buy. With `--buy <amount>` (whole units of the quote
|
|
457
|
-
asset, as `--amount` is for a swap) the same wallet buys at launch: in the launch
|
|
457
|
+
asset, as `--amount` is for a swap) the same wallet buys at launch: as its own leg in one Jito bundle with the launch on
|
|
458
458
|
Solana, as a last leg after the curve and the fee on Hood. Candle reserves the buy against the
|
|
459
459
|
key's spend window before anything is signed. `--buy` needs the server's
|
|
460
460
|
`TEE_LAUNCH_BUYS_ENABLED=1`, which is off by default; while it is off Candle refuses the buy
|
package/dist/index.js
CHANGED
|
@@ -19050,14 +19050,59 @@ var init_protocol = __esm(() => {
|
|
|
19050
19050
|
PASSKEY_ASSOCIATED_DOMAIN = `webcredentials:${PASSKEY_RP_ID}`;
|
|
19051
19051
|
});
|
|
19052
19052
|
|
|
19053
|
+
// release-homes.json
|
|
19054
|
+
var release_homes_default;
|
|
19055
|
+
var init_release_homes = __esm(() => {
|
|
19056
|
+
release_homes_default = {
|
|
19057
|
+
note: "Where each CLI version is released from (spec docs/superpowers/specs/2026-10-23-candle-github-spec.md, section 9). Versions up to and including bridgeVersion are released, signed and verified in `legacy`; every later version in `current`. src/release.ts compiles this in, install.sh carries the same three values (a drift test holds them equal), release.yaml refuses a tag pushed in the wrong home, and sync-agentic.yaml never tags a version above bridgeVersion. Change it only together with a release plan.",
|
|
19058
|
+
bridgeVersion: "0.12.0",
|
|
19059
|
+
legacy: "candledottv/agentic",
|
|
19060
|
+
current: "candledottv/cli"
|
|
19061
|
+
};
|
|
19062
|
+
});
|
|
19063
|
+
|
|
19053
19064
|
// src/release.ts
|
|
19054
19065
|
function isPlainVersion(value) {
|
|
19055
19066
|
return VERSION.test(value);
|
|
19056
19067
|
}
|
|
19057
|
-
function
|
|
19068
|
+
function parseReleaseHomes(value) {
|
|
19069
|
+
const homes = value;
|
|
19070
|
+
const { bridgeVersion, legacy, current } = homes ?? {};
|
|
19071
|
+
if (typeof bridgeVersion !== "string" || !VERSION.test(bridgeVersion)) {
|
|
19072
|
+
throw new Error("release-homes.json: bridgeVersion must be three dot-separated numbers");
|
|
19073
|
+
}
|
|
19074
|
+
if (typeof legacy !== "string" || !REPOSITORY.test(legacy))
|
|
19075
|
+
throw new Error("release-homes.json: bad legacy");
|
|
19076
|
+
if (typeof current !== "string" || !REPOSITORY.test(current))
|
|
19077
|
+
throw new Error("release-homes.json: bad current");
|
|
19078
|
+
if (legacy === current)
|
|
19079
|
+
throw new Error("release-homes.json: legacy and current must differ");
|
|
19080
|
+
return { bridgeVersion, legacy, current };
|
|
19081
|
+
}
|
|
19082
|
+
function homeRepository(home) {
|
|
19083
|
+
return home === "legacy" ? LEGACY_RELEASE_REPOSITORY : CURRENT_RELEASE_REPOSITORY;
|
|
19084
|
+
}
|
|
19085
|
+
function homeUrl(home) {
|
|
19086
|
+
return `https://github.com/${homeRepository(home)}`;
|
|
19087
|
+
}
|
|
19088
|
+
function identityRegex(home) {
|
|
19089
|
+
return `^https://github.com/${homeRepository(home)}/\\.github/workflows/release\\.yaml@refs/tags/cli-v`;
|
|
19090
|
+
}
|
|
19091
|
+
function releaseHomeFor(version) {
|
|
19058
19092
|
if (!VERSION.test(version))
|
|
19059
19093
|
throw new Error(`invalid release version: ${version}`);
|
|
19060
|
-
return
|
|
19094
|
+
return compareVersions(version, BRIDGE_VERSION) <= 0 ? "legacy" : "current";
|
|
19095
|
+
}
|
|
19096
|
+
function updateHomeFor(current) {
|
|
19097
|
+
return compareVersions(current, BRIDGE_VERSION) < 0 ? "legacy" : "current";
|
|
19098
|
+
}
|
|
19099
|
+
function releaseIdentityUri(version) {
|
|
19100
|
+
const home = releaseHomeFor(version);
|
|
19101
|
+
return `https://github.com/${homeRepository(home)}/.github/workflows/release.yaml@refs/tags/cli-v${version}`;
|
|
19102
|
+
}
|
|
19103
|
+
function versionOfTag(tag) {
|
|
19104
|
+
const match = /^cli-v(\d+\.\d+\.\d+)$/.exec(tag);
|
|
19105
|
+
return match ? match[1] ?? null : null;
|
|
19061
19106
|
}
|
|
19062
19107
|
function compareVersions(a, b) {
|
|
19063
19108
|
const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
@@ -19107,8 +19152,14 @@ async function fetchLatest(deps, baseUrl) {
|
|
|
19107
19152
|
message: `Could not reach ${latestUrl(baseUrl)}: ${error instanceof Error ? error.message : String(error)}`
|
|
19108
19153
|
};
|
|
19109
19154
|
}
|
|
19110
|
-
if (!res.ok)
|
|
19111
|
-
return {
|
|
19155
|
+
if (!res.ok) {
|
|
19156
|
+
return {
|
|
19157
|
+
ok: false,
|
|
19158
|
+
kind: "unreachable",
|
|
19159
|
+
message: `${latestUrl(baseUrl)} answered ${res.status}`,
|
|
19160
|
+
status: res.status
|
|
19161
|
+
};
|
|
19162
|
+
}
|
|
19112
19163
|
let body;
|
|
19113
19164
|
try {
|
|
19114
19165
|
body = await res.json();
|
|
@@ -19126,7 +19177,7 @@ async function fetchPinned(deps, base, tag) {
|
|
|
19126
19177
|
try {
|
|
19127
19178
|
const res = await deps.fetch(url, { redirect: "follow" });
|
|
19128
19179
|
if (!res.ok)
|
|
19129
|
-
return { ok: false, kind: "unreachable", message: `${url} answered ${res.status}
|
|
19180
|
+
return { ok: false, kind: "unreachable", message: `${url} answered ${res.status}`, status: res.status };
|
|
19130
19181
|
const manifest = await res.json();
|
|
19131
19182
|
const missing = [
|
|
19132
19183
|
typeof manifest.version === "string" ? null : "version",
|
|
@@ -19145,13 +19196,39 @@ async function fetchPinned(deps, base, tag) {
|
|
|
19145
19196
|
};
|
|
19146
19197
|
}
|
|
19147
19198
|
}
|
|
19148
|
-
function releaseBaseUrl(env) {
|
|
19199
|
+
function releaseBaseUrl(env, home = "legacy") {
|
|
19149
19200
|
const override = env.CANDLE_RELEASE_BASE_URL?.trim();
|
|
19150
|
-
return override ? override.replace(/\/$/, "") :
|
|
19201
|
+
return override ? override.replace(/\/$/, "") : homeUrl(home);
|
|
19151
19202
|
}
|
|
19152
|
-
|
|
19203
|
+
function manifestHomeMismatch(manifest, home, pinnedTag) {
|
|
19204
|
+
let versionHome;
|
|
19205
|
+
try {
|
|
19206
|
+
versionHome = releaseHomeFor(manifest.version);
|
|
19207
|
+
} catch (error) {
|
|
19208
|
+
return error instanceof Error ? error.message : String(error);
|
|
19209
|
+
}
|
|
19210
|
+
if (manifest.tag !== `cli-v${manifest.version}`) {
|
|
19211
|
+
return `the manifest's tag ${manifest.tag} does not name its version ${manifest.version}`;
|
|
19212
|
+
}
|
|
19213
|
+
if (pinnedTag !== undefined && manifest.tag !== pinnedTag) {
|
|
19214
|
+
return `asked for ${pinnedTag}, the manifest describes ${manifest.tag}`;
|
|
19215
|
+
}
|
|
19216
|
+
if (versionHome !== home) {
|
|
19217
|
+
return `${manifest.version} is released from ${homeRepository(versionHome)}, not ${homeRepository(home)}`;
|
|
19218
|
+
}
|
|
19219
|
+
return null;
|
|
19220
|
+
}
|
|
19221
|
+
var VERSION, REPOSITORY, HOMES, BRIDGE_VERSION, LEGACY_RELEASE_REPOSITORY, CURRENT_RELEASE_REPOSITORY, RELEASE_ISSUER = "https://token.actions.githubusercontent.com", RELEASE_BASE_URL, RELEASE_IDENTITY_REGEX;
|
|
19153
19222
|
var init_release = __esm(() => {
|
|
19223
|
+
init_release_homes();
|
|
19154
19224
|
VERSION = /^\d+\.\d+\.\d+$/;
|
|
19225
|
+
REPOSITORY = /^candledottv\/[a-z0-9-]+$/;
|
|
19226
|
+
HOMES = parseReleaseHomes(release_homes_default);
|
|
19227
|
+
BRIDGE_VERSION = HOMES.bridgeVersion;
|
|
19228
|
+
LEGACY_RELEASE_REPOSITORY = HOMES.legacy;
|
|
19229
|
+
CURRENT_RELEASE_REPOSITORY = HOMES.current;
|
|
19230
|
+
RELEASE_BASE_URL = homeUrl("legacy");
|
|
19231
|
+
RELEASE_IDENTITY_REGEX = identityRegex("legacy");
|
|
19155
19232
|
});
|
|
19156
19233
|
|
|
19157
19234
|
// ../../node_modules/@noble/curves/esm/nist.js
|
|
@@ -37405,7 +37482,7 @@ var init_convert = __esm(() => {
|
|
|
37405
37482
|
});
|
|
37406
37483
|
|
|
37407
37484
|
// ../mcp/src/version.ts
|
|
37408
|
-
var SERVER_VERSION = "0.10.
|
|
37485
|
+
var SERVER_VERSION = "0.10.22";
|
|
37409
37486
|
|
|
37410
37487
|
// ../mcp/src/update-notice.ts
|
|
37411
37488
|
function newer(a, b) {
|
|
@@ -37954,7 +38031,7 @@ async function executeTrade(args, cfg, doFetch) {
|
|
|
37954
38031
|
const wrapped = JSON.stringify({ clientTradeId, resolved, api: posted.body });
|
|
37955
38032
|
return posted.ok ? { text: wrapped } : { text: wrapped, isError: true };
|
|
37956
38033
|
}
|
|
37957
|
-
async function executeLaunchAndSeed(args, cfg, doFetch, imageDir, imageFs) {
|
|
38034
|
+
async function executeLaunchAndSeed(args, cfg, doFetch, imageDir, imageFs, opts = {}) {
|
|
37958
38035
|
const apiKey = requireApiKey(cfg);
|
|
37959
38036
|
const clientLaunchId = args.clientLaunchId ?? randomUUID3();
|
|
37960
38037
|
const { devBuy, dryRun, buyAmount: _rawBuyAmount, ...imageFields } = args;
|
|
@@ -37968,6 +38045,12 @@ async function executeLaunchAndSeed(args, cfg, doFetch, imageDir, imageFs) {
|
|
|
37968
38045
|
return converted.err;
|
|
37969
38046
|
buyAmount = converted.raw;
|
|
37970
38047
|
}
|
|
38048
|
+
const atomic = buyAmount !== undefined && (args.chain ?? "solana") === "solana" && !opts.headlessSeed;
|
|
38049
|
+
if (atomic) {
|
|
38050
|
+
const refused2 = seedCeilingRefusal(args.quoteAsset ?? "sol", args.mode, buyAmount, clientLaunchId);
|
|
38051
|
+
if (refused2)
|
|
38052
|
+
return refused2;
|
|
38053
|
+
}
|
|
37971
38054
|
const image = await resolveLaunchImage(imageFields, imageDir, cfg, doFetch, imageFs);
|
|
37972
38055
|
if ("refused" in image)
|
|
37973
38056
|
return { text: imageRefusalBody(image.refused, { clientLaunchId }), isError: true };
|
|
@@ -37981,16 +38064,20 @@ async function executeLaunchAndSeed(args, cfg, doFetch, imageDir, imageFs) {
|
|
|
37981
38064
|
return { text: JSON.stringify({ clientLaunchId, api }), isError: true };
|
|
37982
38065
|
}
|
|
37983
38066
|
const uploaded = uploadedFields(image);
|
|
38067
|
+
const transportFailure = (thrown) => {
|
|
38068
|
+
const failed = transportError("clientLaunchId", clientLaunchId, thrown);
|
|
38069
|
+
return Object.keys(uploaded).length === 0 ? failed : { ...failed, text: JSON.stringify({ ...JSON.parse(failed.text), ...uploaded }) };
|
|
38070
|
+
};
|
|
38071
|
+
if (atomic)
|
|
38072
|
+
return launchAndSeedAtomic({ ...image.args, clientLaunchId }, buyAmount, { clientLaunchId, uploaded, dryRun: dryRun === true, transportFailure }, cfg, doFetch);
|
|
37984
38073
|
const path = dryRun ? "/api/v1/launch/headless/dry-run" : "/api/v1/launch/headless";
|
|
37985
38074
|
const posted = await postJson(`${base(cfg)}${path}`, apiKey, {
|
|
37986
38075
|
...image.args,
|
|
37987
38076
|
clientLaunchId,
|
|
37988
38077
|
...buyAmount !== undefined ? { buyAmount } : {}
|
|
37989
38078
|
}, doFetch);
|
|
37990
|
-
if ("thrown" in posted)
|
|
37991
|
-
|
|
37992
|
-
return Object.keys(uploaded).length === 0 ? failed : { ...failed, text: JSON.stringify({ ...JSON.parse(failed.text), ...uploaded }) };
|
|
37993
|
-
}
|
|
38079
|
+
if ("thrown" in posted)
|
|
38080
|
+
return transportFailure(posted.thrown);
|
|
37994
38081
|
if (!posted.ok) {
|
|
37995
38082
|
return { text: JSON.stringify({ clientLaunchId, ...uploaded, api: posted.body }), isError: true };
|
|
37996
38083
|
}
|
|
@@ -37998,25 +38085,109 @@ async function executeLaunchAndSeed(args, cfg, doFetch, imageDir, imageFs) {
|
|
|
37998
38085
|
return { text: JSON.stringify({ clientLaunchId, dryRun: true, ...uploaded, api: posted.body }) };
|
|
37999
38086
|
}
|
|
38000
38087
|
const launch = posted.body;
|
|
38001
|
-
|
|
38002
|
-
|
|
38088
|
+
const { market, note } = await readLaunchedMarket(launch.chain ?? "solana", launch.mint, cfg, doFetch);
|
|
38089
|
+
return {
|
|
38090
|
+
text: JSON.stringify({ clientLaunchId, ...uploaded, launch, market, ...note ? { note } : {} })
|
|
38091
|
+
};
|
|
38092
|
+
}
|
|
38093
|
+
async function readLaunchedMarket(chain2, mint, cfg, doFetch) {
|
|
38094
|
+
const note = "launch confirmed; the follow-up market read failed, read it via candle_get_market";
|
|
38003
38095
|
try {
|
|
38004
|
-
if (
|
|
38096
|
+
if (mint === undefined)
|
|
38005
38097
|
throw new Error("the launch response carried no mint");
|
|
38006
|
-
const marketRes = await doFetch(`${base(cfg)}/api/v1/markets/${encodeURIComponent(
|
|
38098
|
+
const marketRes = await doFetch(`${base(cfg)}/api/v1/markets/${encodeURIComponent(chain2)}/${encodeURIComponent(mint)}`, {
|
|
38007
38099
|
method: "GET",
|
|
38008
38100
|
headers: headers()
|
|
38009
38101
|
});
|
|
38010
|
-
if (marketRes.ok)
|
|
38011
|
-
|
|
38012
|
-
|
|
38013
|
-
note = "launch confirmed; the follow-up market read failed, read it via candle_get_market";
|
|
38014
|
-
}
|
|
38102
|
+
if (!marketRes.ok)
|
|
38103
|
+
return { market: null, note };
|
|
38104
|
+
return { market: JSON.parse(await marketRes.text()).market ?? null };
|
|
38015
38105
|
} catch {
|
|
38016
|
-
|
|
38106
|
+
return { market: null, note };
|
|
38107
|
+
}
|
|
38108
|
+
}
|
|
38109
|
+
function seedCeilingRefusal(quoteAsset, mode, buyAmount, clientLaunchId) {
|
|
38110
|
+
const quote = quoteAsset.toLowerCase();
|
|
38111
|
+
const ceiling = SOLANA_SEED_CEILINGS[quote];
|
|
38112
|
+
if (!ceiling)
|
|
38113
|
+
return;
|
|
38114
|
+
const expCap = ceiling.expRaw;
|
|
38115
|
+
const preset = mode === "exp-a" || mode === "exp-b" || mode === "exp-c";
|
|
38116
|
+
const effective = preset && expCap !== undefined && expCap < ceiling.raw ? expCap : ceiling.raw;
|
|
38117
|
+
if (BigInt(buyAmount) <= effective)
|
|
38118
|
+
return;
|
|
38119
|
+
if (preset && expCap !== undefined) {
|
|
38120
|
+
return {
|
|
38121
|
+
text: JSON.stringify({
|
|
38122
|
+
clientLaunchId,
|
|
38123
|
+
success: false,
|
|
38124
|
+
error: {
|
|
38125
|
+
code: "DEV_BUY_TOO_HIGH",
|
|
38126
|
+
message: `buyAmount exceeds this launch's dev-buy ceiling of ${effective} ${ceiling.unit}: at most ${ceiling.raw} ${ceiling.unit}, or 10% of the threshold when that is lower`,
|
|
38127
|
+
field: "devBuy",
|
|
38128
|
+
retryable: false
|
|
38129
|
+
}
|
|
38130
|
+
}),
|
|
38131
|
+
isError: true
|
|
38132
|
+
};
|
|
38017
38133
|
}
|
|
38018
38134
|
return {
|
|
38019
|
-
text: JSON.stringify({
|
|
38135
|
+
text: JSON.stringify({
|
|
38136
|
+
clientLaunchId,
|
|
38137
|
+
success: false,
|
|
38138
|
+
error: {
|
|
38139
|
+
code: "DEV_BUY_TOO_HIGH",
|
|
38140
|
+
message: `buyAmount exceeds the per-launch dev-buy ceiling of ${effective} ${ceiling.unit} for ${quote} on solana`,
|
|
38141
|
+
field: "devBuy",
|
|
38142
|
+
retryable: false
|
|
38143
|
+
}
|
|
38144
|
+
}),
|
|
38145
|
+
isError: true
|
|
38146
|
+
};
|
|
38147
|
+
}
|
|
38148
|
+
async function launchAndSeedAtomic(launchBody, buyAmount, ctx, cfg, doFetch) {
|
|
38149
|
+
const apiKey = requireApiKey(cfg);
|
|
38150
|
+
const { clientLaunchId, uploaded } = ctx;
|
|
38151
|
+
const failed = (api, extra = {}) => ({
|
|
38152
|
+
text: JSON.stringify({ clientLaunchId, ...uploaded, ...extra, api }),
|
|
38153
|
+
isError: true
|
|
38154
|
+
});
|
|
38155
|
+
const body = { ...launchBody, payer: MAIN_PAYER, firstBuys: [{ payer: MAIN_PAYER, amountRaw: buyAmount }] };
|
|
38156
|
+
const built = await postJson(`${base(cfg)}/api/v1/launch/atomic/${ctx.dryRun ? "dry-run" : "build"}`, apiKey, body, doFetch);
|
|
38157
|
+
if ("thrown" in built)
|
|
38158
|
+
return ctx.transportFailure(built.thrown);
|
|
38159
|
+
if (!built.ok)
|
|
38160
|
+
return failed(built.body);
|
|
38161
|
+
if (ctx.dryRun)
|
|
38162
|
+
return { text: JSON.stringify({ clientLaunchId, dryRun: true, ...uploaded, api: built.body }) };
|
|
38163
|
+
const bundle = built.body;
|
|
38164
|
+
if (typeof bundle.bundleId !== "string" || !Array.isArray(bundle.legs) || bundle.legs.length !== 2 || bundle.legs.some((leg) => leg?.signer !== "server"))
|
|
38165
|
+
return failed(built.body, {
|
|
38166
|
+
success: false,
|
|
38167
|
+
error: {
|
|
38168
|
+
code: "MCP_INVALID_RESPONSE",
|
|
38169
|
+
message: "Candle did not build this launch and its seed as two legs it signs from the embedded wallet; nothing was sent. Retry with the same clientLaunchId to build it again."
|
|
38170
|
+
}
|
|
38171
|
+
});
|
|
38172
|
+
const bundleId = bundle.bundleId;
|
|
38173
|
+
const submitted = await postJson(`${base(cfg)}/api/v1/launch/atomic/submit`, apiKey, { bundleId, signedTxsBase64: [] }, doFetch);
|
|
38174
|
+
if ("thrown" in submitted)
|
|
38175
|
+
return ctx.transportFailure(submitted.thrown);
|
|
38176
|
+
const outcome = submitted.body;
|
|
38177
|
+
if (!submitted.ok || outcome.status !== "landed") {
|
|
38178
|
+
const next = outcome.status === "failed" ? "Neither the launch nor the seed is on chain. Retry with the same clientLaunchId: Candle starts a clean attempt under it." : outcome.status === "timeout" ? "The bundle is unresolved and may still land. Call candle_get_operation with this clientLaunchId and kind launch; never relaunch under a new id, which would be a second token." : undefined;
|
|
38179
|
+
return failed(submitted.body, { bundleId, ...next ? { next } : {} });
|
|
38180
|
+
}
|
|
38181
|
+
const { market, note } = await readLaunchedMarket("solana", outcome.mint, cfg, doFetch);
|
|
38182
|
+
return {
|
|
38183
|
+
text: JSON.stringify({
|
|
38184
|
+
clientLaunchId,
|
|
38185
|
+
bundleId,
|
|
38186
|
+
...uploaded,
|
|
38187
|
+
launch: submitted.body,
|
|
38188
|
+
market,
|
|
38189
|
+
...note ? { note } : {}
|
|
38190
|
+
})
|
|
38020
38191
|
};
|
|
38021
38192
|
}
|
|
38022
38193
|
async function executeSweep(args, cfg, doFetch) {
|
|
@@ -38127,11 +38298,17 @@ async function executionStatus(cfg, doFetch) {
|
|
|
38127
38298
|
...unreadable.length > 0 ? { isError: true } : {}
|
|
38128
38299
|
};
|
|
38129
38300
|
}
|
|
38130
|
-
var SWEEP_BASE_ASSETS;
|
|
38301
|
+
var SOLANA_SEED_CEILINGS, MAIN_PAYER, SWEEP_BASE_ASSETS;
|
|
38131
38302
|
var init_orchestrate = __esm(() => {
|
|
38132
38303
|
init_convert();
|
|
38133
38304
|
init_image();
|
|
38134
38305
|
init_update_notice();
|
|
38306
|
+
SOLANA_SEED_CEILINGS = {
|
|
38307
|
+
sol: { raw: 500000000n, unit: "lamports", expRaw: 500000000n },
|
|
38308
|
+
usdc: { raw: 500000000n, unit: "USDC raw units" },
|
|
38309
|
+
cndl: { raw: 2000000000000n, unit: "CNDL raw units", expRaw: 33000000000n }
|
|
38310
|
+
};
|
|
38311
|
+
MAIN_PAYER = { type: "main" };
|
|
38135
38312
|
SWEEP_BASE_ASSETS = {
|
|
38136
38313
|
solana: ["USDC", "CNDL", "SOL"],
|
|
38137
38314
|
hood: ["USDG", "ETH"]
|
|
@@ -42567,10 +42744,12 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
|
|
|
42567
42744
|
});
|
|
42568
42745
|
register("candle_launch_and_seed", {
|
|
42569
42746
|
title: "Launch a token and seed it",
|
|
42570
|
-
description: "Launch a new token from the account's embedded wallet, with an optional dev-buy seed
|
|
42747
|
+
description: "Launch a new token from the account's embedded wallet, with an optional dev-buy seed, then " + "return the fresh market state and token links in one result. On solana a seed makes this " + (options.headlessSeed === true ? "the seed is a separate first-buy transaction in the same Jito bundle as the launch, so " + "both land or neither does. That works on every plan, Free included, and needs no " + "swap:write. On hood the seed is " : "the atomic launch: the launch and the seed are two transactions Candle signs and sends as " + "one Jito bundle, so both land or neither does. That needs Pro or Max (TIER_REQUIRED " + "otherwise) and swap:write on the key, and the result carries bundleId. On hood the seed is ") + "a best-effort follow-up. A launch with no seed works on every plan, Free included. MOVES " + "REAL FUNDS unless dryRun; dry-run first, which spends nothing. Seeds above the platform " + "dev-buy ceiling are rejected (DEV_BUY_TOO_HIGH): at most 0.5 SOL / 500 USDC / 2,000,000 " + "CNDL, or on an exp- curve 10% of the threshold when that is lower; the server holds the " + "creator's own seed to the effective ceiling, so launch with the largest devBuy it allows " + "and top up with candle_trade. " + CUSTOM_CURVE_RULE + (imageDir ? IMAGE_PATH_DISCLOSURE : ""),
|
|
42571
42748
|
inputSchema: imageDir ? localLaunchAndSeedShape : launchAndSeedShape
|
|
42572
42749
|
}, async (args) => {
|
|
42573
|
-
const result = await executeLaunchAndSeed(args, getConfig(), doFetch, imageDir
|
|
42750
|
+
const result = await executeLaunchAndSeed(args, getConfig(), doFetch, imageDir, undefined, {
|
|
42751
|
+
headlessSeed: options.headlessSeed === true
|
|
42752
|
+
});
|
|
42574
42753
|
return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
|
|
42575
42754
|
});
|
|
42576
42755
|
const marketRead = createPerpsMarketExecutor(getConfig, env, doFetch);
|
|
@@ -42833,7 +43012,7 @@ var init_tools = __esm(() => {
|
|
|
42833
43012
|
minutes: exports_external.number().positive().max(30)
|
|
42834
43013
|
}).strict()
|
|
42835
43014
|
]).optional().describe("Solana only: the graduated pool's fee. { tier: 25 | 50 | 100 } is a flat fee in bps (default 50), or " + "{ startBps, endBps, minutes } is an anti-snipe fall from startBps (at most 2500, at least endBps) to " + "endBps (25, 50 or 100) over minutes (at most 30). Cannot be combined with creatorFeeBps unless the tier is 50. " + "Omit for the standard 50 bps pool"),
|
|
42836
|
-
poolOpen: exports_external.enum(["legacy", "curve_price"]).optional().describe(`Solana preset tiers only. "curve_price" opens the pool at the curve's last price (the whole reserve is ` + "seeded and the remaining base burned); stakerAllocationBps plus the team allocation must fit the tier's limit, " + "which the dry run returns as graduation.maxStakerCarveBps.
|
|
43015
|
+
poolOpen: exports_external.enum(["legacy", "curve_price"]).optional().describe(`Solana preset tiers only. "curve_price" opens the pool at the curve's last price (the whole reserve is ` + "seeded and the remaining base burned); stakerAllocationBps plus the team allocation must fit the tier's limit, " + "which the dry run returns as graduation.maxStakerCarveBps. Omitted defaults to curve_price when those " + "allocations fit, otherwise falls back to legacy without refusal. Set legacy explicitly to opt out. " + "Explicit curve_price is refused when the allocations exceed the limit"),
|
|
42837
43016
|
expPoolShape: exports_external.enum(["floor", "full_range"]).optional().describe('Solana exp- curves only. "full_range" opens the pool across the whole price range. Omit for floor'),
|
|
42838
43017
|
dexVersion: exports_external.string().nullable().optional().describe('"v3" or "v4"; required for hood launches'),
|
|
42839
43018
|
buyAmount: exports_external.union([exports_external.string(), exports_external.number()]).optional().describe("Initial buy, in quote base units (string or number)"),
|
|
@@ -42950,7 +43129,7 @@ var init_tools = __esm(() => {
|
|
|
42950
43129
|
({ buyAmount: _rawBuyAmount, ...seedableLaunchShape } = launchTokenShape);
|
|
42951
43130
|
seedShape = {
|
|
42952
43131
|
clientLaunchId: exports_external.string().optional().describe("Idempotency key. Auto-generated when omitted and echoed in the result."),
|
|
42953
|
-
devBuy: exports_external.string().optional().describe('Seed buy in DECIMAL units of the quote asset this launch selects (e.g. "0.25" SOL, or ' + "ETH on hood),
|
|
43132
|
+
devBuy: exports_external.string().optional().describe('Seed buy in DECIMAL units of the quote asset this launch selects (e.g. "0.25" SOL, or ' + "ETH on hood), paid from the account's embedded wallet; the tool's description says how " + "each chain sends it. Follows quoteAsset, which defaults to sol on solana and eth on hood. " + "Capped by the platform dev-buy ceiling; for a larger seed, launch then follow with candle_trade.")
|
|
42954
43133
|
};
|
|
42955
43134
|
launchAndSeedShape = { ...seedableLaunchShape, ...seedShape };
|
|
42956
43135
|
localImageShape = {
|
|
@@ -70488,7 +70667,7 @@ init_render();
|
|
|
70488
70667
|
init_secret_store();
|
|
70489
70668
|
|
|
70490
70669
|
// src/version.ts
|
|
70491
|
-
var CLI_VERSION = "0.11.
|
|
70670
|
+
var CLI_VERSION = "0.11.41";
|
|
70492
70671
|
|
|
70493
70672
|
// src/commands/auth.ts
|
|
70494
70673
|
init_keys_embedded_wallet();
|
|
@@ -71446,9 +71625,10 @@ var HELP = {
|
|
|
71446
71625
|
launch: {
|
|
71447
71626
|
group: "Trade",
|
|
71448
71627
|
summary: "Create a token on Solana or Hood, with an optional first buy",
|
|
71449
|
-
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 is its own transaction, sent with the launch as one Jito bundle (an atomic launch), so the launch and the first buy land together or not at all and the bundle pays a Jito tip; on Hood it is a last leg after the curve and the fee. On Solana it also needs the swap:write scope and a Candle deployment with a Jito block engine. Only configured raw caps and USD limits apply; unset means unlimited and zero still binds. When a USD limit is set, 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).
|
|
71628
|
+
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 is its own transaction, sent with the launch as one Jito bundle (an atomic launch), so the launch and the first buy land together or not at all and the bundle pays a Jito tip; on Hood it is a last leg after the curve and the fee. On Solana it also needs the swap:write scope and a Candle deployment with a Jito block engine. Only configured raw caps and USD limits apply; unset means unlimited and zero still binds. When a USD limit is set, 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). --wallet embedded (or main) launches from the account's embedded wallet instead, on Solana, as an atomic launch: it needs --buy, the embedded wallet is the creator and the only first buyer, and Candle signs both legs and sends them as one Jito bundle, so the launch and the first buy land together or not at all. It runs Candle's dry run first, which builds, reserves and records nothing, shows that quote to confirm, and only then builds and sends; the build's first-buy floor must be at least the one shown. --dry-run stops after the preview. It needs Pro or Max, launch:write and swap:write, a key allowed the embedded wallet, and does not depend on TEE_LAUNCH_BUYS_ENABLED. Without a first buy, every plan can launch from the embedded wallet through the MCP tool candle_launch_token or the SDK's launch(). 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. --banner <file|URL> adds a wide banner: an https URL is passed through; a local file uses the same publication notice and 10MB limit, uploaded with kind=banner so it is never cropped. Banners must be wider than 1.5:1. The result reports bannerUrl, and an atomic retry reuses the saved URL. --mode picks the curve: open or exclusive (the default tier for the pair), test-open or test-exclusive where the server has ENABLE_TEST_CURVES on, or on Solana with sol or cndl the experimental curves exp-a, exp-b, exp-c and exp-custom. exp-custom takes your own curve: --curve-v <V> (the virtual quote it starts with) and --curve-g <G> (what it raises before graduating), in whole units of the quote asset, within limits Candle checks; it works only where the server has EXP_CUSTOM_CURVES on. --creator-fee-bps 100 or 200 adds an extra creator fee on an exp- mode, paid to the creator in full. On an exp- curve the first buy is at most 0.5 SOL (2,000,000 CNDL), or 10% of the threshold when that is lower. --dry-run sends the launch's body to Candle's dry run and prints the preview (the curve's terms, the first buy's share, the fees, how the pool opens at graduation and the dev-buy ceiling) without building, signing or recording anything; the id stays unused. With --image it still uploads the file, which is public and permanent.",
|
|
71450
71629
|
usage: [
|
|
71451
|
-
"candle launch --name <name> --symbol <symbol> --image <path>|--image-url <url> --wallet <tee> [--banner <file|URL>] [--buy <amount>] [--quote-asset <asset>] [--dex-version v3|v4] [--mode <mode>] [--curve-v <V> --curve-g <G>] [--creator-fee-bps 0|100|200] [--pool-fee <25|50|100>] [--pool-open <shape>] [--exp-pool-shape <shape>] [--dry-run]"
|
|
71630
|
+
"candle launch --name <name> --symbol <symbol> --image <path>|--image-url <url> --wallet <tee> [--banner <file|URL>] [--buy <amount>] [--quote-asset <asset>] [--dex-version v3|v4] [--mode <mode>] [--curve-v <V> --curve-g <G>] [--creator-fee-bps 0|100|200] [--pool-fee <25|50|100>] [--pool-open <shape>] [--exp-pool-shape <shape>] [--dry-run]",
|
|
71631
|
+
"candle launch --name <name> --symbol <symbol> --image <path>|--image-url <url> --wallet embedded --buy <amount> [--banner <file|URL>] [--quote-asset <asset>] [--dry-run]"
|
|
71452
71632
|
],
|
|
71453
71633
|
rows: [],
|
|
71454
71634
|
flags: [
|
|
@@ -71463,7 +71643,10 @@ var HELP = {
|
|
|
71463
71643
|
invocation: "--banner <file|URL>",
|
|
71464
71644
|
description: "A wide banner from a local file or https URL; files become public and permanent, without cropping"
|
|
71465
71645
|
},
|
|
71466
|
-
{
|
|
71646
|
+
{
|
|
71647
|
+
invocation: "--wallet <tee>|embedded",
|
|
71648
|
+
description: "The TEE wallet that creates it, or embedded (main) for the account's embedded wallet, with --buy, as an atomic launch"
|
|
71649
|
+
},
|
|
71467
71650
|
{
|
|
71468
71651
|
invocation: "--buy <amount>",
|
|
71469
71652
|
description: "A first buy from the same wallet, in whole units of the quote asset; needs TEE_LAUNCH_BUYS_ENABLED on the server"
|
|
@@ -71483,7 +71666,7 @@ var HELP = {
|
|
|
71483
71666
|
},
|
|
71484
71667
|
{
|
|
71485
71668
|
invocation: "--pool-open legacy|curve_price",
|
|
71486
|
-
description: "Solana preset tiers: curve_price opens the pool at the curve's last price and burns the rest
|
|
71669
|
+
description: "Solana preset tiers: curve_price opens the pool at the curve's last price and burns the rest. Omitted defaults to curve_price when stakerAllocationBps plus the team allocation fit the tier's limit (dry-run graduation.maxStakerCarveBps), otherwise falls back to legacy without refusal. Use --pool-open legacy to opt out. Explicit curve_price is refused above the limit"
|
|
71487
71670
|
},
|
|
71488
71671
|
{
|
|
71489
71672
|
invocation: "--exp-pool-shape floor|full_range",
|
|
@@ -71511,6 +71694,7 @@ var HELP = {
|
|
|
71511
71694
|
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne",
|
|
71512
71695
|
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet HoodAgent --dex-version v4",
|
|
71513
71696
|
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne --buy 0.1",
|
|
71697
|
+
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet embedded --buy 0.1 --dry-run",
|
|
71514
71698
|
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne --mode exp-custom --curve-v 4 --curve-g 20 --creator-fee-bps 100 --dry-run"
|
|
71515
71699
|
],
|
|
71516
71700
|
env: ENV_API
|
|
@@ -80651,7 +80835,7 @@ async function doctor(args, ctx) {
|
|
|
80651
80835
|
const method = detectInstall(deps.execPath, realExec);
|
|
80652
80836
|
const installDetail = method === "binary" ? `binary at ${deps.execPath}` : method === "homebrew" ? `Homebrew (${realExec})` : `script (${deps.execPath}); update with npm`;
|
|
80653
80837
|
rows.push({ check: "Install", state: "PASS", detail: installDetail });
|
|
80654
|
-
const latest = await fetchLatest(deps, releaseBaseUrl(deps.env));
|
|
80838
|
+
const latest = await fetchLatest(deps, releaseBaseUrl(deps.env, updateHomeFor(CLI_VERSION)));
|
|
80655
80839
|
const helper = await locateFido2Helper(deps);
|
|
80656
80840
|
const declaredHelper = latest.ok && deps.platformKey ? latest.manifest.helpers?.[deps.platformKey] : undefined;
|
|
80657
80841
|
if (helper.state === "ready") {
|
|
@@ -84802,7 +84986,9 @@ function poolRequestFields(flags) {
|
|
|
84802
84986
|
|
|
84803
84987
|
// src/commands/launch.ts
|
|
84804
84988
|
init_swap();
|
|
84805
|
-
var USAGE6 = "Usage: candle launch --name <name> --symbol <symbol> --image <path>|--image-url <https-url> --wallet <tee
|
|
84989
|
+
var USAGE6 = "Usage: candle launch --name <name> --symbol <symbol> --image <path>|--image-url <https-url> --wallet <tee>|embedded [--banner <file|URL>] [--buy <amount>] [--client-trade-id <id>] [--quote-asset sol|usdc|cndl|eth|usdg] [--dex-version v3|v4] [--mode <mode>] [--curve-v <V> --curve-g <G>] [--creator-fee-bps 0|100|200] [--pool-fee <25|50|100> [--pool-fee-from <bps> --pool-fee-minutes <min>]] [--pool-open legacy|curve_price] [--exp-pool-shape floor|full_range] [--dry-run] [--rpc-url <url>] [--yes]. A Hood TEE wallet launches on Hood and needs --dex-version.";
|
|
84990
|
+
var EMBEDDED_WALLET_NAMES = new Set(["embedded", "main"]);
|
|
84991
|
+
var EMBEDDED_USAGE = "--wallet embedded launches on Solana as an atomic launch: it needs --buy <amount>, takes --quote-asset sol, usdc or cndl, and takes no --dex-version. To launch from the embedded wallet with no first buy, use the MCP tool candle_launch_token or POST /api/v1/launch/headless.";
|
|
84806
84992
|
var QUOTE_CHAIN = {
|
|
84807
84993
|
sol: "solana",
|
|
84808
84994
|
usdc: "solana",
|
|
@@ -84920,7 +85106,15 @@ async function launch(args, ctx) {
|
|
|
84920
85106
|
}
|
|
84921
85107
|
const flags = parsed.values;
|
|
84922
85108
|
const id = flags["--client-trade-id"] ?? `launch-${randomUUID5()}`;
|
|
85109
|
+
const embedded = EMBEDDED_WALLET_NAMES.has(flags["--wallet"] ?? "");
|
|
84923
85110
|
const dryRun = parsed.booleans.has("--dry-run");
|
|
85111
|
+
if (embedded) {
|
|
85112
|
+
const quote = flags["--quote-asset"]?.toLowerCase();
|
|
85113
|
+
if (flags["--buy"] === undefined || flags["--dex-version"] !== undefined || quote && QUOTE_CHAIN[quote] !== "solana") {
|
|
85114
|
+
writeUsageFailure(ctx.deps, `${EMBEDDED_USAGE} ${USAGE6}`, ctx.json);
|
|
85115
|
+
return 2;
|
|
85116
|
+
}
|
|
85117
|
+
}
|
|
84924
85118
|
const curveProblem = curveFlagProblem(flags);
|
|
84925
85119
|
if (curveProblem) {
|
|
84926
85120
|
writeUsageFailure(ctx.deps, `${curveProblem} ${USAGE6}`, ctx.json);
|
|
@@ -84991,6 +85185,16 @@ async function launch(args, ctx) {
|
|
|
84991
85185
|
return printTradingResult(ctx, prior);
|
|
84992
85186
|
}
|
|
84993
85187
|
}
|
|
85188
|
+
if (embedded)
|
|
85189
|
+
return await embeddedLaunch(ctx, key, {
|
|
85190
|
+
flags,
|
|
85191
|
+
id,
|
|
85192
|
+
image,
|
|
85193
|
+
banner,
|
|
85194
|
+
retry,
|
|
85195
|
+
yes: parsed.booleans.has("--yes"),
|
|
85196
|
+
dryRun
|
|
85197
|
+
});
|
|
84994
85198
|
const wallet3 = await tradingWallet(ctx, key, flags["--wallet"], "launch:write", hinted ?? "any");
|
|
84995
85199
|
const chain2 = wallet3.chain === "evm" ? "hood" : "solana";
|
|
84996
85200
|
if (chain2 === "hood" && flags["--dex-version"] === undefined)
|
|
@@ -85000,38 +85204,10 @@ async function launch(args, ctx) {
|
|
|
85000
85204
|
if (atomic)
|
|
85001
85205
|
atomicPreflight(ctx, flags, wallet3, buy);
|
|
85002
85206
|
const solana = chain2 === "solana" && !atomic && !dryRun ? await tradingSolanaClient(ctx, flags["--rpc-url"]) : undefined;
|
|
85003
|
-
|
|
85004
|
-
if (
|
|
85005
|
-
|
|
85006
|
-
|
|
85007
|
-
ctx.deps.stderr.write(uploadNotice(image));
|
|
85008
|
-
try {
|
|
85009
|
-
imageUrl = await uploadImage(ctx, key, image);
|
|
85010
|
-
} catch (error) {
|
|
85011
|
-
ctx.deps.stderr.write(`The launch was not started, and its id ${id} is unused.
|
|
85012
|
-
`);
|
|
85013
|
-
return tradingFailure(ctx, error);
|
|
85014
|
-
}
|
|
85015
|
-
ctx.deps.stderr.write(`Image: ${safeText(imageUrl)}
|
|
85016
|
-
`);
|
|
85017
|
-
}
|
|
85018
|
-
let bannerUrl = retry ? retry.bannerUrl : flags["--banner"]?.startsWith("https://") ? flags["--banner"] : undefined;
|
|
85019
|
-
if (banner && !retry) {
|
|
85020
|
-
ctx.deps.stderr.write(uploadNotice(banner));
|
|
85021
|
-
try {
|
|
85022
|
-
bannerUrl = await uploadImage(ctx, key, banner, "banner");
|
|
85023
|
-
} catch (error) {
|
|
85024
|
-
ctx.deps.stderr.write(`The launch was not started, and its id ${id} is unused.
|
|
85025
|
-
`);
|
|
85026
|
-
return tradingFailure(ctx, error);
|
|
85027
|
-
}
|
|
85028
|
-
ctx.deps.stderr.write(`Banner: ${safeText(bannerUrl)}
|
|
85029
|
-
`);
|
|
85030
|
-
}
|
|
85031
|
-
const used = {
|
|
85032
|
-
...image ? { imageUrl: safeText(imageUrl) } : {},
|
|
85033
|
-
...bannerUrl ? { bannerUrl: safeText(bannerUrl) } : {}
|
|
85034
|
-
};
|
|
85207
|
+
const uploaded = await uploadLaunchImages(ctx, key, { flags, id, image, banner, retry });
|
|
85208
|
+
if (typeof uploaded === "number")
|
|
85209
|
+
return uploaded;
|
|
85210
|
+
const { imageUrl, bannerUrl, used } = uploaded;
|
|
85035
85211
|
if (dryRun)
|
|
85036
85212
|
return await dryRunLaunch(ctx, key, { flags, id, wallet: wallet3, chain: chain2, atomic, buy, imageUrl, bannerUrl, used });
|
|
85037
85213
|
if (atomic)
|
|
@@ -85108,6 +85284,42 @@ async function launch(args, ctx) {
|
|
|
85108
85284
|
return tradingFailure(ctx, error, dryRun ? undefined : id);
|
|
85109
85285
|
}
|
|
85110
85286
|
}
|
|
85287
|
+
async function uploadLaunchImages(ctx, key, args) {
|
|
85288
|
+
const { flags, id, image, banner, retry } = args;
|
|
85289
|
+
let imageUrl = flags["--image-url"] ?? "";
|
|
85290
|
+
if (image && retry?.imageUrl)
|
|
85291
|
+
imageUrl = retry.imageUrl;
|
|
85292
|
+
else if (image) {
|
|
85293
|
+
ctx.deps.stderr.write(uploadNotice(image));
|
|
85294
|
+
try {
|
|
85295
|
+
imageUrl = await uploadImage(ctx, key, image);
|
|
85296
|
+
} catch (error) {
|
|
85297
|
+
ctx.deps.stderr.write(`The launch was not started, and its id ${id} is unused.
|
|
85298
|
+
`);
|
|
85299
|
+
return tradingFailure(ctx, error);
|
|
85300
|
+
}
|
|
85301
|
+
ctx.deps.stderr.write(`Image: ${safeText(imageUrl)}
|
|
85302
|
+
`);
|
|
85303
|
+
}
|
|
85304
|
+
let bannerUrl = retry ? retry.bannerUrl : flags["--banner"]?.startsWith("https://") ? flags["--banner"] : undefined;
|
|
85305
|
+
if (banner && !retry) {
|
|
85306
|
+
ctx.deps.stderr.write(uploadNotice(banner));
|
|
85307
|
+
try {
|
|
85308
|
+
bannerUrl = await uploadImage(ctx, key, banner, "banner");
|
|
85309
|
+
} catch (error) {
|
|
85310
|
+
ctx.deps.stderr.write(`The launch was not started, and its id ${id} is unused.
|
|
85311
|
+
`);
|
|
85312
|
+
return tradingFailure(ctx, error);
|
|
85313
|
+
}
|
|
85314
|
+
ctx.deps.stderr.write(`Banner: ${safeText(bannerUrl)}
|
|
85315
|
+
`);
|
|
85316
|
+
}
|
|
85317
|
+
const used = {
|
|
85318
|
+
...image ? { imageUrl: safeText(imageUrl) } : {},
|
|
85319
|
+
...bannerUrl ? { bannerUrl: safeText(bannerUrl) } : {}
|
|
85320
|
+
};
|
|
85321
|
+
return { imageUrl, ...bannerUrl ? { bannerUrl } : {}, used };
|
|
85322
|
+
}
|
|
85111
85323
|
function launchFields(args) {
|
|
85112
85324
|
const { flags } = args;
|
|
85113
85325
|
return {
|
|
@@ -85237,6 +85449,11 @@ function expThresholdRaw(flags, quoteAsset) {
|
|
|
85237
85449
|
return mode === "exp-a" || mode === "exp-b" || mode === "exp-c" ? EXP_PRESET_THRESHOLD_RAW[quoteAsset] : undefined;
|
|
85238
85450
|
}
|
|
85239
85451
|
function atomicPreflight(ctx, flags, wallet3, buy) {
|
|
85452
|
+
firstBuyCeiling(ctx, flags, buy);
|
|
85453
|
+
if (wallet3.scopes !== undefined && !wallet3.scopes.includes("swap:write"))
|
|
85454
|
+
throw swapScopeRefusal();
|
|
85455
|
+
}
|
|
85456
|
+
function firstBuyCeiling(ctx, flags, buy) {
|
|
85240
85457
|
const quoteAsset = (flags["--quote-asset"] ?? "sol").toLowerCase();
|
|
85241
85458
|
const { ceiling, unit } = devBuyCeiling(ctx.deps.env, quoteAsset);
|
|
85242
85459
|
const threshold = expThresholdRaw(flags, quoteAsset);
|
|
@@ -85251,8 +85468,6 @@ function atomicPreflight(ctx, flags, wallet3, buy) {
|
|
|
85251
85468
|
}
|
|
85252
85469
|
if (BigInt(buy.raw) > ceiling)
|
|
85253
85470
|
throw new TradingError("DEV_BUY_TOO_HIGH", `buyAmount exceeds the per-launch dev-buy ceiling of ${ceiling} ${unit} for ${quoteAsset} on solana`, { details: { field: "buyAmount" } });
|
|
85254
|
-
if (wallet3.scopes !== undefined && !wallet3.scopes.includes("swap:write"))
|
|
85255
|
-
throw swapScopeRefusal();
|
|
85256
85471
|
}
|
|
85257
85472
|
function swapScopeRefusal() {
|
|
85258
85473
|
return new TradingError("SCOPE_MISSING", "This key needs swap:write for a first buy on a TEE launch: the buy is its own leg in an atomic launch. Nothing was built or signed.", {
|
|
@@ -85363,28 +85578,33 @@ async function atomicLaunch(ctx, key, args) {
|
|
|
85363
85578
|
await saveOperationBundle(ctx, key, id, bundle);
|
|
85364
85579
|
ctx.deps.stderr.write(`Bundle: ${safeText(built.bundleId)} (launch ${signatures[0]}, first buy ${signatures[1]})
|
|
85365
85580
|
`);
|
|
85581
|
+
return await submitBundle(ctx, key, { id, bundle, signed, quote, used: args.used });
|
|
85582
|
+
}
|
|
85583
|
+
async function submitBundle(ctx, key, args) {
|
|
85584
|
+
const { id, bundle, signed, quote } = args;
|
|
85585
|
+
const { bundleId, signatures } = bundle;
|
|
85366
85586
|
const submitted = await apiRequest("/api/v1/launch/atomic/submit", {
|
|
85367
85587
|
apiUrl: ctx.apiUrl,
|
|
85368
85588
|
credentials: { apiKey: key },
|
|
85369
85589
|
auth: "key",
|
|
85370
85590
|
method: "POST",
|
|
85371
|
-
body: { bundleId
|
|
85591
|
+
body: { bundleId, signedTxsBase64: signed },
|
|
85372
85592
|
fetch: ctx.deps.fetch,
|
|
85373
85593
|
env: ctx.deps.env
|
|
85374
85594
|
});
|
|
85375
85595
|
const outcome = atomicOutcomeSchema.safeParse(submitted.ok ? submitted.body : submitted.raw);
|
|
85376
|
-
const details = { bundleId
|
|
85377
|
-
if (!outcome.success || outcome.data.bundleId !==
|
|
85596
|
+
const details = { bundleId, signatures };
|
|
85597
|
+
if (!outcome.success || outcome.data.bundleId !== bundleId) {
|
|
85378
85598
|
const refused2 = !submitted.ok && submitted.status >= 400 && submitted.status < 500 && submitted.status !== 409;
|
|
85379
85599
|
if (refused2)
|
|
85380
85600
|
await saveOperationBundle(ctx, key, id, { ...bundle, outcome: "refused" });
|
|
85381
|
-
throw new TradingError(submitted.ok ? "INVALID_RESPONSE" : submitted.code ?? "REQUEST_FAILED", refused2 ? `Candle refused bundle ${safeText(
|
|
85601
|
+
throw new TradingError(submitted.ok ? "INVALID_RESPONSE" : submitted.code ?? "REQUEST_FAILED", refused2 ? `Candle refused bundle ${safeText(bundleId)} and sent nothing: ${safeText(submitted.ok ? "" : submitted.message)}. Re-run with the same --client-trade-id ${id} to build it again.` : `Bundle ${safeText(bundleId)} may have been sent, and its outcome is unknown${submitted.ok ? "" : `: ${safeText(submitted.message)}`}. Re-run with the same --client-trade-id ${id} to read it; nothing is re-signed or re-sent. Never relaunch under a new id: that would be a second token.`, { ...refused2 ? {} : { exitCode: 3 }, details });
|
|
85382
85602
|
}
|
|
85383
85603
|
await saveOperationBundle(ctx, key, id, { ...bundle, outcome: outcome.data.status });
|
|
85384
85604
|
if (outcome.data.status === "failed")
|
|
85385
|
-
throw new TradingError("BUNDLE_FAILED", `Bundle ${safeText(
|
|
85605
|
+
throw new TradingError("BUNDLE_FAILED", `Bundle ${safeText(bundleId)} did not land: neither the launch nor its first buy is on chain. Retry with the same --client-trade-id ${id}; Candle starts a clean attempt under it.`, { details });
|
|
85386
85606
|
if (outcome.data.status === "timeout")
|
|
85387
|
-
throw new TradingError("BUNDLE_UNRESOLVED", `Bundle ${safeText(
|
|
85607
|
+
throw new TradingError("BUNDLE_UNRESOLVED", `Bundle ${safeText(bundleId)} is unresolved and may still land. Re-run with the same --client-trade-id ${id} to read its outcome; nothing is re-signed or re-sent. Never relaunch under a new id: that would be a second token.`, { exitCode: 3, details });
|
|
85388
85608
|
const [launchSignature, buySignature] = outcome.data.signatures;
|
|
85389
85609
|
ctx.deps.stderr.write(`Launched ${safeText(outcome.data.mint)}. Launch signature: ${safeText(launchSignature)}. First buy signature: ${safeText(buySignature)}.
|
|
85390
85610
|
`);
|
|
@@ -85408,6 +85628,127 @@ function resumeAtomicLaunch(ctx, id, local, prior) {
|
|
|
85408
85628
|
return printTradingResult(ctx, { ...prior, ...saved });
|
|
85409
85629
|
throw new TradingError("OPERATION_IN_FLIGHT", `Launch ${id}'s bundle ${safeText(local.bundleId)} is unresolved (the launch job is ${safeText(prior.job.status)}) and may still land. Nothing was re-signed or re-sent. Re-run with the same --client-trade-id to read it again; never relaunch under a new id, which would be a second token.`, { exitCode: 3, details: { ...saved, job: prior.job } });
|
|
85410
85630
|
}
|
|
85631
|
+
var EMBEDDED_PAYER = { type: "main" };
|
|
85632
|
+
var atomicDryRunSchema = exports_external.object({
|
|
85633
|
+
launchWallet: exports_external.string(),
|
|
85634
|
+
launchSigner: exports_external.enum(["server", "client"]),
|
|
85635
|
+
fills: exports_external.array(exports_external.object({
|
|
85636
|
+
index: exports_external.number().int(),
|
|
85637
|
+
signer: exports_external.enum(["server", "client"]),
|
|
85638
|
+
wallet: exports_external.string(),
|
|
85639
|
+
amountRaw: exports_external.string().regex(/^\d+$/),
|
|
85640
|
+
feeRaw: exports_external.string().regex(/^\d+$/),
|
|
85641
|
+
expectedFill: exports_external.object({
|
|
85642
|
+
amountOutRaw: exports_external.string().regex(/^\d+$/),
|
|
85643
|
+
minAmountOutRaw: exports_external.string().regex(/^\d+$/)
|
|
85644
|
+
}),
|
|
85645
|
+
supplyPct: exports_external.number()
|
|
85646
|
+
}).passthrough()),
|
|
85647
|
+
graduatesInBundle: exports_external.boolean().optional()
|
|
85648
|
+
}).passthrough();
|
|
85649
|
+
async function embeddedLaunchWallet(ctx, key) {
|
|
85650
|
+
const discovered = embeddedSchema.parse(await request(ctx, key, "/api/v1/agent/wallets/embedded"));
|
|
85651
|
+
const address = discovered.wallets?.solana?.address;
|
|
85652
|
+
if (!address)
|
|
85653
|
+
throw new TradingError("PAYER_REQUIRED", "This account has no Solana embedded wallet. Create one in the app, or launch from a Solana TEE wallet with --wallet <tee>. Nothing was built.");
|
|
85654
|
+
if (discovered.embeddedWalletPermission === "denied")
|
|
85655
|
+
throw new TradingError("EMBEDDED_WALLET_NOT_PERMITTED", `This key may not use the account's embedded wallet (${address}). Nothing was built.`, {
|
|
85656
|
+
suggestion: "Have the owner allow it: candle keys update <prefix> --embedded-wallet allow (device token), or in the web key manager. Or launch from a TEE wallet bound to this key with --wallet <tee>."
|
|
85657
|
+
});
|
|
85658
|
+
return address;
|
|
85659
|
+
}
|
|
85660
|
+
function embeddedRefusal(error) {
|
|
85661
|
+
if (!(error instanceof TradingError))
|
|
85662
|
+
return error;
|
|
85663
|
+
if (error.code === "SCOPE_MISSING")
|
|
85664
|
+
return new TradingError("SCOPE_MISSING", "This key needs swap:write as well as launch:write: the first buy is its own leg in the atomic launch. Nothing was built or signed.", { suggestion: "Add swap:write to the key, then run the same command again." });
|
|
85665
|
+
if (error.code === "VALIDATION_FAILED" && error.message.includes("configured Jito block engine"))
|
|
85666
|
+
return new TradingError("VALIDATION_FAILED", "An embedded-wallet launch goes to Candle as one Jito bundle, and this Candle deployment has no Jito block engine configured. Nothing was built or signed.");
|
|
85667
|
+
return error;
|
|
85668
|
+
}
|
|
85669
|
+
async function embeddedLaunch(ctx, key, args) {
|
|
85670
|
+
const { flags, id } = args;
|
|
85671
|
+
const buy = launchBuy(flags, "solana");
|
|
85672
|
+
firstBuyCeiling(ctx, flags, buy);
|
|
85673
|
+
const wallet3 = await embeddedLaunchWallet(ctx, key);
|
|
85674
|
+
const uploaded = await uploadLaunchImages(ctx, key, args);
|
|
85675
|
+
if (typeof uploaded === "number")
|
|
85676
|
+
return uploaded;
|
|
85677
|
+
const { imageUrl, bannerUrl, used } = uploaded;
|
|
85678
|
+
const body = {
|
|
85679
|
+
clientLaunchId: id,
|
|
85680
|
+
chain: "solana",
|
|
85681
|
+
name: flags["--name"],
|
|
85682
|
+
symbol: flags["--symbol"],
|
|
85683
|
+
imageUrl,
|
|
85684
|
+
...bannerUrl ? { bannerUrl } : {},
|
|
85685
|
+
...flags["--description"] ? { description: flags["--description"] } : {},
|
|
85686
|
+
...flags["--quote-asset"] ? { quoteAsset: flags["--quote-asset"] } : {},
|
|
85687
|
+
...flags["--mode"] ? { mode: flags["--mode"] } : {},
|
|
85688
|
+
...launchCurveFields(flags),
|
|
85689
|
+
...poolRequestFields(flags),
|
|
85690
|
+
payer: EMBEDDED_PAYER,
|
|
85691
|
+
firstBuys: [{ payer: EMBEDDED_PAYER, amountRaw: buy.raw }]
|
|
85692
|
+
};
|
|
85693
|
+
const answer2 = await request(ctx, key, "/api/v1/launch/atomic/dry-run", body).catch((error) => {
|
|
85694
|
+
throw embeddedRefusal(error);
|
|
85695
|
+
});
|
|
85696
|
+
const preview = atomicDryRunSchema.safeParse(answer2);
|
|
85697
|
+
const fill = preview.success ? preview.data.fills[0] : undefined;
|
|
85698
|
+
if (!preview.success || !fill || preview.data.fills.length !== 1 || preview.data.launchWallet !== wallet3 || preview.data.launchSigner !== "server" || fill.wallet !== wallet3 || fill.signer !== "server" || fill.amountRaw !== buy.raw)
|
|
85699
|
+
throw new TradingError("INVALID_RESPONSE", "Candle's dry run does not read as this launch and its first buy from the embedded wallet; nothing was built.");
|
|
85700
|
+
const quoteAsset = (flags["--quote-asset"] ?? "sol").toUpperCase();
|
|
85701
|
+
const decimals = BASES[quoteAsset]?.decimals ?? 9;
|
|
85702
|
+
const quote = {
|
|
85703
|
+
intent: `Launch ${flags["--name"]} (${flags["--symbol"]}) with a first buy of ${buy.display} from the embedded wallet, as one Jito bundle`,
|
|
85704
|
+
wallet: wallet3,
|
|
85705
|
+
venue: "curve launch (atomic bundle, signed by Candle)",
|
|
85706
|
+
priceImpactPct: null,
|
|
85707
|
+
candleFee: fill.feeRaw !== "0" ? `${decimalAmount(fill.feeRaw, decimals)} ${quoteAsset} (the tier fee on the first buy, in its leg)` : "none on this first buy",
|
|
85708
|
+
minimumReceived: `${fill.expectedFill.minAmountOutRaw} raw units of the new token (the first buy's floor; ${fill.supplyPct}% of the supply expected)`,
|
|
85709
|
+
tokenRisks: []
|
|
85710
|
+
};
|
|
85711
|
+
if (args.dryRun) {
|
|
85712
|
+
ctx.deps.stderr.write(`Dry run: nothing was built, signed or recorded, and launch id ${id} is unused.
|
|
85713
|
+
`);
|
|
85714
|
+
return printTradingResult(ctx, { ...answer2, clientTradeId: id, kind: "launch", quote, ...used });
|
|
85715
|
+
}
|
|
85716
|
+
ctx.deps.stderr.write(`Launch creates the token, then spends ${buy.display} on a first buy from the embedded wallet as its own transaction. Both go to Candle as one Jito bundle: the launch and the first buy land together or not at all. Candle signs both, and the bundle pays a Jito tip from this wallet inside the launch transaction, beside the launch's rent and network fees. Price impact does not apply to creation.
|
|
85717
|
+
`);
|
|
85718
|
+
if (preview.data.graduatesInBundle)
|
|
85719
|
+
ctx.deps.stderr.write(`This first buy alone takes the new curve to graduation inside the bundle.
|
|
85720
|
+
`);
|
|
85721
|
+
if (!await confirmQuote(ctx, quote, args.yes))
|
|
85722
|
+
return printTradingResult(ctx, {
|
|
85723
|
+
success: true,
|
|
85724
|
+
status: "cancelled",
|
|
85725
|
+
clientTradeId: id,
|
|
85726
|
+
kind: "launch",
|
|
85727
|
+
quote,
|
|
85728
|
+
...used
|
|
85729
|
+
});
|
|
85730
|
+
if (!args.retry && !await claimOperation(ctx, key, id, "launch"))
|
|
85731
|
+
throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this launch id; no write was resent.");
|
|
85732
|
+
ctx.deps.stderr.write(`Operation: ${id}
|
|
85733
|
+
`);
|
|
85734
|
+
const built = atomicBuildSchema.parse(await request(ctx, key, "/api/v1/launch/atomic/build", body).catch((error) => {
|
|
85735
|
+
throw embeddedRefusal(error);
|
|
85736
|
+
}));
|
|
85737
|
+
const bundle = { bundleId: built.bundleId, signatures: [], imageUrl, ...bannerUrl ? { bannerUrl } : {} };
|
|
85738
|
+
await saveOperationBundle(ctx, key, id, { ...bundle, outcome: "unsigned" });
|
|
85739
|
+
const [launchLeg, buyLeg] = built.legs;
|
|
85740
|
+
if (built.legs.length !== 2 || launchLeg?.role !== "launch" || buyLeg?.role !== "buy" || built.legs.some((leg, index) => leg.index !== index || leg.signer !== "server"))
|
|
85741
|
+
throw new TradingError("INVALID_RESPONSE", `Candle did not build bundle ${safeText(built.bundleId)} as this launch and its first buy, both from the embedded wallet; nothing was sent. Re-run with the same --client-trade-id ${id} to build it again.`);
|
|
85742
|
+
const floor = buyLeg.expectedFill?.minAmountOutRaw;
|
|
85743
|
+
if (floor === undefined || BigInt(floor) < BigInt(fill.expectedFill.minAmountOutRaw))
|
|
85744
|
+
throw new TradingError("QUOTE_CHANGED", `Bundle ${safeText(built.bundleId)}'s first-buy floor (${safeText(floor ?? "none")} raw units) is below the ${fill.expectedFill.minAmountOutRaw} you confirmed; nothing was sent. Re-run with the same --client-trade-id ${id} to preview it again.`);
|
|
85745
|
+
if (!Number.isFinite(built.expiresAt) || built.expiresAt <= ctx.deps.now())
|
|
85746
|
+
throw new TradingError("QUOTE_EXPIRED", "The launch build expired before it was sent.");
|
|
85747
|
+
await saveOperationBundle(ctx, key, id, bundle);
|
|
85748
|
+
ctx.deps.stderr.write(`Bundle: ${safeText(built.bundleId)} (Candle signs the launch and the first buy)
|
|
85749
|
+
`);
|
|
85750
|
+
return await submitBundle(ctx, key, { id, bundle, signed: [], quote, used });
|
|
85751
|
+
}
|
|
85411
85752
|
async function hoodLaunch(ctx, key, args) {
|
|
85412
85753
|
const { flags, id, wallet: wallet3, buy } = args;
|
|
85413
85754
|
if (Object.keys(poolRequestFields(flags)).length)
|
|
@@ -95137,7 +95478,6 @@ function stepReporter(write, tty, ascii = false) {
|
|
|
95137
95478
|
`);
|
|
95138
95479
|
return;
|
|
95139
95480
|
}
|
|
95140
|
-
write(`${FRAMES[0]} ${label}`);
|
|
95141
95481
|
frame = 1;
|
|
95142
95482
|
const interrupt = (signal) => {
|
|
95143
95483
|
clearSpinner();
|
|
@@ -95158,6 +95498,7 @@ function stepReporter(write, tty, ascii = false) {
|
|
|
95158
95498
|
}, INTERVAL_MS3);
|
|
95159
95499
|
if (typeof timer === "object" && "unref" in timer)
|
|
95160
95500
|
timer.unref();
|
|
95501
|
+
write(`${FRAMES[0]} ${label}`);
|
|
95161
95502
|
},
|
|
95162
95503
|
done(label) {
|
|
95163
95504
|
clearSpinner();
|
|
@@ -95482,7 +95823,7 @@ init_render();
|
|
|
95482
95823
|
init_fido2();
|
|
95483
95824
|
var SIGNATURE_VERIFIED = "signature verified (Sigstore, keyless; signer pinned to the release workflow)";
|
|
95484
95825
|
var INSTALLER_LINE = "curl -fsSL https://candle.tv/install.sh | bash";
|
|
95485
|
-
async function update(args, ctx) {
|
|
95826
|
+
async function update(args, ctx, current = CLI_VERSION) {
|
|
95486
95827
|
let __stack = [];
|
|
95487
95828
|
try {
|
|
95488
95829
|
const { deps, json } = ctx;
|
|
@@ -95497,11 +95838,16 @@ async function update(args, ctx) {
|
|
|
95497
95838
|
}
|
|
95498
95839
|
const check = parsed.booleans.has("--check");
|
|
95499
95840
|
const pinned = parsed.values["--to"];
|
|
95841
|
+
const pinnedVersion = pinned === undefined ? undefined : versionOfTag(pinned);
|
|
95842
|
+
if (pinned !== undefined && pinnedVersion === null) {
|
|
95843
|
+
writeUsageFailure(deps, `--to takes a release tag such as cli-v${current}, not ${pinned}`, json);
|
|
95844
|
+
return 2;
|
|
95845
|
+
}
|
|
95500
95846
|
const realExec = await deps.realpath(deps.execPath).catch(() => deps.execPath);
|
|
95501
95847
|
const method = detectInstall(deps.execPath, realExec);
|
|
95502
95848
|
if (method === "homebrew") {
|
|
95503
95849
|
if (json) {
|
|
95504
|
-
const payload = { current
|
|
95850
|
+
const payload = { current, latest: null, updated: false, path: realExec, method };
|
|
95505
95851
|
deps.stdout.write(`${JSON.stringify(payload)}
|
|
95506
95852
|
`);
|
|
95507
95853
|
} else {
|
|
@@ -95512,7 +95858,7 @@ async function update(args, ctx) {
|
|
|
95512
95858
|
}
|
|
95513
95859
|
if (method === "script") {
|
|
95514
95860
|
if (json) {
|
|
95515
|
-
const payload = { current
|
|
95861
|
+
const payload = { current, latest: null, updated: false, path: realExec, method };
|
|
95516
95862
|
deps.stdout.write(`${JSON.stringify(payload)}
|
|
95517
95863
|
`);
|
|
95518
95864
|
} else {
|
|
@@ -95521,8 +95867,21 @@ async function update(args, ctx) {
|
|
|
95521
95867
|
}
|
|
95522
95868
|
return 0;
|
|
95523
95869
|
}
|
|
95524
|
-
const
|
|
95870
|
+
const home = pinned && pinnedVersion ? releaseHomeFor(pinnedVersion) : updateHomeFor(current);
|
|
95871
|
+
const base2 = releaseBaseUrl(deps.env, home);
|
|
95525
95872
|
const fetched = pinned ? await fetchPinned(deps, base2, pinned) : await fetchLatest(deps, base2);
|
|
95873
|
+
if (!fetched.ok && !pinned && fetched.status === 404 && compareVersions(current, BRIDGE_VERSION) === 0) {
|
|
95874
|
+
const message = `candle ${current} is the final release from ${homeRepository("legacy")}. Later releases come from ${homeRepository("current")}, which has not published one yet. Nothing was changed.`;
|
|
95875
|
+
if (json) {
|
|
95876
|
+
const payload = { current, latest: null, updated: false, path: realExec, pending: homeRepository("current") };
|
|
95877
|
+
deps.stdout.write(`${JSON.stringify(payload)}
|
|
95878
|
+
`);
|
|
95879
|
+
} else {
|
|
95880
|
+
deps.stdout.write(`${message}
|
|
95881
|
+
`);
|
|
95882
|
+
}
|
|
95883
|
+
return 0;
|
|
95884
|
+
}
|
|
95526
95885
|
if (!fetched.ok) {
|
|
95527
95886
|
const code = fetched.kind === "invalid" ? "MANIFEST_INVALID" : "UPDATE_UNREACHABLE";
|
|
95528
95887
|
writeLocalFailure(deps, { code, message: fetched.message }, json);
|
|
@@ -95531,6 +95890,9 @@ async function update(args, ctx) {
|
|
|
95531
95890
|
const target = fetched.manifest;
|
|
95532
95891
|
let identityUri;
|
|
95533
95892
|
try {
|
|
95893
|
+
const mismatch = manifestHomeMismatch(target, home, pinned);
|
|
95894
|
+
if (mismatch !== null)
|
|
95895
|
+
throw new Error(mismatch);
|
|
95534
95896
|
identityUri = releaseIdentityUri(target.version);
|
|
95535
95897
|
} catch (error) {
|
|
95536
95898
|
writeLocalFailure(deps, {
|
|
@@ -95540,34 +95902,34 @@ async function update(args, ctx) {
|
|
|
95540
95902
|
}, json);
|
|
95541
95903
|
return 1;
|
|
95542
95904
|
}
|
|
95543
|
-
const order = compareVersions(
|
|
95905
|
+
const order = compareVersions(current, target.version);
|
|
95544
95906
|
if (check) {
|
|
95545
95907
|
if (json) {
|
|
95546
|
-
const payload = { current
|
|
95908
|
+
const payload = { current, latest: target.version, updated: false, path: realExec };
|
|
95547
95909
|
deps.stdout.write(`${JSON.stringify(payload)}
|
|
95548
95910
|
`);
|
|
95549
95911
|
} else if (order < 0) {
|
|
95550
|
-
deps.stdout.write(`candle ${
|
|
95912
|
+
deps.stdout.write(`candle ${current}; ${target.version} available. Run: candle update
|
|
95551
95913
|
`);
|
|
95552
95914
|
} else {
|
|
95553
|
-
deps.stdout.write(`candle ${
|
|
95915
|
+
deps.stdout.write(`candle ${current} is up to date (latest ${target.version})
|
|
95554
95916
|
`);
|
|
95555
95917
|
}
|
|
95556
95918
|
return 0;
|
|
95557
95919
|
}
|
|
95558
95920
|
if (order === 0 || order > 0 && !pinned) {
|
|
95559
95921
|
if (json) {
|
|
95560
|
-
const payload = { current
|
|
95922
|
+
const payload = { current, latest: target.version, updated: false, path: realExec };
|
|
95561
95923
|
deps.stdout.write(`${JSON.stringify(payload)}
|
|
95562
95924
|
`);
|
|
95563
95925
|
} else {
|
|
95564
|
-
deps.stdout.write(`candle ${
|
|
95926
|
+
deps.stdout.write(`candle ${current} is up to date
|
|
95565
95927
|
`);
|
|
95566
95928
|
}
|
|
95567
95929
|
return 0;
|
|
95568
95930
|
}
|
|
95569
95931
|
if (order > 0 && pinned)
|
|
95570
|
-
deps.stderr.write(`Warning: ${target.version} is a downgrade from ${
|
|
95932
|
+
deps.stderr.write(`Warning: ${target.version} is a downgrade from ${current}
|
|
95571
95933
|
`);
|
|
95572
95934
|
if (!deps.platformKey) {
|
|
95573
95935
|
writeLocalFailure(deps, {
|
|
@@ -95607,7 +95969,7 @@ async function update(args, ctx) {
|
|
|
95607
95969
|
const steps = __using(__stack, commandSteps(ctx), 0);
|
|
95608
95970
|
if (!json) {
|
|
95609
95971
|
const total = asset.size + (helperAsset?.size ?? 0);
|
|
95610
|
-
deps.stderr.write(`Updating candle ${
|
|
95972
|
+
deps.stderr.write(`Updating candle ${current} -> ${target.version} (${formatBytes(total)})
|
|
95611
95973
|
`);
|
|
95612
95974
|
}
|
|
95613
95975
|
steps.start("downloading SHA256SUMS");
|
|
@@ -95689,7 +96051,7 @@ async function update(args, ctx) {
|
|
|
95689
96051
|
const helperReport2 = helper !== null ? { name: HELPER_NAME, updated: true } : null;
|
|
95690
96052
|
if (json) {
|
|
95691
96053
|
const payload = {
|
|
95692
|
-
current
|
|
96054
|
+
current,
|
|
95693
96055
|
latest: target.version,
|
|
95694
96056
|
updated: true,
|
|
95695
96057
|
path: realExec,
|
|
@@ -95698,7 +96060,7 @@ async function update(args, ctx) {
|
|
|
95698
96060
|
deps.stdout.write(`${JSON.stringify(payload)}
|
|
95699
96061
|
`);
|
|
95700
96062
|
} else {
|
|
95701
|
-
deps.stdout.write(`Updated candle ${
|
|
96063
|
+
deps.stdout.write(`Updated candle ${current} -> ${target.version}
|
|
95702
96064
|
`);
|
|
95703
96065
|
if (helper !== null)
|
|
95704
96066
|
deps.stdout.write(`Installed ${HELPER_NAME} ${target.version} to ${helper.target}
|
|
@@ -96930,7 +97292,8 @@ async function installHelperForThisRelease(ctx) {
|
|
|
96930
97292
|
}
|
|
96931
97293
|
if (!deps.platformKey)
|
|
96932
97294
|
return { ok: false, message: "this CLI ships no security key helper for this platform" };
|
|
96933
|
-
const
|
|
97295
|
+
const home = releaseHomeFor(CLI_VERSION);
|
|
97296
|
+
const base2 = releaseBaseUrl(deps.env, home);
|
|
96934
97297
|
const tag = `cli-v${CLI_VERSION}`;
|
|
96935
97298
|
const name2 = helperAssetName(deps.platformKey);
|
|
96936
97299
|
const steps = __using(__stack, commandSteps(ctx), 0);
|
|
@@ -96940,6 +97303,11 @@ async function installHelperForThisRelease(ctx) {
|
|
|
96940
97303
|
steps.fail(`reading the ${tag} release manifest`);
|
|
96941
97304
|
return { ok: false, message: fetched.message };
|
|
96942
97305
|
}
|
|
97306
|
+
const mismatch = manifestHomeMismatch(fetched.manifest, home, tag);
|
|
97307
|
+
if (mismatch !== null) {
|
|
97308
|
+
steps.fail(`reading the ${tag} release manifest`);
|
|
97309
|
+
return { ok: false, message: `refusing the ${tag} release manifest: ${mismatch}` };
|
|
97310
|
+
}
|
|
96943
97311
|
const asset = fetched.manifest.helpers?.[deps.platformKey];
|
|
96944
97312
|
if (asset === undefined) {
|
|
96945
97313
|
steps.fail(`reading the ${tag} release manifest`);
|
package/package.json
CHANGED