@candledottv/cli 0.11.36 → 0.11.38
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 +2 -2
- package/dist/index.js +976 -412
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -46,6 +46,43 @@ var __export = (target, all) => {
|
|
|
46
46
|
};
|
|
47
47
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
48
48
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
49
|
+
var __dispose = Symbol.dispose || /* @__PURE__ */ Symbol.for("Symbol.dispose");
|
|
50
|
+
var __asyncDispose = Symbol.asyncDispose || /* @__PURE__ */ Symbol.for("Symbol.asyncDispose");
|
|
51
|
+
var __using = (stack, value, async) => {
|
|
52
|
+
if (value != null) {
|
|
53
|
+
if (typeof value !== "object" && typeof value !== "function")
|
|
54
|
+
throw TypeError('Object expected to be assigned to "using" declaration');
|
|
55
|
+
var dispose;
|
|
56
|
+
if (async)
|
|
57
|
+
dispose = value[__asyncDispose];
|
|
58
|
+
if (dispose === undefined)
|
|
59
|
+
dispose = value[__dispose];
|
|
60
|
+
if (typeof dispose !== "function")
|
|
61
|
+
throw TypeError("Object not disposable");
|
|
62
|
+
stack.push([async, dispose, value]);
|
|
63
|
+
} else if (async) {
|
|
64
|
+
stack.push([async]);
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
};
|
|
68
|
+
var __callDispose = (stack, error, hasError) => {
|
|
69
|
+
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
|
|
70
|
+
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
|
|
71
|
+
}, fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e), next = (it) => {
|
|
72
|
+
while (it = stack.pop()) {
|
|
73
|
+
try {
|
|
74
|
+
var result = it[1] && it[1].call(it[2]);
|
|
75
|
+
if (it[0])
|
|
76
|
+
return Promise.resolve(result).then(next, (e) => (fail(e), next()));
|
|
77
|
+
} catch (e) {
|
|
78
|
+
fail(e);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (hasError)
|
|
82
|
+
throw error;
|
|
83
|
+
};
|
|
84
|
+
return next();
|
|
85
|
+
};
|
|
49
86
|
|
|
50
87
|
// src/client.ts
|
|
51
88
|
function trimTrailingSlashes(url) {
|
|
@@ -37368,7 +37405,7 @@ var init_convert = __esm(() => {
|
|
|
37368
37405
|
});
|
|
37369
37406
|
|
|
37370
37407
|
// ../mcp/src/version.ts
|
|
37371
|
-
var SERVER_VERSION = "0.10.
|
|
37408
|
+
var SERVER_VERSION = "0.10.21";
|
|
37372
37409
|
|
|
37373
37410
|
// ../mcp/src/update-notice.ts
|
|
37374
37411
|
function newer(a, b) {
|
|
@@ -39647,18 +39684,53 @@ function perpsCandleWindow(opts, now) {
|
|
|
39647
39684
|
return usage3(`interval must be one of ${PERPS_CANDLE_INTERVALS.join(" ")}.`);
|
|
39648
39685
|
const count = bounded(opts.count, 100, 1, 5000, "count");
|
|
39649
39686
|
const raw = opts.end;
|
|
39650
|
-
const endTime = raw === undefined ? now :
|
|
39687
|
+
const endTime = raw === undefined ? now : epoch(raw);
|
|
39651
39688
|
const startTime = endTime - count * at(INTERVAL_MS, index);
|
|
39652
39689
|
if (!Number.isSafeInteger(endTime) || endTime < 0 || endTime > now || startTime < 0)
|
|
39653
39690
|
return usage3("end must be an ISO 8601 time or epoch milliseconds, no later than now.");
|
|
39654
39691
|
return { interval, count, startTime, endTime };
|
|
39655
39692
|
}
|
|
39693
|
+
function epoch(raw) {
|
|
39694
|
+
return typeof raw === "number" || /^\d+$/.test(raw) ? Number(raw) : /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})$/.test(raw) ? Date.parse(raw) : NaN;
|
|
39695
|
+
}
|
|
39696
|
+
function perpsFundingWindow(opts, now) {
|
|
39697
|
+
const endTime = opts.end === undefined ? now : epoch(opts.end);
|
|
39698
|
+
if (!Number.isSafeInteger(endTime) || endTime < 0 || endTime > now)
|
|
39699
|
+
return usage3("end must be an ISO 8601 time or epoch milliseconds, no later than now.");
|
|
39700
|
+
const startTime = opts.start === undefined ? endTime - 86400000 : epoch(opts.start);
|
|
39701
|
+
if (!Number.isSafeInteger(startTime) || startTime < 0 || startTime > endTime)
|
|
39702
|
+
return usage3("start must be an ISO 8601 time or epoch milliseconds, no later than end.");
|
|
39703
|
+
if (endTime - startTime > PERPS_FUNDING_HISTORY_MAX_MS)
|
|
39704
|
+
return usage3("start must be at most 30 days before end.");
|
|
39705
|
+
return { startTime, endTime };
|
|
39706
|
+
}
|
|
39656
39707
|
function bounded(value, fallback, min, max, name) {
|
|
39657
39708
|
const n = value ?? fallback;
|
|
39658
39709
|
if (!Number.isInteger(n) || n < min || n > max)
|
|
39659
39710
|
return usage3(`${name} must be an integer from ${min} to ${max}.`);
|
|
39660
39711
|
return n;
|
|
39661
39712
|
}
|
|
39713
|
+
function parsePerpsFundingHistory(value, coin, window) {
|
|
39714
|
+
if (!Array.isArray(value))
|
|
39715
|
+
return unavailable2();
|
|
39716
|
+
const funding = [];
|
|
39717
|
+
for (const row of value) {
|
|
39718
|
+
if (!object(row) || row.coin !== coin || !integer(row.time) || !decimal2(row.fundingRate) || !decimal2(row.premium))
|
|
39719
|
+
return unavailable2();
|
|
39720
|
+
if (row.time >= window.startTime && row.time <= window.endTime)
|
|
39721
|
+
funding.push({
|
|
39722
|
+
time: row.time,
|
|
39723
|
+
fundingRate: row.fundingRate,
|
|
39724
|
+
fundingAprPct: fixed(scaled(row.fundingRate) * 876000n, 2),
|
|
39725
|
+
premium: row.premium
|
|
39726
|
+
});
|
|
39727
|
+
}
|
|
39728
|
+
funding.sort((a, b) => a.time - b.time);
|
|
39729
|
+
return {
|
|
39730
|
+
funding: funding.slice(0, PERPS_FUNDING_HISTORY_MAX_ROWS),
|
|
39731
|
+
truncated: funding.length > PERPS_FUNDING_HISTORY_MAX_ROWS
|
|
39732
|
+
};
|
|
39733
|
+
}
|
|
39662
39734
|
|
|
39663
39735
|
class PerpsMarketReader {
|
|
39664
39736
|
opts;
|
|
@@ -39936,8 +40008,46 @@ class PerpsMarketReader {
|
|
|
39936
40008
|
candles: candles.slice(-window.count)
|
|
39937
40009
|
};
|
|
39938
40010
|
}
|
|
40011
|
+
async fundingHistory(coin, opts = {}) {
|
|
40012
|
+
this.key();
|
|
40013
|
+
const window = perpsFundingWindow(opts, this.now());
|
|
40014
|
+
const [config, core] = await Promise.all([this.config(), this.core()]);
|
|
40015
|
+
const canonical2 = at(core.universe, resolve(core, coin)).name;
|
|
40016
|
+
const value = await info(this.opts.fetcher, this.opts.network, {
|
|
40017
|
+
type: "fundingHistory",
|
|
40018
|
+
coin: canonical2,
|
|
40019
|
+
startTime: window.startTime,
|
|
40020
|
+
endTime: window.endTime
|
|
40021
|
+
});
|
|
40022
|
+
return {
|
|
40023
|
+
...this.envelope(config),
|
|
40024
|
+
coin: canonical2,
|
|
40025
|
+
startTime: window.startTime,
|
|
40026
|
+
endTime: window.endTime,
|
|
40027
|
+
...parsePerpsFundingHistory(value, canonical2, window)
|
|
40028
|
+
};
|
|
40029
|
+
}
|
|
40030
|
+
async predictedFunding() {
|
|
40031
|
+
this.key();
|
|
40032
|
+
const [config, core, extras] = await Promise.all([this.config(), this.core(), this.extras()]);
|
|
40033
|
+
if (extras.predicted === null)
|
|
40034
|
+
return unavailable2();
|
|
40035
|
+
const predicted = [];
|
|
40036
|
+
for (const meta of core.universe) {
|
|
40037
|
+
const row = extras.predicted.get(meta.name);
|
|
40038
|
+
if (row)
|
|
40039
|
+
predicted.push({
|
|
40040
|
+
coin: meta.name,
|
|
40041
|
+
fundingRate: row.fundingRate,
|
|
40042
|
+
fundingAprPct: fixed(scaled(row.fundingRate) * 876000n, 2),
|
|
40043
|
+
nextFundingTime: row.nextFundingTime
|
|
40044
|
+
});
|
|
40045
|
+
}
|
|
40046
|
+
predicted.sort((a, b) => a.coin < b.coin ? -1 : a.coin > b.coin ? 1 : 0);
|
|
40047
|
+
return { ...this.envelope(config, extras.warnings, this.extrasCache?.at ?? this.now()), predicted };
|
|
40048
|
+
}
|
|
39939
40049
|
}
|
|
39940
|
-
var INFO_URLS, ONE, DECIMAL, PerpsMarketError, PERPS_CANDLE_INTERVALS, INTERVAL_MS;
|
|
40050
|
+
var INFO_URLS, ONE, DECIMAL, PerpsMarketError, PERPS_CANDLE_INTERVALS, INTERVAL_MS, PERPS_FUNDING_HISTORY_MAX_MS, PERPS_FUNDING_HISTORY_MAX_ROWS = 500;
|
|
39941
40051
|
var init_hyperliquid_markets = __esm(() => {
|
|
39942
40052
|
INFO_URLS = {
|
|
39943
40053
|
mainnet: "https://api.hyperliquid.xyz/info",
|
|
@@ -39987,6 +40097,7 @@ var init_hyperliquid_markets = __esm(() => {
|
|
|
39987
40097
|
604800000,
|
|
39988
40098
|
2678400000
|
|
39989
40099
|
];
|
|
40100
|
+
PERPS_FUNDING_HISTORY_MAX_MS = 30 * 86400000;
|
|
39990
40101
|
});
|
|
39991
40102
|
|
|
39992
40103
|
// ../mcp/src/perps-history.ts
|
|
@@ -42628,7 +42739,7 @@ var init_tools = __esm(() => {
|
|
|
42628
42739
|
init_plans();
|
|
42629
42740
|
init_update_notice();
|
|
42630
42741
|
VAMP_CANDIDATES_DESCRIPTION = "Lists Solana tokens that are close to graduating or graduated in the last 6 hours whose ticker other tokens " + "are copying, most-copied in the last 24 hours first. Each entry carries a launch draft (name, ticker, image URL) " + "for `candle_launch_token` or `candle_launch_and_seed`; draft notes identify fields you must supply or adjust. " + "Upload your own image through the agent image upload when the image is unavailable. Use `dryRun: true` first. " + "Counts exclude the candidate itself and cover both Solana and Hood launches at the venues returned in " + "`coverage`, not Candle's own. `coverage.since` is the returned Solana coverage date; Hood history may differ.";
|
|
42631
|
-
CUSTOM_CURVE_RULE = 'mode "exp-custom" with curve { virtualQuote, threshold } launches a Solana curve with your own V (the virtual ' + "quote it starts with) and G (the quote it raises before graduating), in whole SOL or CNDL as decimal strings, " + "accepted when V is 1 to 100 SOL and G 1 to 250 SOL (66,000 to 6,600,000 and 66,000 to 16,500,000 CNDL), when " + "every price up to 20 x G stays under the ceiling (V + 20G)^2 < V x X at this stakerAllocationBps, when neither " + "is finer than the quote's decimals, and when the dev buy is at most 10% of G; a curve past a limit is " + "CURVE_OUT_OF_RANGE, and at the ceiling it carries maxThresholdRaw, the largest G that fits. " + "Dry-run it first (dryRun: true): the dry run spends nothing and returns curveTerms, the market cap at start and " + "at graduation
|
|
42742
|
+
CUSTOM_CURVE_RULE = 'mode "exp-custom" with curve { virtualQuote, threshold } launches a Solana curve with your own V (the virtual ' + "quote it starts with) and G (the quote it raises before graduating), in whole SOL or CNDL as decimal strings, " + "accepted when V is 1 to 100 SOL and G 1 to 250 SOL (66,000 to 6,600,000 and 66,000 to 16,500,000 CNDL), when " + "every price up to 20 x G stays under the ceiling (V + 20G)^2 < V x X at this stakerAllocationBps, when neither " + "is finer than the quote's decimals, and when the dev buy is at most 0.5 SOL (2,000,000 CNDL), or 10% of G when " + "that is lower (every exp- mode; an operator may set the 0.5 SOL lower); a curve past a limit is " + "CURVE_OUT_OF_RANGE, and at the ceiling it carries maxThresholdRaw, the largest G that fits. " + "Dry-run it first (dryRun: true): the dry run spends nothing and returns curveTerms, the market cap at start and " + "at graduation, the supply sold by graduation, the share your own buyAmount takes, the fees and the migration " + "terms, and devBuyMax, the dev-buy ceiling that applies. Only where the API has EXP_CUSTOM_CURVES on; " + "elsewhere exp-custom is refused as an unknown mode.";
|
|
42632
42743
|
localProfileTools = new WeakMap;
|
|
42633
42744
|
stakingWriteShape = {
|
|
42634
42745
|
payer: exports_external.enum(["main", "linked"]).optional().describe("main is the embedded wallet; linked names a linked or TEE wallet"),
|
|
@@ -42707,12 +42818,23 @@ var init_tools = __esm(() => {
|
|
|
42707
42818
|
bannerUrl: exports_external.string().optional().describe("Optional https URL to WIDE artwork for the token page's banner strip (wider than 1.5:1, " + "e.g. 1200x630). This is where a share card or OG image belongs. A square image here is " + "rejected with BANNER_WRONG_SHAPE: send the wide original, not a cropped copy. Omit it and the strip " + "falls back to imageUrl"),
|
|
42708
42819
|
chain: exports_external.string().optional().describe('"solana" or "hood"; defaults to solana'),
|
|
42709
42820
|
quoteAsset: exports_external.string().optional().describe("Quote asset symbol; defaults per chain"),
|
|
42710
|
-
mode: exports_external.string().optional().describe('"open" or "exclusive"; defaults to open. "test-open" / "test-exclusive" launch the ' + "low-threshold test curves (~1/80 economics) where the API has ENABLE_TEST_CURVES on. " + '"exp-custom" launches a Solana curve with your own V and G, given in curve, where the API has it ' + "switched on"),
|
|
42821
|
+
mode: exports_external.string().optional().describe('"open" or "exclusive"; defaults to open. "test-open" / "test-exclusive" launch the ' + "low-threshold test curves (~1/80 economics) where the API has ENABLE_TEST_CURVES on. " + '"exp-a", "exp-b" and "exp-c" are the experimental fast Solana curves (sol or cndl). ' + '"exp-custom" launches a Solana curve with your own V and G, given in curve, where the API has it ' + "switched on"),
|
|
42711
42822
|
curve: exports_external.object({
|
|
42712
42823
|
virtualQuote: exports_external.string().describe('V: the virtual quote the curve starts with, in whole quote units, e.g. "12" SOL'),
|
|
42713
42824
|
threshold: exports_external.string().describe('G: the quote the curve raises before it graduates, in whole quote units, e.g. "40" SOL')
|
|
42714
42825
|
}).strict().optional().describe('mode "exp-custom" only, and required there. Decimal strings in whole units of the quote asset (sol or ' + "cndl), never raw units. Dry-run first: the API checks the limits and returns the curve's terms"),
|
|
42826
|
+
creatorFeeBps: exports_external.union([exports_external.literal(0), exports_external.literal(100), exports_external.literal(200)]).optional().describe("An exp- mode only: an extra creator fee of 100 or 200 basis points on top of the curve's 1% and the " + "pool's 0.5%, paid to the creator in full. 0 is the same as leaving it out"),
|
|
42715
42827
|
stakerAllocationBps: exports_external.number().optional().describe("Staker allocation in basis points"),
|
|
42828
|
+
poolFee: exports_external.union([
|
|
42829
|
+
exports_external.object({ tier: exports_external.union([exports_external.literal(25), exports_external.literal(50), exports_external.literal(100)]) }).strict(),
|
|
42830
|
+
exports_external.object({
|
|
42831
|
+
startBps: exports_external.number().int().max(2500),
|
|
42832
|
+
endBps: exports_external.union([exports_external.literal(25), exports_external.literal(50), exports_external.literal(100)]),
|
|
42833
|
+
minutes: exports_external.number().positive().max(30)
|
|
42834
|
+
}).strict()
|
|
42835
|
+
]).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. Omit for legacy"),
|
|
42837
|
+
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'),
|
|
42716
42838
|
dexVersion: exports_external.string().nullable().optional().describe('"v3" or "v4"; required for hood launches'),
|
|
42717
42839
|
buyAmount: exports_external.union([exports_external.string(), exports_external.number()]).optional().describe("Initial buy, in quote base units (string or number)"),
|
|
42718
42840
|
description: exports_external.string().optional(),
|
|
@@ -70366,7 +70488,7 @@ init_render();
|
|
|
70366
70488
|
init_secret_store();
|
|
70367
70489
|
|
|
70368
70490
|
// src/version.ts
|
|
70369
|
-
var CLI_VERSION = "0.11.
|
|
70491
|
+
var CLI_VERSION = "0.11.38";
|
|
70370
70492
|
|
|
70371
70493
|
// src/commands/auth.ts
|
|
70372
70494
|
init_keys_embedded_wallet();
|
|
@@ -70766,6 +70888,86 @@ async function authStatus(args, ctx) {
|
|
|
70766
70888
|
return exitCode;
|
|
70767
70889
|
}
|
|
70768
70890
|
|
|
70891
|
+
// src/terminal-theme.ts
|
|
70892
|
+
var TERMINAL_TOKENS = {
|
|
70893
|
+
web: {
|
|
70894
|
+
primary: "#32fe9f",
|
|
70895
|
+
positive: "#32d583",
|
|
70896
|
+
negative: "#f2536d",
|
|
70897
|
+
warning: "#f5a524",
|
|
70898
|
+
edge: "#26262b"
|
|
70899
|
+
},
|
|
70900
|
+
dark: {
|
|
70901
|
+
brand: { hex: "#32fe9f", ansi256: 48, ansi16: 92 },
|
|
70902
|
+
positive: { hex: "#32d583", ansi256: 42, ansi16: 92 },
|
|
70903
|
+
negative: { hex: "#f2536d", ansi256: 204, ansi16: 91 },
|
|
70904
|
+
warning: { hex: "#f5a524", ansi256: 214, ansi16: 93 }
|
|
70905
|
+
},
|
|
70906
|
+
light: {
|
|
70907
|
+
brand: { hex: "#006b40", ansi256: 22, ansi16: 32 },
|
|
70908
|
+
positive: { hex: "#087443", ansi256: 22, ansi16: 32 },
|
|
70909
|
+
negative: { hex: "#b4233c", ansi256: 125, ansi16: 31 },
|
|
70910
|
+
warning: { hex: "#875400", ansi256: 94, ansi16: 33 }
|
|
70911
|
+
}
|
|
70912
|
+
};
|
|
70913
|
+
|
|
70914
|
+
// src/presentation.ts
|
|
70915
|
+
function terminalStyle(deps, stream, machine = false) {
|
|
70916
|
+
const { env, presentation } = deps;
|
|
70917
|
+
const styled = !(machine || presentation?.plain || env.CANDLE_PLAIN === "1" || !deps.isTTY[stream] || env.CI || env.TERM === "dumb");
|
|
70918
|
+
const noColor = presentation?.noColor || Boolean(env.NO_COLOR);
|
|
70919
|
+
const color = !styled || noColor ? 0 : /truecolor|24bit/i.test(env.COLORTERM ?? "") ? 24 : /256color/i.test(env.TERM ?? "") ? 256 : env.TERM ? 16 : 0;
|
|
70920
|
+
const background = Number(env.COLORFGBG?.split(";").at(-1));
|
|
70921
|
+
const theme = Number.isFinite(background) && background >= 0 && background <= 6 ? "dark" : "light";
|
|
70922
|
+
const columns = deps.columns?.[stream];
|
|
70923
|
+
return {
|
|
70924
|
+
width: columns && Number.isFinite(columns) ? Math.max(20, Math.floor(columns)) : 118,
|
|
70925
|
+
styled,
|
|
70926
|
+
color,
|
|
70927
|
+
theme,
|
|
70928
|
+
motion: styled && !noColor
|
|
70929
|
+
};
|
|
70930
|
+
}
|
|
70931
|
+
function accent(text, style, role = "brand") {
|
|
70932
|
+
if (!style.color)
|
|
70933
|
+
return text;
|
|
70934
|
+
const token = TERMINAL_TOKENS[style.theme][role];
|
|
70935
|
+
const code = style.color === 24 ? `38;2;${token.hex.slice(1).match(/../g)?.map((part) => Number.parseInt(part, 16)).join(";")}` : style.color === 256 ? `38;5;${token.ansi256}` : `${token.ansi16}`;
|
|
70936
|
+
return `\x1B[${code}m${text}\x1B[0m`;
|
|
70937
|
+
}
|
|
70938
|
+
function wrapText(text, width) {
|
|
70939
|
+
const lines = [];
|
|
70940
|
+
let line = "";
|
|
70941
|
+
for (const word3 of text.split(" ")) {
|
|
70942
|
+
if (!line || line.length + word3.length + 1 <= width)
|
|
70943
|
+
line += `${line ? " " : ""}${word3}`;
|
|
70944
|
+
else {
|
|
70945
|
+
lines.push(line);
|
|
70946
|
+
line = word3;
|
|
70947
|
+
}
|
|
70948
|
+
}
|
|
70949
|
+
lines.push(line);
|
|
70950
|
+
return lines;
|
|
70951
|
+
}
|
|
70952
|
+
var WORDMARK = ["█▀▀ ▄▀█ █▄░█ █▀▄ █░░ █▀▀", "█▄▄ █▀█ █░▀█ █▄▀ █▄▄ ██▄"];
|
|
70953
|
+
function brandHeader(style) {
|
|
70954
|
+
if (!style.styled)
|
|
70955
|
+
return [];
|
|
70956
|
+
const lines = [
|
|
70957
|
+
...style.width >= 80 ? WORDMARK : ["CANDLE"],
|
|
70958
|
+
`CLI ${CLI_VERSION}`,
|
|
70959
|
+
"",
|
|
70960
|
+
...wrapText("Your wallets and agent tools, one command away.", style.width - 6)
|
|
70961
|
+
];
|
|
70962
|
+
const width = Math.min(style.width - 2, 68);
|
|
70963
|
+
return [
|
|
70964
|
+
accent(`┌${"─".repeat(width - 2)}┐`, style),
|
|
70965
|
+
...lines.map((line) => `│ ${accent(line, style)}${" ".repeat(Math.max(0, width - 4 - line.length))} │`),
|
|
70966
|
+
accent(`└${"─".repeat(width - 2)}┘`, style),
|
|
70967
|
+
""
|
|
70968
|
+
];
|
|
70969
|
+
}
|
|
70970
|
+
|
|
70769
70971
|
// src/help.ts
|
|
70770
70972
|
var GROUPS = [
|
|
70771
70973
|
"Start here",
|
|
@@ -70776,6 +70978,7 @@ var GROUPS = [
|
|
|
70776
70978
|
"Maintain"
|
|
70777
70979
|
];
|
|
70778
70980
|
var ENVIRONMENT = [
|
|
70981
|
+
{ name: "CANDLE_PLAIN", description: "Set to 1 for --plain output" },
|
|
70779
70982
|
{
|
|
70780
70983
|
name: "CANDLE_CONFIG_DIR",
|
|
70781
70984
|
description: "Where vault.enc, tee-wallets.enc, credentials and config live (default ~/.config/candle). Set it once instead of passing --keystore."
|
|
@@ -70838,6 +71041,8 @@ var KEYSTORE_FLAG = {
|
|
|
70838
71041
|
description: "The vault file for this invocation. For a permanent location set CANDLE_CONFIG_DIR instead."
|
|
70839
71042
|
};
|
|
70840
71043
|
var GLOBAL_FLAGS = [
|
|
71044
|
+
{ invocation: "--plain", description: "Plain ASCII: no banner, boxes, color or animation" },
|
|
71045
|
+
{ invocation: "--no-color", description: "Suppress color and decorative animation; keep terminal layout" },
|
|
70841
71046
|
{ invocation: "--profile <name>", description: "Act as a named profile" },
|
|
70842
71047
|
{ invocation: "--api-url <url>", description: "Override the API base URL" },
|
|
70843
71048
|
{ invocation: "--json", description: "Machine-readable output: exactly one JSON value on stdout" },
|
|
@@ -71241,9 +71446,9 @@ var HELP = {
|
|
|
71241
71446
|
launch: {
|
|
71242
71447
|
group: "Trade",
|
|
71243
71448
|
summary: "Create a token on Solana or Hood, with an optional first buy",
|
|
71244
|
-
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). 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. --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.",
|
|
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). 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. --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.",
|
|
71245
71450
|
usage: [
|
|
71246
|
-
"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]"
|
|
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]"
|
|
71247
71452
|
],
|
|
71248
71453
|
rows: [],
|
|
71249
71454
|
flags: [
|
|
@@ -71267,13 +71472,46 @@ var HELP = {
|
|
|
71267
71472
|
{
|
|
71268
71473
|
invocation: "--dex-version v3|v4",
|
|
71269
71474
|
description: "Hood only, required there: the Uniswap version the curve graduates to"
|
|
71475
|
+
},
|
|
71476
|
+
{
|
|
71477
|
+
invocation: "--pool-fee 25|50|100",
|
|
71478
|
+
description: "Solana only: the graduated pool's fee in bps (default 50), or with --pool-fee-from the rate a falling fee settles at"
|
|
71479
|
+
},
|
|
71480
|
+
{
|
|
71481
|
+
invocation: "--pool-fee-from <bps> --pool-fee-minutes <min>",
|
|
71482
|
+
description: "Solana only, with --pool-fee: an anti-snipe fee that starts at <bps> (at most 2500) and falls to the --pool-fee rate over <min> minutes (at most 30)"
|
|
71483
|
+
},
|
|
71484
|
+
{
|
|
71485
|
+
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; the staker carve plus team allocation must fit the tier's limit (the dry run returns it)"
|
|
71487
|
+
},
|
|
71488
|
+
{
|
|
71489
|
+
invocation: "--exp-pool-shape floor|full_range",
|
|
71490
|
+
description: "Solana exp- curves: full_range opens the pool across the whole price range"
|
|
71491
|
+
},
|
|
71492
|
+
{
|
|
71493
|
+
invocation: "--mode <mode>",
|
|
71494
|
+
description: "open or exclusive; test-open or test-exclusive (ENABLE_TEST_CURVES); exp-a, exp-b, exp-c or exp-custom (Solana, sol or cndl)"
|
|
71495
|
+
},
|
|
71496
|
+
{
|
|
71497
|
+
invocation: "--curve-v <V> --curve-g <G>",
|
|
71498
|
+
description: "exp-custom only, and required there: the curve's virtual quote and threshold, in whole units of the quote asset"
|
|
71499
|
+
},
|
|
71500
|
+
{
|
|
71501
|
+
invocation: "--creator-fee-bps 0|100|200",
|
|
71502
|
+
description: "An exp- mode only: an extra creator fee on the curve and the pool, paid to the creator in full"
|
|
71503
|
+
},
|
|
71504
|
+
{
|
|
71505
|
+
invocation: "--dry-run",
|
|
71506
|
+
description: "Print Candle's preview of this launch, with the dev-buy ceiling (0.5 SOL, or 10% of the threshold when lower, on an exp- curve), and build, sign and record nothing"
|
|
71270
71507
|
}
|
|
71271
71508
|
],
|
|
71272
71509
|
examples: [
|
|
71273
71510
|
"candle launch --name Demo --symbol DEMO --image ./logo.png --wallet AgentOne",
|
|
71274
71511
|
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne",
|
|
71275
71512
|
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet HoodAgent --dex-version v4",
|
|
71276
|
-
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne --buy 0.1"
|
|
71513
|
+
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne --buy 0.1",
|
|
71514
|
+
"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"
|
|
71277
71515
|
],
|
|
71278
71516
|
env: ENV_API
|
|
71279
71517
|
},
|
|
@@ -71953,36 +72191,15 @@ function documentedFlags(topic) {
|
|
|
71953
72191
|
return [...flags].sort();
|
|
71954
72192
|
}
|
|
71955
72193
|
var WIDTH = 118;
|
|
71956
|
-
function
|
|
71957
|
-
if (text === "")
|
|
71958
|
-
return [""];
|
|
71959
|
-
if (width < 20)
|
|
71960
|
-
return [text];
|
|
71961
|
-
const lines = [];
|
|
71962
|
-
let line = "";
|
|
71963
|
-
for (const word3 of text.split(" ")) {
|
|
71964
|
-
if (line === "")
|
|
71965
|
-
line = word3;
|
|
71966
|
-
else if (`${line} ${word3}`.length <= width)
|
|
71967
|
-
line = `${line} ${word3}`;
|
|
71968
|
-
else {
|
|
71969
|
-
lines.push(line);
|
|
71970
|
-
line = word3;
|
|
71971
|
-
}
|
|
71972
|
-
}
|
|
71973
|
-
if (line !== "")
|
|
71974
|
-
lines.push(line);
|
|
71975
|
-
return lines;
|
|
71976
|
-
}
|
|
71977
|
-
function renderRows(rows, maxColumn) {
|
|
72194
|
+
function renderRows(rows, maxColumn, width = WIDTH) {
|
|
71978
72195
|
if (rows.length === 0)
|
|
71979
72196
|
return [];
|
|
71980
72197
|
const longest = Math.max(...rows.map((row) => row.invocation.length));
|
|
71981
|
-
const column = Math.min(2 + longest + 2, maxColumn);
|
|
72198
|
+
const column = Math.min(2 + longest + 2, maxColumn, Math.max(4, width - 24));
|
|
71982
72199
|
const pad = " ".repeat(column);
|
|
71983
72200
|
const out = [];
|
|
71984
72201
|
for (const row of rows) {
|
|
71985
|
-
const [first = "", ...rest] =
|
|
72202
|
+
const [first = "", ...rest] = wrapText(row.description, width - column);
|
|
71986
72203
|
if (2 + row.invocation.length + 2 <= column)
|
|
71987
72204
|
out.push(` ${row.invocation.padEnd(column - 2)}${first}`);
|
|
71988
72205
|
else
|
|
@@ -71992,11 +72209,13 @@ function renderRows(rows, maxColumn) {
|
|
|
71992
72209
|
}
|
|
71993
72210
|
return out;
|
|
71994
72211
|
}
|
|
71995
|
-
function renderEnv(names) {
|
|
72212
|
+
function renderEnv(names, width = WIDTH) {
|
|
71996
72213
|
const vars = names.map((name) => ENVIRONMENT.find((entry) => entry.name === name)).filter((entry) => entry !== undefined);
|
|
71997
|
-
return renderRows(vars.map((entry) => ({ invocation: entry.name, description: entry.description })), 32);
|
|
72214
|
+
return renderRows(vars.map((entry) => ({ invocation: entry.name, description: entry.description })), 32, width);
|
|
71998
72215
|
}
|
|
71999
|
-
function renderTopLevel() {
|
|
72216
|
+
function renderTopLevel(style) {
|
|
72217
|
+
if (style)
|
|
72218
|
+
return renderPresentedTopLevel(style);
|
|
72000
72219
|
const out = [
|
|
72001
72220
|
"candle: Candle from the terminal: trade, launch, and hold your keys on your own machine",
|
|
72002
72221
|
"",
|
|
@@ -72021,24 +72240,67 @@ function renderTopLevel() {
|
|
|
72021
72240
|
`)}
|
|
72022
72241
|
`;
|
|
72023
72242
|
}
|
|
72024
|
-
function renderTopic(word3) {
|
|
72243
|
+
function renderTopic(word3, style) {
|
|
72025
72244
|
const topic = topicFor(word3);
|
|
72026
72245
|
if (!topic)
|
|
72027
72246
|
return;
|
|
72028
72247
|
const canonical = Object.keys(HELP).find((key) => HELP[key] === topic) ?? word3;
|
|
72029
72248
|
const display = displayName(canonical);
|
|
72030
|
-
const
|
|
72249
|
+
const width = style?.width ?? WIDTH;
|
|
72250
|
+
const out = [wrapText(`candle ${display}: ${topic.description}`, width).join(`
|
|
72031
72251
|
`)];
|
|
72032
72252
|
out.push("", `Usage: ${topic.usage.join(`
|
|
72033
72253
|
`)}`);
|
|
72034
72254
|
if (topic.rows.length > 0)
|
|
72035
|
-
out.push("", ...renderRows(topic.rows, 58));
|
|
72255
|
+
out.push("", ...renderRows(topic.rows, 58, width));
|
|
72036
72256
|
if (topic.flags && topic.flags.length > 0) {
|
|
72037
|
-
out.push("", `Flags every ${display} subcommand takes`, ...renderRows(topic.flags, 28));
|
|
72257
|
+
out.push("", `Flags every ${display} subcommand takes`, ...renderRows(topic.flags, 28, width));
|
|
72038
72258
|
}
|
|
72039
72259
|
out.push("", "Examples", ...topic.examples.map((example) => ` ${example}`));
|
|
72040
72260
|
if (topic.env && topic.env.length > 0)
|
|
72041
|
-
out.push("", "Environment", ...renderEnv(topic.env));
|
|
72261
|
+
out.push("", "Environment", ...renderEnv(topic.env, width));
|
|
72262
|
+
return `${out.join(`
|
|
72263
|
+
`)}
|
|
72264
|
+
`;
|
|
72265
|
+
}
|
|
72266
|
+
function renderPresentedTopLevel(style) {
|
|
72267
|
+
const { width } = style;
|
|
72268
|
+
const heading = (text) => accent(text, style);
|
|
72269
|
+
const out = [
|
|
72270
|
+
...brandHeader(style),
|
|
72271
|
+
...!style.styled ? [...wrapText("Candle CLI: Your wallets and agent tools, one command away.", width), ""] : [],
|
|
72272
|
+
heading("Start here"),
|
|
72273
|
+
...renderRows([
|
|
72274
|
+
{ invocation: "candle setup", description: "Connect your account" },
|
|
72275
|
+
{ invocation: "candle portfolio", description: "Read your holdings after setup" },
|
|
72276
|
+
{ invocation: "candle mcp", description: "Connect an MCP client using this install" }
|
|
72277
|
+
], 24, width),
|
|
72278
|
+
"",
|
|
72279
|
+
...wrapText("Usage: candle <command> [<subcommand>] [flags]", width),
|
|
72280
|
+
" candle help <command>"
|
|
72281
|
+
];
|
|
72282
|
+
const group = (name, available) => [
|
|
72283
|
+
heading(name),
|
|
72284
|
+
...renderRows(Object.entries(HELP).filter(([, topic]) => topic.group === name).map(([word3, topic]) => ({ invocation: displayName(word3), description: topic.summary })), 18, available)
|
|
72285
|
+
];
|
|
72286
|
+
for (let i = 0;i < GROUPS.length; i++) {
|
|
72287
|
+
const name = GROUPS[i];
|
|
72288
|
+
if (width >= 100 && (name === "Trade" || name === "Custody, on this machine")) {
|
|
72289
|
+
const column = Math.floor((width - 4) / 2);
|
|
72290
|
+
const left = group(name, column);
|
|
72291
|
+
const right = group(GROUPS[++i], column);
|
|
72292
|
+
out.push("");
|
|
72293
|
+
for (let row = 0;row < Math.max(left.length, right.length); row++) {
|
|
72294
|
+
const first = left[row] ?? "";
|
|
72295
|
+
const visible = first.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
72296
|
+
out.push(`${first}${" ".repeat(Math.max(4, column - visible + 4))}${right[row] ?? ""}`.trimEnd());
|
|
72297
|
+
}
|
|
72298
|
+
} else
|
|
72299
|
+
out.push("", ...group(name, width));
|
|
72300
|
+
}
|
|
72301
|
+
out.push("", heading("Global flags"), ...renderRows(GLOBAL_FLAGS, 28, width));
|
|
72302
|
+
out.push("", heading("Environment"), ...renderEnv(ENVIRONMENT.map((entry) => entry.name), width));
|
|
72303
|
+
out.push("", heading("Plug-ins"), ...renderRows([PLUGIN_LINE], 26, width));
|
|
72042
72304
|
return `${out.join(`
|
|
72043
72305
|
`)}
|
|
72044
72306
|
`;
|
|
@@ -76876,9 +77138,9 @@ async function planTransfer(input) {
|
|
|
76876
77138
|
}
|
|
76877
77139
|
const feeLines = [];
|
|
76878
77140
|
if (profile.newerTransferFee || profile.olderTransferFee) {
|
|
76879
|
-
const
|
|
76880
|
-
const { feeRaw, postFeeAmountRaw } = transferFeeFor(profile, raw,
|
|
76881
|
-
feeLines.push(`post-fee ${postFeeAmountRaw} raw arrives (${feeRaw} raw withheld by the mint at epoch ${
|
|
77141
|
+
const epoch2 = await rpc.getEpoch();
|
|
77142
|
+
const { feeRaw, postFeeAmountRaw } = transferFeeFor(profile, raw, epoch2);
|
|
77143
|
+
feeLines.push(`post-fee ${postFeeAmountRaw} raw arrives (${feeRaw} raw withheld by the mint at epoch ${epoch2})`);
|
|
76882
77144
|
}
|
|
76883
77145
|
instructions.push(tokenTransferChecked({
|
|
76884
77146
|
source,
|
|
@@ -83029,7 +83291,7 @@ async function sweepEverythingTo(input) {
|
|
|
83029
83291
|
});
|
|
83030
83292
|
}
|
|
83031
83293
|
}
|
|
83032
|
-
let
|
|
83294
|
+
let epoch2;
|
|
83033
83295
|
for (const acct of tokenAccounts) {
|
|
83034
83296
|
const token2022 = acct.programId === TOKEN_2022_PROGRAM_ID;
|
|
83035
83297
|
if (acct.state !== "initialized") {
|
|
@@ -83072,9 +83334,9 @@ async function sweepEverythingTo(input) {
|
|
|
83072
83334
|
for (const risk of profile.risks)
|
|
83073
83335
|
say(` ${acct.mint}: ${risk.message}`);
|
|
83074
83336
|
if (profile.newerTransferFee || profile.olderTransferFee) {
|
|
83075
|
-
|
|
83076
|
-
const { feeRaw, postFeeAmountRaw } = transferFeeFor(profile, amount,
|
|
83077
|
-
say(` ${acct.mint}: ${postFeeAmountRaw} raw will arrive; ${feeRaw} raw is withheld by the mint at epoch ${
|
|
83337
|
+
epoch2 ??= await rpc.getEpoch();
|
|
83338
|
+
const { feeRaw, postFeeAmountRaw } = transferFeeFor(profile, amount, epoch2);
|
|
83339
|
+
say(` ${acct.mint}: ${postFeeAmountRaw} raw will arrive; ${feeRaw} raw is withheld by the mint at epoch ${epoch2}`);
|
|
83078
83340
|
}
|
|
83079
83341
|
}
|
|
83080
83342
|
const existing = await rpc.getAccountInfo(encodePubkey(destinationAta));
|
|
@@ -83844,10 +84106,10 @@ async function feesClaim(args, ctx) {
|
|
|
83844
84106
|
async function help(args, ctx) {
|
|
83845
84107
|
const word3 = args[0];
|
|
83846
84108
|
if (word3 === undefined) {
|
|
83847
|
-
ctx.deps.stdout.write(renderTopLevel());
|
|
84109
|
+
ctx.deps.stdout.write(renderTopLevel(terminalStyle(ctx.deps, "stdout", ctx.json)));
|
|
83848
84110
|
return 0;
|
|
83849
84111
|
}
|
|
83850
|
-
const topic = renderTopic(word3);
|
|
84112
|
+
const topic = renderTopic(word3, terminalStyle(ctx.deps, "stdout", ctx.json));
|
|
83851
84113
|
if (topic === undefined) {
|
|
83852
84114
|
ctx.deps.stderr.write(`Unknown command: ${word3}
|
|
83853
84115
|
`);
|
|
@@ -84496,8 +84758,51 @@ init_render();
|
|
|
84496
84758
|
init_solana_endpoint();
|
|
84497
84759
|
init_solana_lite();
|
|
84498
84760
|
init_trading();
|
|
84761
|
+
|
|
84762
|
+
// src/commands/pool-options.ts
|
|
84763
|
+
var POOL_FLAGS = ["--pool-fee", "--pool-fee-from", "--pool-fee-minutes", "--pool-open", "--exp-pool-shape"];
|
|
84764
|
+
var TIERS = ["25", "50", "100"];
|
|
84765
|
+
function poolRequestFields(flags) {
|
|
84766
|
+
const out = {};
|
|
84767
|
+
const fee = flags["--pool-fee"];
|
|
84768
|
+
const from = flags["--pool-fee-from"];
|
|
84769
|
+
const minutes = flags["--pool-fee-minutes"];
|
|
84770
|
+
if ((from !== undefined || minutes !== undefined) && fee === undefined)
|
|
84771
|
+
throw new Error("--pool-fee-from and --pool-fee-minutes need --pool-fee <25|50|100>, the rate the fall ends at");
|
|
84772
|
+
if (fee !== undefined) {
|
|
84773
|
+
if (!TIERS.includes(fee))
|
|
84774
|
+
throw new Error("--pool-fee takes 25, 50 or 100 (bps)");
|
|
84775
|
+
if (from === undefined !== (minutes === undefined))
|
|
84776
|
+
throw new Error("A falling pool fee needs both --pool-fee-from <bps> and --pool-fee-minutes <minutes>");
|
|
84777
|
+
if (from === undefined)
|
|
84778
|
+
out.poolFee = { tier: Number(fee) };
|
|
84779
|
+
else {
|
|
84780
|
+
if (!/^\d+$/.test(from))
|
|
84781
|
+
throw new Error("--pool-fee-from takes whole bps");
|
|
84782
|
+
const m = Number(minutes);
|
|
84783
|
+
if (!Number.isFinite(m) || m <= 0)
|
|
84784
|
+
throw new Error("--pool-fee-minutes takes a positive number of minutes");
|
|
84785
|
+
out.poolFee = { startBps: Number(from), endBps: Number(fee), minutes: m };
|
|
84786
|
+
}
|
|
84787
|
+
}
|
|
84788
|
+
const open8 = flags["--pool-open"];
|
|
84789
|
+
if (open8 !== undefined) {
|
|
84790
|
+
if (open8 !== "legacy" && open8 !== "curve_price")
|
|
84791
|
+
throw new Error("--pool-open takes legacy or curve_price");
|
|
84792
|
+
out.poolOpen = open8;
|
|
84793
|
+
}
|
|
84794
|
+
const shape = flags["--exp-pool-shape"];
|
|
84795
|
+
if (shape !== undefined) {
|
|
84796
|
+
if (shape !== "floor" && shape !== "full_range")
|
|
84797
|
+
throw new Error("--exp-pool-shape takes floor or full_range");
|
|
84798
|
+
out.expPoolShape = shape;
|
|
84799
|
+
}
|
|
84800
|
+
return out;
|
|
84801
|
+
}
|
|
84802
|
+
|
|
84803
|
+
// src/commands/launch.ts
|
|
84499
84804
|
init_swap();
|
|
84500
|
-
var USAGE6 = "Usage: candle launch --name <name> --symbol <symbol> --image <path>|--image-url <https-url> --wallet <tee> [--banner <file|URL>] [--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.";
|
|
84805
|
+
var USAGE6 = "Usage: candle launch --name <name> --symbol <symbol> --image <path>|--image-url <https-url> --wallet <tee> [--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.";
|
|
84501
84806
|
var QUOTE_CHAIN = {
|
|
84502
84807
|
sol: "solana",
|
|
84503
84808
|
usdc: "solana",
|
|
@@ -84530,6 +84835,36 @@ function buyRefusal(error) {
|
|
|
84530
84835
|
details: error.details
|
|
84531
84836
|
});
|
|
84532
84837
|
}
|
|
84838
|
+
function curveFlagProblem(flags) {
|
|
84839
|
+
const mode = flags["--mode"];
|
|
84840
|
+
const v = flags["--curve-v"];
|
|
84841
|
+
const g = flags["--curve-g"];
|
|
84842
|
+
if (v === undefined !== (g === undefined))
|
|
84843
|
+
return "--curve-v and --curve-g go together: a curve is its V and its G.";
|
|
84844
|
+
if (v !== undefined && mode !== "exp-custom")
|
|
84845
|
+
return "--curve-v and --curve-g take --mode exp-custom.";
|
|
84846
|
+
if (mode === "exp-custom" && v === undefined)
|
|
84847
|
+
return "--mode exp-custom needs --curve-v and --curve-g: the curve's V and G in whole units of the quote asset.";
|
|
84848
|
+
for (const [flag, value] of [
|
|
84849
|
+
["--curve-v", v],
|
|
84850
|
+
["--curve-g", g]
|
|
84851
|
+
]) {
|
|
84852
|
+
if (value !== undefined && !/^\d+(\.\d+)?$/.test(value))
|
|
84853
|
+
return `${flag} takes a whole-unit decimal amount of the quote asset, such as 4 or 0.5.`;
|
|
84854
|
+
}
|
|
84855
|
+
const fee = flags["--creator-fee-bps"];
|
|
84856
|
+
if (fee !== undefined && fee !== "0" && fee !== "100" && fee !== "200")
|
|
84857
|
+
return "--creator-fee-bps takes 0, 100 or 200.";
|
|
84858
|
+
if (fee !== undefined && !mode?.startsWith("exp-"))
|
|
84859
|
+
return "--creator-fee-bps takes an exp- --mode.";
|
|
84860
|
+
return;
|
|
84861
|
+
}
|
|
84862
|
+
function launchCurveFields(flags) {
|
|
84863
|
+
return {
|
|
84864
|
+
...flags["--curve-v"] !== undefined && flags["--curve-g"] !== undefined ? { curve: { virtualQuote: flags["--curve-v"], threshold: flags["--curve-g"] } } : {},
|
|
84865
|
+
...flags["--creator-fee-bps"] !== undefined ? { creatorFeeBps: Number(flags["--creator-fee-bps"]) } : {}
|
|
84866
|
+
};
|
|
84867
|
+
}
|
|
84533
84868
|
function buyNotice(buy, chain2) {
|
|
84534
84869
|
if (!buy)
|
|
84535
84870
|
return `Launch creates the token only. Make the first buy with a separate candle swap. Price impact does not apply to creation.
|
|
@@ -84570,9 +84905,13 @@ async function launch(args, ctx) {
|
|
|
84570
84905
|
"--quote-asset",
|
|
84571
84906
|
"--mode",
|
|
84572
84907
|
"--dex-version",
|
|
84573
|
-
"--buy"
|
|
84908
|
+
"--buy",
|
|
84909
|
+
...POOL_FLAGS,
|
|
84910
|
+
"--curve-v",
|
|
84911
|
+
"--curve-g",
|
|
84912
|
+
"--creator-fee-bps"
|
|
84574
84913
|
],
|
|
84575
|
-
booleanFlags: ["--yes"],
|
|
84914
|
+
booleanFlags: ["--yes", "--dry-run"],
|
|
84576
84915
|
pathFlags: ["--image", "--banner"]
|
|
84577
84916
|
});
|
|
84578
84917
|
if ("error" in parsed) {
|
|
@@ -84581,6 +84920,12 @@ async function launch(args, ctx) {
|
|
|
84581
84920
|
}
|
|
84582
84921
|
const flags = parsed.values;
|
|
84583
84922
|
const id = flags["--client-trade-id"] ?? `launch-${randomUUID5()}`;
|
|
84923
|
+
const dryRun = parsed.booleans.has("--dry-run");
|
|
84924
|
+
const curveProblem = curveFlagProblem(flags);
|
|
84925
|
+
if (curveProblem) {
|
|
84926
|
+
writeUsageFailure(ctx.deps, `${curveProblem} ${USAGE6}`, ctx.json);
|
|
84927
|
+
return 2;
|
|
84928
|
+
}
|
|
84584
84929
|
if (flags["--image"] !== undefined && flags["--image-url"] !== undefined) {
|
|
84585
84930
|
writeUsageFailure(ctx.deps, `Pass --image <path> or --image-url <https-url>, not both: a launch has one image. ${USAGE6}`, ctx.json);
|
|
84586
84931
|
return 2;
|
|
@@ -84597,6 +84942,12 @@ async function launch(args, ctx) {
|
|
|
84597
84942
|
writeUsageFailure(ctx.deps, USAGE6, ctx.json);
|
|
84598
84943
|
return 2;
|
|
84599
84944
|
}
|
|
84945
|
+
try {
|
|
84946
|
+
poolRequestFields(flags);
|
|
84947
|
+
} catch (error) {
|
|
84948
|
+
writeUsageFailure(ctx.deps, `${error.message}. ${USAGE6}`, ctx.json);
|
|
84949
|
+
return 2;
|
|
84950
|
+
}
|
|
84600
84951
|
try {
|
|
84601
84952
|
if (flags["--buy"] !== undefined)
|
|
84602
84953
|
rawAmount(flags["--buy"], 18);
|
|
@@ -84616,7 +84967,7 @@ async function launch(args, ctx) {
|
|
|
84616
84967
|
return tradingFailure(ctx, error);
|
|
84617
84968
|
}
|
|
84618
84969
|
const key = await tradingKey(ctx);
|
|
84619
|
-
const prior = await lookupOperation(ctx, key, id, "launch");
|
|
84970
|
+
const prior = dryRun ? null : await lookupOperation(ctx, key, id, "launch");
|
|
84620
84971
|
let retry;
|
|
84621
84972
|
if (prior) {
|
|
84622
84973
|
const local = await savedOperation(ctx, key, id);
|
|
@@ -84648,7 +84999,7 @@ async function launch(args, ctx) {
|
|
|
84648
84999
|
const atomic = chain2 === "solana" && buy !== undefined;
|
|
84649
85000
|
if (atomic)
|
|
84650
85001
|
atomicPreflight(ctx, flags, wallet3, buy);
|
|
84651
|
-
const solana = chain2 === "solana" && !atomic ? await tradingSolanaClient(ctx, flags["--rpc-url"]) : undefined;
|
|
85002
|
+
const solana = chain2 === "solana" && !atomic && !dryRun ? await tradingSolanaClient(ctx, flags["--rpc-url"]) : undefined;
|
|
84652
85003
|
let imageUrl = flags["--image-url"] ?? "";
|
|
84653
85004
|
if (image && retry?.imageUrl)
|
|
84654
85005
|
imageUrl = retry.imageUrl;
|
|
@@ -84681,6 +85032,8 @@ async function launch(args, ctx) {
|
|
|
84681
85032
|
...image ? { imageUrl: safeText(imageUrl) } : {},
|
|
84682
85033
|
...bannerUrl ? { bannerUrl: safeText(bannerUrl) } : {}
|
|
84683
85034
|
};
|
|
85035
|
+
if (dryRun)
|
|
85036
|
+
return await dryRunLaunch(ctx, key, { flags, id, wallet: wallet3, chain: chain2, atomic, buy, imageUrl, bannerUrl, used });
|
|
84684
85037
|
if (atomic)
|
|
84685
85038
|
return await atomicLaunch(ctx, key, {
|
|
84686
85039
|
flags,
|
|
@@ -84708,19 +85061,7 @@ async function launch(args, ctx) {
|
|
|
84708
85061
|
throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this launch id; no write was resent.");
|
|
84709
85062
|
ctx.deps.stderr.write(`Operation: ${id}
|
|
84710
85063
|
`);
|
|
84711
|
-
const built = launchBuildSchema.parse(await request(ctx, key, "/api/v1/launch/self/build", {
|
|
84712
|
-
clientLaunchId: id,
|
|
84713
|
-
chain: "solana",
|
|
84714
|
-
buyAmount: buy ? buy.raw : 0,
|
|
84715
|
-
name: flags["--name"],
|
|
84716
|
-
symbol: flags["--symbol"],
|
|
84717
|
-
imageUrl,
|
|
84718
|
-
...bannerUrl ? { bannerUrl } : {},
|
|
84719
|
-
linkedWalletId: wallet3.id,
|
|
84720
|
-
...flags["--description"] ? { description: flags["--description"] } : {},
|
|
84721
|
-
...flags["--quote-asset"] ? { quoteAsset: flags["--quote-asset"] } : {},
|
|
84722
|
-
...flags["--mode"] ? { mode: flags["--mode"] } : {}
|
|
84723
|
-
}).catch((error) => {
|
|
85064
|
+
const built = launchBuildSchema.parse(await request(ctx, key, "/api/v1/launch/self/build", solanaSelfBody({ flags, id, wallet: wallet3, buy, imageUrl, bannerUrl })).catch((error) => {
|
|
84724
85065
|
throw buyRefusal(error);
|
|
84725
85066
|
}));
|
|
84726
85067
|
if (!built.transaction || !/^\d+$/.test(built.maxDebitLamports ?? ""))
|
|
@@ -84764,9 +85105,109 @@ async function launch(args, ctx) {
|
|
|
84764
85105
|
const result = await request(ctx, key, "/api/v1/launch/self/confirm", { clientLaunchId: id, signature });
|
|
84765
85106
|
return printTradingResult(ctx, { ...result, clientTradeId: id, kind: "launch", quote, ...used });
|
|
84766
85107
|
} catch (error) {
|
|
84767
|
-
return tradingFailure(ctx, error, id);
|
|
85108
|
+
return tradingFailure(ctx, error, dryRun ? undefined : id);
|
|
84768
85109
|
}
|
|
84769
85110
|
}
|
|
85111
|
+
function launchFields(args) {
|
|
85112
|
+
const { flags } = args;
|
|
85113
|
+
return {
|
|
85114
|
+
clientLaunchId: args.id,
|
|
85115
|
+
name: flags["--name"],
|
|
85116
|
+
symbol: flags["--symbol"],
|
|
85117
|
+
imageUrl: args.imageUrl,
|
|
85118
|
+
...args.bannerUrl ? { bannerUrl: args.bannerUrl } : {},
|
|
85119
|
+
...flags["--description"] ? { description: flags["--description"] } : {},
|
|
85120
|
+
...flags["--mode"] ? { mode: flags["--mode"] } : {},
|
|
85121
|
+
...launchCurveFields(flags),
|
|
85122
|
+
...poolRequestFields(flags)
|
|
85123
|
+
};
|
|
85124
|
+
}
|
|
85125
|
+
function solanaSelfBody(args) {
|
|
85126
|
+
const quoteAsset = args.flags["--quote-asset"];
|
|
85127
|
+
return {
|
|
85128
|
+
...launchFields(args),
|
|
85129
|
+
chain: "solana",
|
|
85130
|
+
buyAmount: args.buy ? args.buy.raw : 0,
|
|
85131
|
+
linkedWalletId: args.wallet.id,
|
|
85132
|
+
...quoteAsset ? { quoteAsset } : {}
|
|
85133
|
+
};
|
|
85134
|
+
}
|
|
85135
|
+
function atomicBody(args) {
|
|
85136
|
+
const payer = { type: "linked", linkedWalletId: args.wallet.id };
|
|
85137
|
+
const quoteAsset = args.flags["--quote-asset"];
|
|
85138
|
+
return {
|
|
85139
|
+
...launchFields(args),
|
|
85140
|
+
chain: "solana",
|
|
85141
|
+
...quoteAsset ? { quoteAsset } : {},
|
|
85142
|
+
payer,
|
|
85143
|
+
firstBuys: [{ payer, amountRaw: args.buy.raw }]
|
|
85144
|
+
};
|
|
85145
|
+
}
|
|
85146
|
+
function hoodBody(args) {
|
|
85147
|
+
const quoteAsset = args.flags["--quote-asset"];
|
|
85148
|
+
return {
|
|
85149
|
+
...launchFields(args),
|
|
85150
|
+
chain: "hood",
|
|
85151
|
+
buyAmount: args.buy ? args.buy.raw : 0,
|
|
85152
|
+
linkedWalletId: args.wallet.id,
|
|
85153
|
+
dexVersion: args.flags["--dex-version"],
|
|
85154
|
+
...quoteAsset ? { quoteAsset: quoteAsset.toLowerCase() } : {}
|
|
85155
|
+
};
|
|
85156
|
+
}
|
|
85157
|
+
var previewSchema = exports_external.object({
|
|
85158
|
+
resolved: exports_external.object({ mode: exports_external.string(), quoteAsset: exports_external.string() }).passthrough().optional(),
|
|
85159
|
+
curveTerms: exports_external.object({
|
|
85160
|
+
symbol: exports_external.string(),
|
|
85161
|
+
raise: exports_external.number(),
|
|
85162
|
+
startFdv: exports_external.number(),
|
|
85163
|
+
bondingFdv: exports_external.number(),
|
|
85164
|
+
supplySoldPct: exports_external.number(),
|
|
85165
|
+
firstBuy: exports_external.object({ basis: exports_external.string(), amountRaw: exports_external.string(), supplyPct: exports_external.number() }).passthrough().optional(),
|
|
85166
|
+
fees: exports_external.object({ curve: exports_external.object({ feeBps: exports_external.number() }), pool: exports_external.object({ feeBps: exports_external.number() }) }).passthrough().optional(),
|
|
85167
|
+
migration: exports_external.object({ venue: exports_external.string(), poolShape: exports_external.string(), openingPriceStepPct: exports_external.number() }).passthrough().optional()
|
|
85168
|
+
}).passthrough().optional(),
|
|
85169
|
+
devBuyMax: exports_external.string().nullable().optional(),
|
|
85170
|
+
fills: exports_external.array(exports_external.object({ index: exports_external.number(), amountRaw: exports_external.string(), supplyPct: exports_external.number() }).passthrough()).optional()
|
|
85171
|
+
}).passthrough();
|
|
85172
|
+
var percent = (value) => `${Number(value.toFixed(4))}%`;
|
|
85173
|
+
function previewLines(answer2, chain2) {
|
|
85174
|
+
const parsed = previewSchema.safeParse(answer2);
|
|
85175
|
+
if (!parsed.success)
|
|
85176
|
+
return "";
|
|
85177
|
+
const { resolved, curveTerms: terms, devBuyMax, fills } = parsed.data;
|
|
85178
|
+
const quote = (resolved?.quoteAsset ?? (chain2 === "hood" ? "eth" : "sol")).toUpperCase();
|
|
85179
|
+
const decimals = (chain2 === "hood" ? HOOD_BASES[quote] : BASES[quote])?.decimals;
|
|
85180
|
+
const whole = (raw) => decimals === undefined ? `${raw} raw` : `${shortAmount(raw, decimals)} ${quote}`;
|
|
85181
|
+
const lines = [];
|
|
85182
|
+
if (terms) {
|
|
85183
|
+
lines.push(`Curve${resolved ? ` (${safeText(resolved.mode)})` : ""}: raises ${terms.raise} ${safeText(terms.symbol)}; market cap ${terms.startFdv} at start and ${terms.bondingFdv} at graduation; ${percent(terms.supplySoldPct)} of the supply sold by graduation.`);
|
|
85184
|
+
if (terms.firstBuy)
|
|
85185
|
+
lines.push(`First buy (${terms.firstBuy.basis === "buyAmount" ? "your --buy" : "a reference buy"}): ${whole(terms.firstBuy.amountRaw)} takes ${percent(terms.firstBuy.supplyPct)} of the supply after the curve fee.`);
|
|
85186
|
+
if (terms.fees)
|
|
85187
|
+
lines.push(`Fees: ${terms.fees.curve.feeBps / 100}% on the curve, ${terms.fees.pool.feeBps / 100}% in the pool after graduation.`);
|
|
85188
|
+
if (terms.migration)
|
|
85189
|
+
lines.push(`Graduation: a ${safeText(terms.migration.poolShape)} pool on ${safeText(terms.migration.venue)}, opening ${percent(terms.migration.openingPriceStepPct)} against the curve's last price, its liquidity locked for good.`);
|
|
85190
|
+
}
|
|
85191
|
+
for (const fill of fills ?? [])
|
|
85192
|
+
lines.push(`First buy ${fill.index}: ${whole(fill.amountRaw)} takes ${percent(fill.supplyPct)} of the supply.`);
|
|
85193
|
+
if (devBuyMax)
|
|
85194
|
+
lines.push(`Dev-buy ceiling: ${whole(devBuyMax)}.`);
|
|
85195
|
+
return lines.length ? `${lines.join(`
|
|
85196
|
+
`)}
|
|
85197
|
+
` : "";
|
|
85198
|
+
}
|
|
85199
|
+
async function dryRunLaunch(ctx, key, args) {
|
|
85200
|
+
const { chain: chain2, buy } = args;
|
|
85201
|
+
const atomic = args.atomic && buy !== undefined;
|
|
85202
|
+
const path = atomic ? "/api/v1/launch/atomic/dry-run" : "/api/v1/launch/self/dry-run";
|
|
85203
|
+
const body = atomic ? atomicBody({ ...args, buy }) : chain2 === "hood" ? hoodBody(args) : solanaSelfBody(args);
|
|
85204
|
+
const answer2 = await request(ctx, key, path, body).catch((error) => {
|
|
85205
|
+
throw atomic ? atomicRefusal(error) : buyRefusal(error);
|
|
85206
|
+
});
|
|
85207
|
+
ctx.deps.stderr.write(`Dry run: nothing was built, signed or recorded, and launch id ${args.id} is unused.
|
|
85208
|
+
${previewLines(answer2, chain2)}`);
|
|
85209
|
+
return printTradingResult(ctx, { ...answer2, clientTradeId: args.id, kind: "launch", ...args.used });
|
|
85210
|
+
}
|
|
84770
85211
|
var DEV_BUY_CEILINGS = {
|
|
84771
85212
|
sol: { env: "DEV_BUY_MAX_LAMPORTS", fallback: 500000000n, unit: "lamports" },
|
|
84772
85213
|
usdc: { env: "DEV_BUY_MAX_USDC_RAW", fallback: 500000000n, unit: "USDC raw units" },
|
|
@@ -84780,9 +85221,34 @@ function devBuyCeiling(env, quoteAsset) {
|
|
|
84780
85221
|
const value = raw && /^\d+$/.test(raw) ? BigInt(raw) : 0n;
|
|
84781
85222
|
return { ceiling: value > 0n ? value : bounds.fallback, unit: bounds.unit };
|
|
84782
85223
|
}
|
|
85224
|
+
function shortAmount(raw, decimals) {
|
|
85225
|
+
const full = decimalAmount(raw, decimals);
|
|
85226
|
+
return full.includes(".") ? full.replace(/0+$/, "").replace(/\.$/, "") : full;
|
|
85227
|
+
}
|
|
85228
|
+
var EXP_DEV_BUY_MAX_THRESHOLD_BPS = 1000n;
|
|
85229
|
+
var EXP_PRESET_THRESHOLD_RAW = { sol: 5000000000n, cndl: 330000000000n };
|
|
85230
|
+
function expThresholdRaw(flags, quoteAsset) {
|
|
85231
|
+
const mode = flags["--mode"];
|
|
85232
|
+
if (mode === "exp-custom") {
|
|
85233
|
+
const g = flags["--curve-g"];
|
|
85234
|
+
const decimals = BASES[quoteAsset.toUpperCase()]?.decimals;
|
|
85235
|
+
return g === undefined || decimals === undefined ? undefined : BigInt(rawAmount(g, decimals));
|
|
85236
|
+
}
|
|
85237
|
+
return mode === "exp-a" || mode === "exp-b" || mode === "exp-c" ? EXP_PRESET_THRESHOLD_RAW[quoteAsset] : undefined;
|
|
85238
|
+
}
|
|
84783
85239
|
function atomicPreflight(ctx, flags, wallet3, buy) {
|
|
84784
85240
|
const quoteAsset = (flags["--quote-asset"] ?? "sol").toLowerCase();
|
|
84785
85241
|
const { ceiling, unit } = devBuyCeiling(ctx.deps.env, quoteAsset);
|
|
85242
|
+
const threshold = expThresholdRaw(flags, quoteAsset);
|
|
85243
|
+
if (threshold !== undefined) {
|
|
85244
|
+
const curveCap = threshold * EXP_DEV_BUY_MAX_THRESHOLD_BPS / 10000n;
|
|
85245
|
+
const applies = curveCap < ceiling ? curveCap : ceiling;
|
|
85246
|
+
if (BigInt(buy.raw) > applies) {
|
|
85247
|
+
const symbol = quoteAsset.toUpperCase();
|
|
85248
|
+
const decimals = BASES[symbol]?.decimals ?? 9;
|
|
85249
|
+
throw new TradingError("DEV_BUY_TOO_HIGH", `buyAmount exceeds this launch's dev-buy ceiling of ${shortAmount(applies.toString(), decimals)} ${symbol} (${applies} ${unit}): at most ${shortAmount(ceiling.toString(), decimals)} ${symbol}, or 10% of the threshold when that is lower`, { details: { field: "buyAmount" } });
|
|
85250
|
+
}
|
|
85251
|
+
}
|
|
84786
85252
|
if (BigInt(buy.raw) > ceiling)
|
|
84787
85253
|
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" } });
|
|
84788
85254
|
if (wallet3.scopes !== undefined && !wallet3.scopes.includes("swap:write"))
|
|
@@ -84827,20 +85293,7 @@ async function atomicLaunch(ctx, key, args) {
|
|
|
84827
85293
|
throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this launch id; no write was resent.");
|
|
84828
85294
|
ctx.deps.stderr.write(`Operation: ${id}
|
|
84829
85295
|
`);
|
|
84830
|
-
const
|
|
84831
|
-
const built = atomicBuildSchema.parse(await request(ctx, key, "/api/v1/launch/atomic/build", {
|
|
84832
|
-
clientLaunchId: id,
|
|
84833
|
-
chain: "solana",
|
|
84834
|
-
name: flags["--name"],
|
|
84835
|
-
symbol: flags["--symbol"],
|
|
84836
|
-
imageUrl: args.imageUrl,
|
|
84837
|
-
...args.bannerUrl ? { bannerUrl: args.bannerUrl } : {},
|
|
84838
|
-
...flags["--description"] ? { description: flags["--description"] } : {},
|
|
84839
|
-
...flags["--quote-asset"] ? { quoteAsset: flags["--quote-asset"] } : {},
|
|
84840
|
-
...flags["--mode"] ? { mode: flags["--mode"] } : {},
|
|
84841
|
-
payer,
|
|
84842
|
-
firstBuys: [{ payer, amountRaw: buy.raw }]
|
|
84843
|
-
}).catch((error) => {
|
|
85296
|
+
const built = atomicBuildSchema.parse(await request(ctx, key, "/api/v1/launch/atomic/build", atomicBody(args)).catch((error) => {
|
|
84844
85297
|
throw atomicRefusal(error);
|
|
84845
85298
|
}));
|
|
84846
85299
|
const [launchLeg, buyLeg] = built.legs;
|
|
@@ -84957,25 +85410,14 @@ function resumeAtomicLaunch(ctx, id, local, prior) {
|
|
|
84957
85410
|
}
|
|
84958
85411
|
async function hoodLaunch(ctx, key, args) {
|
|
84959
85412
|
const { flags, id, wallet: wallet3, buy } = args;
|
|
85413
|
+
if (Object.keys(poolRequestFields(flags)).length)
|
|
85414
|
+
throw new TradingUsage("Pool fee and pool shape choices are Solana only; a Hood launch cannot take them.");
|
|
84960
85415
|
const allowedLegs = buy ? HOOD_LAUNCH_BUY_LEGS : HOOD_LAUNCH_LEGS;
|
|
84961
85416
|
if (!await claimOperation(ctx, key, id, "launch"))
|
|
84962
85417
|
throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this launch id; no write was resent.");
|
|
84963
85418
|
ctx.deps.stderr.write(`Operation: ${id}
|
|
84964
85419
|
`);
|
|
84965
|
-
const built = await request(ctx, key, "/api/v1/launch/self/build", {
|
|
84966
|
-
clientLaunchId: id,
|
|
84967
|
-
chain: "hood",
|
|
84968
|
-
buyAmount: buy ? buy.raw : 0,
|
|
84969
|
-
name: flags["--name"],
|
|
84970
|
-
symbol: flags["--symbol"],
|
|
84971
|
-
imageUrl: args.imageUrl,
|
|
84972
|
-
...args.bannerUrl ? { bannerUrl: args.bannerUrl } : {},
|
|
84973
|
-
linkedWalletId: wallet3.id,
|
|
84974
|
-
dexVersion: flags["--dex-version"],
|
|
84975
|
-
...flags["--description"] ? { description: flags["--description"] } : {},
|
|
84976
|
-
...flags["--quote-asset"] ? { quoteAsset: flags["--quote-asset"].toLowerCase() } : {},
|
|
84977
|
-
...flags["--mode"] ? { mode: flags["--mode"] } : {}
|
|
84978
|
-
}).catch((error) => {
|
|
85420
|
+
const built = await request(ctx, key, "/api/v1/launch/self/build", hoodBody(args)).catch((error) => {
|
|
84979
85421
|
throw buyRefusal(error);
|
|
84980
85422
|
});
|
|
84981
85423
|
const parsed = sequencedSchema.safeParse(built);
|
|
@@ -85454,8 +85896,8 @@ async function lpRemove(args, ctx) {
|
|
|
85454
85896
|
return usage4(ctx, parsed.error);
|
|
85455
85897
|
const flags = parsed.values;
|
|
85456
85898
|
const id = flags["--client-trade-id"] ?? `lp-${randomUUID6()}`;
|
|
85457
|
-
const
|
|
85458
|
-
if (parsed.positionals.length !== 1 || !flags["--percent"] || !/^\d+(\.\d{1,2})?$/.test(flags["--percent"]) || !(
|
|
85899
|
+
const percent2 = Number(flags["--percent"]);
|
|
85900
|
+
if (parsed.positionals.length !== 1 || !flags["--percent"] || !/^\d+(\.\d{1,2})?$/.test(flags["--percent"]) || !(percent2 > 0 && percent2 <= 100) || !validClientId(id))
|
|
85459
85901
|
return usage4(ctx, "Usage: candle lp remove <position> --percent <0.01-100> [--wallet <tee>] [--slippage-bps 100] [--client-trade-id <id>] [--rpc-url <url>] [--yes]");
|
|
85460
85902
|
try {
|
|
85461
85903
|
const position = solanaAddress(parsed.positionals[0], "The position");
|
|
@@ -85470,8 +85912,8 @@ async function lpRemove(args, ctx) {
|
|
|
85470
85912
|
wallet: wallet3,
|
|
85471
85913
|
solana,
|
|
85472
85914
|
yes: parsed.booleans.has("--yes"),
|
|
85473
|
-
intent:
|
|
85474
|
-
body: { position, percent, slippageBps }
|
|
85915
|
+
intent: percent2 === 100 ? `Remove all liquidity from position ${position}, claim its fees and close it (rent returns to the wallet)` : `Remove ${percent2}% of the liquidity in position ${position}`,
|
|
85916
|
+
body: { position, percent: percent2, slippageBps }
|
|
85475
85917
|
});
|
|
85476
85918
|
} catch (error) {
|
|
85477
85919
|
return tradingFailure(ctx, error, id);
|
|
@@ -86836,18 +87278,55 @@ function perpsCandleWindow2(opts, now) {
|
|
|
86836
87278
|
return usage5(`interval must be one of ${PERPS_CANDLE_INTERVALS2.join(" ")}.`);
|
|
86837
87279
|
const count2 = bounded2(opts.count, 100, 1, 5000, "count");
|
|
86838
87280
|
const raw = opts.end;
|
|
86839
|
-
const endTime = raw === undefined ? now :
|
|
87281
|
+
const endTime = raw === undefined ? now : epoch2(raw);
|
|
86840
87282
|
const startTime = endTime - count2 * at2(INTERVAL_MS2, index);
|
|
86841
87283
|
if (!Number.isSafeInteger(endTime) || endTime < 0 || endTime > now || startTime < 0)
|
|
86842
87284
|
return usage5("end must be an ISO 8601 time or epoch milliseconds, no later than now.");
|
|
86843
87285
|
return { interval, count: count2, startTime, endTime };
|
|
86844
87286
|
}
|
|
87287
|
+
function epoch2(raw) {
|
|
87288
|
+
return typeof raw === "number" || /^\d+$/.test(raw) ? Number(raw) : /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})$/.test(raw) ? Date.parse(raw) : NaN;
|
|
87289
|
+
}
|
|
87290
|
+
var PERPS_FUNDING_HISTORY_MAX_MS2 = 30 * 86400000;
|
|
87291
|
+
var PERPS_FUNDING_HISTORY_MAX_ROWS2 = 500;
|
|
87292
|
+
function perpsFundingWindow2(opts, now) {
|
|
87293
|
+
const endTime = opts.end === undefined ? now : epoch2(opts.end);
|
|
87294
|
+
if (!Number.isSafeInteger(endTime) || endTime < 0 || endTime > now)
|
|
87295
|
+
return usage5("end must be an ISO 8601 time or epoch milliseconds, no later than now.");
|
|
87296
|
+
const startTime = opts.start === undefined ? endTime - 86400000 : epoch2(opts.start);
|
|
87297
|
+
if (!Number.isSafeInteger(startTime) || startTime < 0 || startTime > endTime)
|
|
87298
|
+
return usage5("start must be an ISO 8601 time or epoch milliseconds, no later than end.");
|
|
87299
|
+
if (endTime - startTime > PERPS_FUNDING_HISTORY_MAX_MS2)
|
|
87300
|
+
return usage5("start must be at most 30 days before end.");
|
|
87301
|
+
return { startTime, endTime };
|
|
87302
|
+
}
|
|
86845
87303
|
function bounded2(value, fallback, min, max, name2) {
|
|
86846
87304
|
const n = value ?? fallback;
|
|
86847
87305
|
if (!Number.isInteger(n) || n < min || n > max)
|
|
86848
87306
|
return usage5(`${name2} must be an integer from ${min} to ${max}.`);
|
|
86849
87307
|
return n;
|
|
86850
87308
|
}
|
|
87309
|
+
function parsePerpsFundingHistory2(value, coin, window) {
|
|
87310
|
+
if (!Array.isArray(value))
|
|
87311
|
+
return unavailable4();
|
|
87312
|
+
const funding = [];
|
|
87313
|
+
for (const row of value) {
|
|
87314
|
+
if (!object3(row) || row.coin !== coin || !integer2(row.time) || !decimal5(row.fundingRate) || !decimal5(row.premium))
|
|
87315
|
+
return unavailable4();
|
|
87316
|
+
if (row.time >= window.startTime && row.time <= window.endTime)
|
|
87317
|
+
funding.push({
|
|
87318
|
+
time: row.time,
|
|
87319
|
+
fundingRate: row.fundingRate,
|
|
87320
|
+
fundingAprPct: fixed2(scaled2(row.fundingRate) * 876000n, 2),
|
|
87321
|
+
premium: row.premium
|
|
87322
|
+
});
|
|
87323
|
+
}
|
|
87324
|
+
funding.sort((a, b) => a.time - b.time);
|
|
87325
|
+
return {
|
|
87326
|
+
funding: funding.slice(0, PERPS_FUNDING_HISTORY_MAX_ROWS2),
|
|
87327
|
+
truncated: funding.length > PERPS_FUNDING_HISTORY_MAX_ROWS2
|
|
87328
|
+
};
|
|
87329
|
+
}
|
|
86851
87330
|
|
|
86852
87331
|
class PerpsMarketReader2 {
|
|
86853
87332
|
opts;
|
|
@@ -87125,6 +87604,44 @@ class PerpsMarketReader2 {
|
|
|
87125
87604
|
candles: candles.slice(-window.count)
|
|
87126
87605
|
};
|
|
87127
87606
|
}
|
|
87607
|
+
async fundingHistory(coin, opts = {}) {
|
|
87608
|
+
this.key();
|
|
87609
|
+
const window = perpsFundingWindow2(opts, this.now());
|
|
87610
|
+
const [config, core] = await Promise.all([this.config(), this.core()]);
|
|
87611
|
+
const canonical4 = at2(core.universe, resolve3(core, coin)).name;
|
|
87612
|
+
const value = await info2(this.opts.fetcher, this.opts.network, {
|
|
87613
|
+
type: "fundingHistory",
|
|
87614
|
+
coin: canonical4,
|
|
87615
|
+
startTime: window.startTime,
|
|
87616
|
+
endTime: window.endTime
|
|
87617
|
+
});
|
|
87618
|
+
return {
|
|
87619
|
+
...this.envelope(config),
|
|
87620
|
+
coin: canonical4,
|
|
87621
|
+
startTime: window.startTime,
|
|
87622
|
+
endTime: window.endTime,
|
|
87623
|
+
...parsePerpsFundingHistory2(value, canonical4, window)
|
|
87624
|
+
};
|
|
87625
|
+
}
|
|
87626
|
+
async predictedFunding() {
|
|
87627
|
+
this.key();
|
|
87628
|
+
const [config, core, extras] = await Promise.all([this.config(), this.core(), this.extras()]);
|
|
87629
|
+
if (extras.predicted === null)
|
|
87630
|
+
return unavailable4();
|
|
87631
|
+
const predicted = [];
|
|
87632
|
+
for (const meta of core.universe) {
|
|
87633
|
+
const row = extras.predicted.get(meta.name);
|
|
87634
|
+
if (row)
|
|
87635
|
+
predicted.push({
|
|
87636
|
+
coin: meta.name,
|
|
87637
|
+
fundingRate: row.fundingRate,
|
|
87638
|
+
fundingAprPct: fixed2(scaled2(row.fundingRate) * 876000n, 2),
|
|
87639
|
+
nextFundingTime: row.nextFundingTime
|
|
87640
|
+
});
|
|
87641
|
+
}
|
|
87642
|
+
predicted.sort((a, b) => a.coin < b.coin ? -1 : a.coin > b.coin ? 1 : 0);
|
|
87643
|
+
return { ...this.envelope(config, extras.warnings, this.extrasCache?.at ?? this.now()), predicted };
|
|
87644
|
+
}
|
|
87128
87645
|
}
|
|
87129
87646
|
|
|
87130
87647
|
// src/perps-history.ts
|
|
@@ -88785,7 +89302,7 @@ function typedDataAuthorization(wallet3, typedData) {
|
|
|
88785
89302
|
});
|
|
88786
89303
|
return { body, authorizationSignature: sign4("sha256", Buffer.from(payload), wallet3.signer).toString("base64") };
|
|
88787
89304
|
}
|
|
88788
|
-
function
|
|
89305
|
+
function previewLines2(build) {
|
|
88789
89306
|
const action = build.action;
|
|
88790
89307
|
const preview = build.preview ?? {};
|
|
88791
89308
|
const notes = [
|
|
@@ -88829,7 +89346,7 @@ async function confirm(ctx, build, address, yes) {
|
|
|
88829
89346
|
if (ctx.deps.env.CANDLE_HYPERLIQUID_BUILDER !== undefined)
|
|
88830
89347
|
output.write(`Builder trust override: ${builderPin2(ctx)} (replaces this release's pin).
|
|
88831
89348
|
`);
|
|
88832
|
-
for (const line of
|
|
89349
|
+
for (const line of previewLines2(build))
|
|
88833
89350
|
output.write(`${line}
|
|
88834
89351
|
`);
|
|
88835
89352
|
if (yes)
|
|
@@ -90482,7 +90999,6 @@ var CANDLE_PERPS_WITHDRAW_PINS = {
|
|
|
90482
90999
|
depositories: ["0x66cf0aace1b4e562593bec10ec7868fba9932224"],
|
|
90483
91000
|
amountForm: "fixed6"
|
|
90484
91001
|
};
|
|
90485
|
-
var RELAY_API_URL = "https://api.relay.link";
|
|
90486
91002
|
var ZERO = "0x0000000000000000000000000000000000000000";
|
|
90487
91003
|
var EVM_ADDRESS_RE5 = /^0x[0-9a-fA-F]{40}$/;
|
|
90488
91004
|
var BYTES32_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
@@ -90633,24 +91149,6 @@ function verifyWithdrawSendAsset(authorization, expect) {
|
|
|
90633
91149
|
}
|
|
90634
91150
|
return { ok: true };
|
|
90635
91151
|
}
|
|
90636
|
-
async function relayRecipientProblem(fetchImpl, requestId, recipient, relayUrl = RELAY_API_URL) {
|
|
90637
|
-
let body;
|
|
90638
|
-
try {
|
|
90639
|
-
const res = await fetchImpl(`${relayUrl}/requests/v3?id=${encodeURIComponent(requestId)}`, { method: "GET" });
|
|
90640
|
-
if (!res.ok)
|
|
90641
|
-
return `Relay's record of the request could not be read (HTTP ${res.status})`;
|
|
90642
|
-
body = await res.json();
|
|
90643
|
-
} catch (error) {
|
|
90644
|
-
return `Relay's record of the request could not be read (${error instanceof Error ? error.message : "unknown"})`;
|
|
90645
|
-
}
|
|
90646
|
-
const requests = isObject(body) && Array.isArray(body.requests) ? body.requests : [];
|
|
90647
|
-
const match = requests.find((r) => isObject(r) && r.id === requestId);
|
|
90648
|
-
if (!isObject(match))
|
|
90649
|
-
return "Relay does not list this request";
|
|
90650
|
-
const named = match.recipient;
|
|
90651
|
-
const same = recipient.startsWith("0x") ? sameEvm(named, recipient) : named === recipient;
|
|
90652
|
-
return same ? null : `Relay's request pays ${String(named)}, not this key's wallet ${recipient}`;
|
|
90653
|
-
}
|
|
90654
91152
|
|
|
90655
91153
|
// src/commands/perps-withdraw.ts
|
|
90656
91154
|
init_render();
|
|
@@ -90771,9 +91269,6 @@ async function perpsWithdraw(args, ctx) {
|
|
|
90771
91269
|
});
|
|
90772
91270
|
if (!mapping.ok)
|
|
90773
91271
|
throw refused2(mapping.reason);
|
|
90774
|
-
const recipient = await relayRecipientProblem(ctx.deps.fetch, build.relayRequestId, build.to.address);
|
|
90775
|
-
if (recipient)
|
|
90776
|
-
throw refused2(recipient);
|
|
90777
91272
|
const output = ctx.json ? ctx.deps.stderr : ctx.deps.stdout;
|
|
90778
91273
|
output.write(`Withdraw ${formatWithdrawAmount(amountMicros)} USDC from Hyperliquid account ${safeText(wallet3.address)} through Relay.
|
|
90779
91274
|
`);
|
|
@@ -92905,7 +93400,7 @@ var SKILLS_CLAUDE_COMMAND = "/plugin marketplace add candledottv/agentic";
|
|
|
92905
93400
|
var CODING_AGENTS_DOCS = "https://docs.candle.tv/developers/coding-agents";
|
|
92906
93401
|
function section(deps, title) {
|
|
92907
93402
|
deps.stdout.write(`
|
|
92908
|
-
|
|
93403
|
+
${accent(`== ${title} ==`, terminalStyle(deps, "stdout"))}
|
|
92909
93404
|
`);
|
|
92910
93405
|
}
|
|
92911
93406
|
async function setup(args, ctx) {
|
|
@@ -92923,6 +93418,10 @@ async function setup(args, ctx) {
|
|
|
92923
93418
|
writeUsageFailure(deps, "setup is an interactive wizard; for machine use, compose `auth login --json` and `doctor --json` directly", json);
|
|
92924
93419
|
return 2;
|
|
92925
93420
|
}
|
|
93421
|
+
const style = terminalStyle(deps, "stdout");
|
|
93422
|
+
if (deps.isTTY.stdin)
|
|
93423
|
+
deps.stdout.write(brandHeader(style).join(`
|
|
93424
|
+
`));
|
|
92926
93425
|
await printIdentity(ctx);
|
|
92927
93426
|
deps.stdout.write(`candle setup: this wizard authorizes the device, shows funding, and verifies everything.
|
|
92928
93427
|
`);
|
|
@@ -94579,6 +95078,14 @@ async function tui(args, ctx) {
|
|
|
94579
95078
|
Usage: candle tui [--profile <name>]`, json);
|
|
94580
95079
|
return 2;
|
|
94581
95080
|
}
|
|
95081
|
+
if (json || !deps.isTTY.stdin || !deps.isTTY.stdout || deps.env.TERM === "dumb" || deps.presentation?.plain || deps.env.CANDLE_PLAIN === "1") {
|
|
95082
|
+
writeLocalFailure(deps, {
|
|
95083
|
+
code: "TUI_REQUIRES_TERMINAL",
|
|
95084
|
+
message: "The Candle TUI needs an interactive terminal.",
|
|
95085
|
+
suggestion: "Use candle portfolio or candle help for command-line output."
|
|
95086
|
+
}, json);
|
|
95087
|
+
return 1;
|
|
95088
|
+
}
|
|
94582
95089
|
const path = findPlugin("tui", deps.env.PATH);
|
|
94583
95090
|
if (path === undefined) {
|
|
94584
95091
|
writeLocalFailure(deps, {
|
|
@@ -94594,6 +95101,8 @@ Usage: candle tui [--profile <name>]`, json);
|
|
|
94594
95101
|
if (value !== undefined)
|
|
94595
95102
|
env[name2] = value;
|
|
94596
95103
|
}
|
|
95104
|
+
if (deps.presentation?.noColor)
|
|
95105
|
+
env.NO_COLOR = "1";
|
|
94597
95106
|
const candle = deps.execPath.endsWith("/node") || deps.execPath.endsWith("/bun") ? "candle" : deps.execPath;
|
|
94598
95107
|
return deps.runPlugin(path, [...ctx.profile ? ["--profile", ctx.profile] : [], "--candle", candle], env);
|
|
94599
95108
|
}
|
|
@@ -94605,11 +95114,13 @@ import { randomBytes as randomBytes11 } from "node:crypto";
|
|
|
94605
95114
|
// src/progress.ts
|
|
94606
95115
|
var FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
94607
95116
|
var INTERVAL_MS3 = 80;
|
|
94608
|
-
function stepReporter(write, tty) {
|
|
95117
|
+
function stepReporter(write, tty, ascii = false) {
|
|
95118
|
+
let detach = () => {};
|
|
94609
95119
|
let timer = null;
|
|
94610
95120
|
let frame = 0;
|
|
94611
95121
|
let current = null;
|
|
94612
95122
|
const clearSpinner = () => {
|
|
95123
|
+
detach();
|
|
94613
95124
|
if (timer !== null)
|
|
94614
95125
|
clearInterval(timer);
|
|
94615
95126
|
timer = null;
|
|
@@ -94628,6 +95139,19 @@ function stepReporter(write, tty) {
|
|
|
94628
95139
|
}
|
|
94629
95140
|
write(`${FRAMES[0]} ${label}`);
|
|
94630
95141
|
frame = 1;
|
|
95142
|
+
const interrupt = (signal) => {
|
|
95143
|
+
clearSpinner();
|
|
95144
|
+
if (process.listenerCount(signal) === 0)
|
|
95145
|
+
process.kill(process.pid, signal);
|
|
95146
|
+
};
|
|
95147
|
+
const sigint = () => interrupt("SIGINT");
|
|
95148
|
+
const sigterm = () => interrupt("SIGTERM");
|
|
95149
|
+
process.once("SIGINT", sigint);
|
|
95150
|
+
process.once("SIGTERM", sigterm);
|
|
95151
|
+
detach = () => {
|
|
95152
|
+
process.off("SIGINT", sigint);
|
|
95153
|
+
process.off("SIGTERM", sigterm);
|
|
95154
|
+
};
|
|
94631
95155
|
timer = setInterval(() => {
|
|
94632
95156
|
write(`\r\x1B[2K${FRAMES[frame % FRAMES.length]} ${current}`);
|
|
94633
95157
|
frame++;
|
|
@@ -94637,17 +95161,22 @@ function stepReporter(write, tty) {
|
|
|
94637
95161
|
},
|
|
94638
95162
|
done(label) {
|
|
94639
95163
|
clearSpinner();
|
|
94640
|
-
write(
|
|
95164
|
+
write(`${ascii ? "OK" : "✓"} ${label}
|
|
94641
95165
|
`);
|
|
94642
95166
|
},
|
|
94643
95167
|
fail(label) {
|
|
94644
95168
|
clearSpinner();
|
|
94645
|
-
write(
|
|
95169
|
+
write(`${ascii ? "FAIL" : "✗"} ${label}
|
|
94646
95170
|
`);
|
|
94647
95171
|
},
|
|
94648
95172
|
stop: clearSpinner
|
|
94649
95173
|
};
|
|
94650
95174
|
}
|
|
95175
|
+
function commandSteps(ctx) {
|
|
95176
|
+
const style = terminalStyle(ctx.deps, "stderr", ctx.json);
|
|
95177
|
+
const reporter = stepReporter(ctx.json ? () => {} : (text3) => ctx.deps.stderr.write(text3), style.motion, !style.styled);
|
|
95178
|
+
return { ...reporter, [Symbol.dispose ?? Symbol.for("Symbol.dispose")]: () => reporter.stop() };
|
|
95179
|
+
}
|
|
94651
95180
|
function formatBytes(n) {
|
|
94652
95181
|
if (n >= 1024 * 1024)
|
|
94653
95182
|
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
@@ -94954,226 +95483,233 @@ init_fido2();
|
|
|
94954
95483
|
var SIGNATURE_VERIFIED = "signature verified (Sigstore, keyless; signer pinned to the release workflow)";
|
|
94955
95484
|
var INSTALLER_LINE = "curl -fsSL https://candle.tv/install.sh | bash";
|
|
94956
95485
|
async function update(args, ctx) {
|
|
94957
|
-
|
|
94958
|
-
|
|
94959
|
-
|
|
94960
|
-
|
|
94961
|
-
|
|
94962
|
-
|
|
94963
|
-
|
|
94964
|
-
|
|
94965
|
-
|
|
94966
|
-
|
|
94967
|
-
|
|
94968
|
-
|
|
94969
|
-
|
|
94970
|
-
|
|
94971
|
-
|
|
94972
|
-
|
|
94973
|
-
|
|
94974
|
-
|
|
95486
|
+
let __stack = [];
|
|
95487
|
+
try {
|
|
95488
|
+
const { deps, json } = ctx;
|
|
95489
|
+
const parsed = parseArgs(args, { valueFlags: ["--to"], booleanFlags: ["--check"] });
|
|
95490
|
+
if ("error" in parsed) {
|
|
95491
|
+
writeUsageFailure(deps, parsed.error, json);
|
|
95492
|
+
return 2;
|
|
95493
|
+
}
|
|
95494
|
+
if (parsed.positionals.length > 0) {
|
|
95495
|
+
writeUsageFailure(deps, `Unexpected argument: ${parsed.positionals[0]}`, json);
|
|
95496
|
+
return 2;
|
|
95497
|
+
}
|
|
95498
|
+
const check = parsed.booleans.has("--check");
|
|
95499
|
+
const pinned = parsed.values["--to"];
|
|
95500
|
+
const realExec = await deps.realpath(deps.execPath).catch(() => deps.execPath);
|
|
95501
|
+
const method = detectInstall(deps.execPath, realExec);
|
|
95502
|
+
if (method === "homebrew") {
|
|
95503
|
+
if (json) {
|
|
95504
|
+
const payload = { current: CLI_VERSION, latest: null, updated: false, path: realExec, method };
|
|
95505
|
+
deps.stdout.write(`${JSON.stringify(payload)}
|
|
94975
95506
|
`);
|
|
94976
|
-
|
|
94977
|
-
|
|
95507
|
+
} else {
|
|
95508
|
+
deps.stdout.write(`Installed by Homebrew. Run: brew upgrade candle
|
|
94978
95509
|
`);
|
|
95510
|
+
}
|
|
95511
|
+
return 0;
|
|
94979
95512
|
}
|
|
94980
|
-
|
|
94981
|
-
|
|
94982
|
-
|
|
94983
|
-
|
|
94984
|
-
const payload = { current: CLI_VERSION, latest: null, updated: false, path: realExec, method };
|
|
94985
|
-
deps.stdout.write(`${JSON.stringify(payload)}
|
|
95513
|
+
if (method === "script") {
|
|
95514
|
+
if (json) {
|
|
95515
|
+
const payload = { current: CLI_VERSION, latest: null, updated: false, path: realExec, method };
|
|
95516
|
+
deps.stdout.write(`${JSON.stringify(payload)}
|
|
94986
95517
|
`);
|
|
94987
|
-
|
|
94988
|
-
|
|
95518
|
+
} else {
|
|
95519
|
+
deps.stdout.write(`Installed with npm (or a dev checkout). Run: npm i -g @candledottv/cli@latest
|
|
94989
95520
|
`);
|
|
95521
|
+
}
|
|
95522
|
+
return 0;
|
|
94990
95523
|
}
|
|
94991
|
-
|
|
94992
|
-
|
|
94993
|
-
|
|
94994
|
-
|
|
94995
|
-
|
|
94996
|
-
|
|
94997
|
-
|
|
94998
|
-
|
|
94999
|
-
|
|
95000
|
-
|
|
95001
|
-
|
|
95002
|
-
|
|
95003
|
-
|
|
95004
|
-
|
|
95005
|
-
|
|
95006
|
-
|
|
95007
|
-
|
|
95008
|
-
|
|
95009
|
-
}
|
|
95010
|
-
|
|
95011
|
-
|
|
95012
|
-
|
|
95013
|
-
|
|
95014
|
-
|
|
95015
|
-
const payload = { current: CLI_VERSION, latest: target.version, updated: false, path: realExec };
|
|
95016
|
-
deps.stdout.write(`${JSON.stringify(payload)}
|
|
95524
|
+
const base2 = releaseBaseUrl(deps.env);
|
|
95525
|
+
const fetched = pinned ? await fetchPinned(deps, base2, pinned) : await fetchLatest(deps, base2);
|
|
95526
|
+
if (!fetched.ok) {
|
|
95527
|
+
const code = fetched.kind === "invalid" ? "MANIFEST_INVALID" : "UPDATE_UNREACHABLE";
|
|
95528
|
+
writeLocalFailure(deps, { code, message: fetched.message }, json);
|
|
95529
|
+
return 1;
|
|
95530
|
+
}
|
|
95531
|
+
const target = fetched.manifest;
|
|
95532
|
+
let identityUri;
|
|
95533
|
+
try {
|
|
95534
|
+
identityUri = releaseIdentityUri(target.version);
|
|
95535
|
+
} catch (error) {
|
|
95536
|
+
writeLocalFailure(deps, {
|
|
95537
|
+
code: "MANIFEST_INVALID",
|
|
95538
|
+
message: `Refusing the release manifest at ${base2}: ${messageOf2(error)}.`,
|
|
95539
|
+
suggestion: "Nothing was downloaded or installed."
|
|
95540
|
+
}, json);
|
|
95541
|
+
return 1;
|
|
95542
|
+
}
|
|
95543
|
+
const order = compareVersions(CLI_VERSION, target.version);
|
|
95544
|
+
if (check) {
|
|
95545
|
+
if (json) {
|
|
95546
|
+
const payload = { current: CLI_VERSION, latest: target.version, updated: false, path: realExec };
|
|
95547
|
+
deps.stdout.write(`${JSON.stringify(payload)}
|
|
95017
95548
|
`);
|
|
95018
|
-
|
|
95019
|
-
|
|
95549
|
+
} else if (order < 0) {
|
|
95550
|
+
deps.stdout.write(`candle ${CLI_VERSION}; ${target.version} available. Run: candle update
|
|
95020
95551
|
`);
|
|
95021
|
-
|
|
95022
|
-
|
|
95552
|
+
} else {
|
|
95553
|
+
deps.stdout.write(`candle ${CLI_VERSION} is up to date (latest ${target.version})
|
|
95023
95554
|
`);
|
|
95555
|
+
}
|
|
95556
|
+
return 0;
|
|
95024
95557
|
}
|
|
95025
|
-
|
|
95026
|
-
|
|
95027
|
-
|
|
95028
|
-
|
|
95029
|
-
const payload = { current: CLI_VERSION, latest: target.version, updated: false, path: realExec };
|
|
95030
|
-
deps.stdout.write(`${JSON.stringify(payload)}
|
|
95558
|
+
if (order === 0 || order > 0 && !pinned) {
|
|
95559
|
+
if (json) {
|
|
95560
|
+
const payload = { current: CLI_VERSION, latest: target.version, updated: false, path: realExec };
|
|
95561
|
+
deps.stdout.write(`${JSON.stringify(payload)}
|
|
95031
95562
|
`);
|
|
95032
|
-
|
|
95033
|
-
|
|
95563
|
+
} else {
|
|
95564
|
+
deps.stdout.write(`candle ${CLI_VERSION} is up to date
|
|
95034
95565
|
`);
|
|
95566
|
+
}
|
|
95567
|
+
return 0;
|
|
95035
95568
|
}
|
|
95036
|
-
|
|
95037
|
-
|
|
95038
|
-
if (order > 0 && pinned)
|
|
95039
|
-
deps.stderr.write(`Warning: ${target.version} is a downgrade from ${CLI_VERSION}
|
|
95569
|
+
if (order > 0 && pinned)
|
|
95570
|
+
deps.stderr.write(`Warning: ${target.version} is a downgrade from ${CLI_VERSION}
|
|
95040
95571
|
`);
|
|
95041
|
-
|
|
95042
|
-
|
|
95043
|
-
|
|
95044
|
-
|
|
95045
|
-
|
|
95046
|
-
|
|
95047
|
-
|
|
95048
|
-
|
|
95049
|
-
|
|
95050
|
-
|
|
95051
|
-
|
|
95052
|
-
|
|
95053
|
-
|
|
95054
|
-
|
|
95055
|
-
|
|
95056
|
-
|
|
95057
|
-
|
|
95058
|
-
|
|
95059
|
-
|
|
95060
|
-
|
|
95061
|
-
|
|
95062
|
-
|
|
95063
|
-
|
|
95064
|
-
|
|
95065
|
-
|
|
95066
|
-
|
|
95067
|
-
|
|
95068
|
-
|
|
95069
|
-
|
|
95070
|
-
|
|
95071
|
-
|
|
95072
|
-
|
|
95073
|
-
|
|
95074
|
-
|
|
95075
|
-
|
|
95076
|
-
|
|
95077
|
-
|
|
95078
|
-
|
|
95079
|
-
|
|
95572
|
+
if (!deps.platformKey) {
|
|
95573
|
+
writeLocalFailure(deps, {
|
|
95574
|
+
code: "UPDATE_UNSUPPORTED_PLATFORM",
|
|
95575
|
+
message: "No release binary for this platform.",
|
|
95576
|
+
suggestion: "Run: npm i -g @candledottv/cli@latest"
|
|
95577
|
+
}, json);
|
|
95578
|
+
return 1;
|
|
95579
|
+
}
|
|
95580
|
+
const expectedName = `candle-${deps.platformKey}`;
|
|
95581
|
+
const asset = target.assets[deps.platformKey];
|
|
95582
|
+
if (!asset) {
|
|
95583
|
+
writeLocalFailure(deps, {
|
|
95584
|
+
code: "UPDATE_UNSUPPORTED_PLATFORM",
|
|
95585
|
+
message: `Release ${target.tag} has no asset for ${deps.platformKey}.`
|
|
95586
|
+
}, json);
|
|
95587
|
+
return 1;
|
|
95588
|
+
}
|
|
95589
|
+
if (asset.name !== expectedName) {
|
|
95590
|
+
writeLocalFailure(deps, {
|
|
95591
|
+
code: "MANIFEST_INVALID",
|
|
95592
|
+
message: `Release ${target.tag} names ${asset.name} as the ${deps.platformKey} asset; this platform installs ${expectedName}.`,
|
|
95593
|
+
suggestion: "Nothing was downloaded or installed."
|
|
95594
|
+
}, json);
|
|
95595
|
+
return 1;
|
|
95596
|
+
}
|
|
95597
|
+
const helperName = helperAssetName(deps.platformKey);
|
|
95598
|
+
const helperAsset = target.helpers?.[deps.platformKey];
|
|
95599
|
+
if (helperAsset !== undefined && helperAsset.name !== helperName) {
|
|
95600
|
+
writeLocalFailure(deps, {
|
|
95601
|
+
code: "MANIFEST_INVALID",
|
|
95602
|
+
message: `Release ${target.tag} names ${helperAsset.name} as the ${deps.platformKey} security key helper; this platform installs ${helperName}.`,
|
|
95603
|
+
suggestion: "Nothing was downloaded or installed."
|
|
95604
|
+
}, json);
|
|
95605
|
+
return 1;
|
|
95606
|
+
}
|
|
95607
|
+
const steps = __using(__stack, commandSteps(ctx), 0);
|
|
95608
|
+
if (!json) {
|
|
95609
|
+
const total = asset.size + (helperAsset?.size ?? 0);
|
|
95610
|
+
deps.stderr.write(`Updating candle ${CLI_VERSION} -> ${target.version} (${formatBytes(total)})
|
|
95080
95611
|
`);
|
|
95081
|
-
|
|
95082
|
-
|
|
95083
|
-
|
|
95084
|
-
|
|
95085
|
-
|
|
95086
|
-
|
|
95087
|
-
|
|
95088
|
-
|
|
95089
|
-
|
|
95090
|
-
|
|
95091
|
-
|
|
95092
|
-
|
|
95093
|
-
|
|
95094
|
-
|
|
95095
|
-
|
|
95096
|
-
|
|
95097
|
-
base: base2,
|
|
95098
|
-
tag: target.tag,
|
|
95099
|
-
name: expectedName,
|
|
95100
|
-
expectedSha256: asset.sha256,
|
|
95101
|
-
sums: sums.sums,
|
|
95102
|
-
identityUri,
|
|
95103
|
-
dir,
|
|
95104
|
-
version: target.version,
|
|
95105
|
-
staged,
|
|
95106
|
-
label: ""
|
|
95107
|
-
});
|
|
95108
|
-
if (!binary.ok) {
|
|
95109
|
-
await discardAll();
|
|
95110
|
-
writeLocalFailure(deps, binary.failure, json);
|
|
95111
|
-
return 1;
|
|
95112
|
-
}
|
|
95113
|
-
let helper = null;
|
|
95114
|
-
if (helperAsset !== undefined) {
|
|
95115
|
-
const fetched2 = await stage(deps, steps, {
|
|
95612
|
+
}
|
|
95613
|
+
steps.start("downloading SHA256SUMS");
|
|
95614
|
+
const sums = await downloadSums(deps, base2, target.tag);
|
|
95615
|
+
if (!sums.ok) {
|
|
95616
|
+
steps.fail("downloading SHA256SUMS");
|
|
95617
|
+
writeLocalFailure(deps, { code: "UPDATE_UNREACHABLE", message: sums.message }, json);
|
|
95618
|
+
return 1;
|
|
95619
|
+
}
|
|
95620
|
+
steps.done("downloaded SHA256SUMS");
|
|
95621
|
+
const dir = realExec.slice(0, realExec.lastIndexOf("/")) || ".";
|
|
95622
|
+
const staged = [];
|
|
95623
|
+
const discardAll = async () => {
|
|
95624
|
+
for (const path of staged)
|
|
95625
|
+
await discard(deps, path);
|
|
95626
|
+
};
|
|
95627
|
+
const binary = await stage(deps, steps, {
|
|
95116
95628
|
base: base2,
|
|
95117
95629
|
tag: target.tag,
|
|
95118
|
-
name:
|
|
95119
|
-
expectedSha256:
|
|
95630
|
+
name: expectedName,
|
|
95631
|
+
expectedSha256: asset.sha256,
|
|
95120
95632
|
sums: sums.sums,
|
|
95121
95633
|
identityUri,
|
|
95122
95634
|
dir,
|
|
95123
95635
|
version: target.version,
|
|
95124
95636
|
staged,
|
|
95125
|
-
label:
|
|
95637
|
+
label: ""
|
|
95126
95638
|
});
|
|
95127
|
-
if (!
|
|
95639
|
+
if (!binary.ok) {
|
|
95128
95640
|
await discardAll();
|
|
95129
|
-
writeLocalFailure(deps,
|
|
95641
|
+
writeLocalFailure(deps, binary.failure, json);
|
|
95130
95642
|
return 1;
|
|
95131
95643
|
}
|
|
95132
|
-
helper =
|
|
95133
|
-
|
|
95134
|
-
|
|
95135
|
-
|
|
95136
|
-
|
|
95137
|
-
|
|
95138
|
-
|
|
95139
|
-
|
|
95140
|
-
|
|
95141
|
-
|
|
95142
|
-
|
|
95143
|
-
|
|
95644
|
+
let helper = null;
|
|
95645
|
+
if (helperAsset !== undefined) {
|
|
95646
|
+
const fetched2 = await stage(deps, steps, {
|
|
95647
|
+
base: base2,
|
|
95648
|
+
tag: target.tag,
|
|
95649
|
+
name: helperName,
|
|
95650
|
+
expectedSha256: helperAsset.sha256,
|
|
95651
|
+
sums: sums.sums,
|
|
95652
|
+
identityUri,
|
|
95653
|
+
dir,
|
|
95654
|
+
version: target.version,
|
|
95655
|
+
staged,
|
|
95656
|
+
label: `${helperName} `
|
|
95657
|
+
});
|
|
95658
|
+
if (!fetched2.ok) {
|
|
95659
|
+
await discardAll();
|
|
95660
|
+
writeLocalFailure(deps, fetched2.failure, json);
|
|
95661
|
+
return 1;
|
|
95662
|
+
}
|
|
95663
|
+
helper = { tmpPath: fetched2.tmpPath, target: `${dir}/${HELPER_NAME}` };
|
|
95664
|
+
}
|
|
95665
|
+
steps.start("installing");
|
|
95144
95666
|
try {
|
|
95145
|
-
await deps.rename(
|
|
95667
|
+
await deps.rename(binary.tmpPath, realExec);
|
|
95146
95668
|
} catch (error) {
|
|
95147
95669
|
steps.fail("installing");
|
|
95148
|
-
await
|
|
95149
|
-
|
|
95150
|
-
writeLocalFailure(deps, {
|
|
95151
|
-
...failure,
|
|
95152
|
-
message: `${failure.message} candle ${target.version} was installed to ${realExec}, but its security key helper could not be renamed to ${helper.target}.`
|
|
95153
|
-
}, json);
|
|
95670
|
+
await discardAll();
|
|
95671
|
+
writeLocalFailure(deps, notWritable(dir, error), json);
|
|
95154
95672
|
return 1;
|
|
95155
95673
|
}
|
|
95156
|
-
|
|
95157
|
-
|
|
95158
|
-
|
|
95159
|
-
|
|
95160
|
-
|
|
95161
|
-
|
|
95162
|
-
|
|
95163
|
-
|
|
95164
|
-
|
|
95165
|
-
|
|
95166
|
-
|
|
95167
|
-
|
|
95674
|
+
if (helper !== null) {
|
|
95675
|
+
try {
|
|
95676
|
+
await deps.rename(helper.tmpPath, helper.target);
|
|
95677
|
+
} catch (error) {
|
|
95678
|
+
steps.fail("installing");
|
|
95679
|
+
await discard(deps, helper.tmpPath);
|
|
95680
|
+
const failure = notWritable(dir, error);
|
|
95681
|
+
writeLocalFailure(deps, {
|
|
95682
|
+
...failure,
|
|
95683
|
+
message: `${failure.message} candle ${target.version} was installed to ${realExec}, but its security key helper could not be renamed to ${helper.target}.`
|
|
95684
|
+
}, json);
|
|
95685
|
+
return 1;
|
|
95686
|
+
}
|
|
95687
|
+
}
|
|
95688
|
+
steps.done(`installed to ${realExec}${helper !== null ? ` and ${helper.target}` : ""}`);
|
|
95689
|
+
const helperReport2 = helper !== null ? { name: HELPER_NAME, updated: true } : null;
|
|
95690
|
+
if (json) {
|
|
95691
|
+
const payload = {
|
|
95692
|
+
current: CLI_VERSION,
|
|
95693
|
+
latest: target.version,
|
|
95694
|
+
updated: true,
|
|
95695
|
+
path: realExec,
|
|
95696
|
+
helper: helperReport2
|
|
95697
|
+
};
|
|
95698
|
+
deps.stdout.write(`${JSON.stringify(payload)}
|
|
95168
95699
|
`);
|
|
95169
|
-
|
|
95170
|
-
|
|
95700
|
+
} else {
|
|
95701
|
+
deps.stdout.write(`Updated candle ${CLI_VERSION} -> ${target.version}
|
|
95171
95702
|
`);
|
|
95172
|
-
|
|
95173
|
-
|
|
95703
|
+
if (helper !== null)
|
|
95704
|
+
deps.stdout.write(`Installed ${HELPER_NAME} ${target.version} to ${helper.target}
|
|
95174
95705
|
`);
|
|
95706
|
+
}
|
|
95707
|
+
return 0;
|
|
95708
|
+
} catch (_catch) {
|
|
95709
|
+
var _err = _catch, _hasErr = 1;
|
|
95710
|
+
} finally {
|
|
95711
|
+
__callDispose(__stack, _err, _hasErr);
|
|
95175
95712
|
}
|
|
95176
|
-
return 0;
|
|
95177
95713
|
}
|
|
95178
95714
|
async function stage(deps, steps, opts) {
|
|
95179
95715
|
const { name: name2, label } = opts;
|
|
@@ -96289,13 +96825,13 @@ async function addPasskeyFactor(ctx, parsed, resolvedVault, hold) {
|
|
|
96289
96825
|
let committed;
|
|
96290
96826
|
try {
|
|
96291
96827
|
const prfOutput = await assertPlatformPrf(deps, session, envelope, vaultId, "derive this vault's key on it");
|
|
96292
|
-
let
|
|
96828
|
+
let wrap;
|
|
96293
96829
|
try {
|
|
96294
|
-
|
|
96830
|
+
wrap = await wrapDekForPrf(vault.dek, prfOutput, envelope, vault.file);
|
|
96295
96831
|
} finally {
|
|
96296
96832
|
wipe(prfOutput);
|
|
96297
96833
|
}
|
|
96298
|
-
sealed = { ...envelope, wrap
|
|
96834
|
+
sealed = { ...envelope, wrap };
|
|
96299
96835
|
committed = hold(await commitVault(vault, { index: vault.index, envelopes: [...vault.file.envelopes, sealed] }, deps));
|
|
96300
96836
|
} catch (error) {
|
|
96301
96837
|
deps.stderr.write(`${PASSKEY_LEFTOVER_NOTE}
|
|
@@ -96380,86 +96916,93 @@ import { randomBytes as randomBytes12 } from "node:crypto";
|
|
|
96380
96916
|
init_release();
|
|
96381
96917
|
init_fido2();
|
|
96382
96918
|
async function installHelperForThisRelease(ctx) {
|
|
96383
|
-
|
|
96384
|
-
const location = await locateFido2Helper(deps);
|
|
96385
|
-
if (location.state === "ready")
|
|
96386
|
-
return { ok: true, path: location.path, installed: false };
|
|
96387
|
-
if (!location.installable)
|
|
96388
|
-
return { ok: false, message: location.reason };
|
|
96389
|
-
const realExec = await deps.realpath(deps.execPath).catch(() => deps.execPath);
|
|
96390
|
-
if (detectInstall(deps.execPath, realExec) === "homebrew") {
|
|
96391
|
-
return { ok: false, message: `candle is installed by Homebrew at ${realExec}. Run: brew reinstall candle` };
|
|
96392
|
-
}
|
|
96393
|
-
if (!deps.platformKey)
|
|
96394
|
-
return { ok: false, message: "this CLI ships no security key helper for this platform" };
|
|
96395
|
-
const base2 = releaseBaseUrl(deps.env);
|
|
96396
|
-
const tag = `cli-v${CLI_VERSION}`;
|
|
96397
|
-
const name2 = helperAssetName(deps.platformKey);
|
|
96398
|
-
const steps = json ? stepReporter(() => {}, false) : stepReporter((text3) => deps.stderr.write(text3), process.stderr.isTTY === true);
|
|
96399
|
-
steps.start(`reading the ${tag} release manifest`);
|
|
96400
|
-
const fetched = await fetchPinned(deps, base2, tag);
|
|
96401
|
-
if (!fetched.ok) {
|
|
96402
|
-
steps.fail(`reading the ${tag} release manifest`);
|
|
96403
|
-
return { ok: false, message: fetched.message };
|
|
96404
|
-
}
|
|
96405
|
-
const asset = fetched.manifest.helpers?.[deps.platformKey];
|
|
96406
|
-
if (asset === undefined) {
|
|
96407
|
-
steps.fail(`reading the ${tag} release manifest`);
|
|
96408
|
-
return { ok: false, message: `release ${tag} declares no ${name2}` };
|
|
96409
|
-
}
|
|
96410
|
-
if (asset.name !== name2) {
|
|
96411
|
-
steps.fail(`reading the ${tag} release manifest`);
|
|
96412
|
-
return {
|
|
96413
|
-
ok: false,
|
|
96414
|
-
message: `release ${tag} names ${asset.name} as the ${deps.platformKey} security key helper; this platform installs ${name2}`
|
|
96415
|
-
};
|
|
96416
|
-
}
|
|
96417
|
-
steps.done(`release ${tag} declares ${name2} (${formatBytes(asset.size)})`);
|
|
96418
|
-
steps.start(`downloading ${name2}`);
|
|
96419
|
-
const sums = await downloadSums(deps, base2, tag);
|
|
96420
|
-
if (!sums.ok) {
|
|
96421
|
-
steps.fail(`downloading ${name2}`);
|
|
96422
|
-
return { ok: false, message: sums.message };
|
|
96423
|
-
}
|
|
96424
|
-
const download = await downloadReleaseAsset(deps, base2, tag, name2);
|
|
96425
|
-
if (!download.ok) {
|
|
96426
|
-
steps.fail(`downloading ${name2}`);
|
|
96427
|
-
return { ok: false, message: download.message };
|
|
96428
|
-
}
|
|
96429
|
-
steps.done(`downloaded ${name2} (${formatBytes(download.bytes.length)})`);
|
|
96430
|
-
steps.start("verifying checksum and signature");
|
|
96431
|
-
const checked = checkReleaseAsset(deps, {
|
|
96432
|
-
name: name2,
|
|
96433
|
-
bytes: download.bytes,
|
|
96434
|
-
sums: sums.sums,
|
|
96435
|
-
bundle: download.bundle,
|
|
96436
|
-
expectedSha256: asset.sha256,
|
|
96437
|
-
identityUri: releaseIdentityUri(CLI_VERSION)
|
|
96438
|
-
});
|
|
96439
|
-
if (!checked.ok) {
|
|
96440
|
-
steps.fail("verifying checksum and signature");
|
|
96441
|
-
return { ok: false, message: `${checked.message}${checked.suggestion ? ` ${checked.suggestion}` : ""}` };
|
|
96442
|
-
}
|
|
96443
|
-
steps.done("checksum and signature verified (Sigstore, keyless; signer pinned to the release workflow)");
|
|
96444
|
-
const dir = realExec.slice(0, realExec.lastIndexOf("/")) || ".";
|
|
96445
|
-
const target = `${dir}/${HELPER_NAME}`;
|
|
96446
|
-
const tmpPath = `${dir}/.candle-fido2-${CLI_VERSION}-${randomBytes12(6).toString("hex")}`;
|
|
96447
|
-
steps.start(`installing ${HELPER_NAME}`);
|
|
96919
|
+
let __stack = [];
|
|
96448
96920
|
try {
|
|
96449
|
-
|
|
96450
|
-
|
|
96451
|
-
|
|
96452
|
-
|
|
96453
|
-
|
|
96454
|
-
|
|
96455
|
-
await deps.
|
|
96456
|
-
|
|
96457
|
-
|
|
96458
|
-
|
|
96459
|
-
|
|
96921
|
+
const { deps } = ctx;
|
|
96922
|
+
const location = await locateFido2Helper(deps);
|
|
96923
|
+
if (location.state === "ready")
|
|
96924
|
+
return { ok: true, path: location.path, installed: false };
|
|
96925
|
+
if (!location.installable)
|
|
96926
|
+
return { ok: false, message: location.reason };
|
|
96927
|
+
const realExec = await deps.realpath(deps.execPath).catch(() => deps.execPath);
|
|
96928
|
+
if (detectInstall(deps.execPath, realExec) === "homebrew") {
|
|
96929
|
+
return { ok: false, message: `candle is installed by Homebrew at ${realExec}. Run: brew reinstall candle` };
|
|
96930
|
+
}
|
|
96931
|
+
if (!deps.platformKey)
|
|
96932
|
+
return { ok: false, message: "this CLI ships no security key helper for this platform" };
|
|
96933
|
+
const base2 = releaseBaseUrl(deps.env);
|
|
96934
|
+
const tag = `cli-v${CLI_VERSION}`;
|
|
96935
|
+
const name2 = helperAssetName(deps.platformKey);
|
|
96936
|
+
const steps = __using(__stack, commandSteps(ctx), 0);
|
|
96937
|
+
steps.start(`reading the ${tag} release manifest`);
|
|
96938
|
+
const fetched = await fetchPinned(deps, base2, tag);
|
|
96939
|
+
if (!fetched.ok) {
|
|
96940
|
+
steps.fail(`reading the ${tag} release manifest`);
|
|
96941
|
+
return { ok: false, message: fetched.message };
|
|
96942
|
+
}
|
|
96943
|
+
const asset = fetched.manifest.helpers?.[deps.platformKey];
|
|
96944
|
+
if (asset === undefined) {
|
|
96945
|
+
steps.fail(`reading the ${tag} release manifest`);
|
|
96946
|
+
return { ok: false, message: `release ${tag} declares no ${name2}` };
|
|
96947
|
+
}
|
|
96948
|
+
if (asset.name !== name2) {
|
|
96949
|
+
steps.fail(`reading the ${tag} release manifest`);
|
|
96950
|
+
return {
|
|
96951
|
+
ok: false,
|
|
96952
|
+
message: `release ${tag} names ${asset.name} as the ${deps.platformKey} security key helper; this platform installs ${name2}`
|
|
96953
|
+
};
|
|
96954
|
+
}
|
|
96955
|
+
steps.done(`release ${tag} declares ${name2} (${formatBytes(asset.size)})`);
|
|
96956
|
+
steps.start(`downloading ${name2}`);
|
|
96957
|
+
const sums = await downloadSums(deps, base2, tag);
|
|
96958
|
+
if (!sums.ok) {
|
|
96959
|
+
steps.fail(`downloading ${name2}`);
|
|
96960
|
+
return { ok: false, message: sums.message };
|
|
96961
|
+
}
|
|
96962
|
+
const download = await downloadReleaseAsset(deps, base2, tag, name2);
|
|
96963
|
+
if (!download.ok) {
|
|
96964
|
+
steps.fail(`downloading ${name2}`);
|
|
96965
|
+
return { ok: false, message: download.message };
|
|
96966
|
+
}
|
|
96967
|
+
steps.done(`downloaded ${name2} (${formatBytes(download.bytes.length)})`);
|
|
96968
|
+
steps.start("verifying checksum and signature");
|
|
96969
|
+
const checked = checkReleaseAsset(deps, {
|
|
96970
|
+
name: name2,
|
|
96971
|
+
bytes: download.bytes,
|
|
96972
|
+
sums: sums.sums,
|
|
96973
|
+
bundle: download.bundle,
|
|
96974
|
+
expectedSha256: asset.sha256,
|
|
96975
|
+
identityUri: releaseIdentityUri(CLI_VERSION)
|
|
96976
|
+
});
|
|
96977
|
+
if (!checked.ok) {
|
|
96978
|
+
steps.fail("verifying checksum and signature");
|
|
96979
|
+
return { ok: false, message: `${checked.message}${checked.suggestion ? ` ${checked.suggestion}` : ""}` };
|
|
96980
|
+
}
|
|
96981
|
+
steps.done("checksum and signature verified (Sigstore, keyless; signer pinned to the release workflow)");
|
|
96982
|
+
const dir = realExec.slice(0, realExec.lastIndexOf("/")) || ".";
|
|
96983
|
+
const target = `${dir}/${HELPER_NAME}`;
|
|
96984
|
+
const tmpPath = `${dir}/.candle-fido2-${CLI_VERSION}-${randomBytes12(6).toString("hex")}`;
|
|
96985
|
+
steps.start(`installing ${HELPER_NAME}`);
|
|
96986
|
+
try {
|
|
96987
|
+
await deps.writeBytes(tmpPath, download.bytes);
|
|
96988
|
+
} catch (error) {
|
|
96989
|
+
steps.fail(`installing ${HELPER_NAME}`);
|
|
96990
|
+
return { ok: false, message: `cannot write ${dir}: ${messageOf2(error)}` };
|
|
96991
|
+
}
|
|
96992
|
+
try {
|
|
96993
|
+
await deps.rename(tmpPath, target);
|
|
96994
|
+
} catch (error) {
|
|
96995
|
+
steps.fail(`installing ${HELPER_NAME}`);
|
|
96996
|
+
await discard2(deps, tmpPath);
|
|
96997
|
+
return { ok: false, message: `cannot write ${dir}: ${messageOf2(error)}` };
|
|
96998
|
+
}
|
|
96999
|
+
steps.done(`installed ${HELPER_NAME} ${CLI_VERSION} to ${target}`);
|
|
97000
|
+
return { ok: true, path: target, installed: true };
|
|
97001
|
+
} catch (_catch) {
|
|
97002
|
+
var _err = _catch, _hasErr = 1;
|
|
97003
|
+
} finally {
|
|
97004
|
+
__callDispose(__stack, _err, _hasErr);
|
|
96460
97005
|
}
|
|
96461
|
-
steps.done(`installed ${HELPER_NAME} ${CLI_VERSION} to ${target}`);
|
|
96462
|
-
return { ok: true, path: target, installed: true };
|
|
96463
97006
|
}
|
|
96464
97007
|
async function discard2(deps, path) {
|
|
96465
97008
|
try {
|
|
@@ -96595,13 +97138,13 @@ async function addSecurityKeyFactor(ctx, parsed, resolvedVault, hold) {
|
|
|
96595
97138
|
wrap: { alg: VAULT_CIPHER, iv: "", ciphertext: "" }
|
|
96596
97139
|
};
|
|
96597
97140
|
const prfOutput = await assertPrf(deps, session, envelope, vault.file.vaultId, "derive this vault's key on it");
|
|
96598
|
-
let
|
|
97141
|
+
let wrap;
|
|
96599
97142
|
try {
|
|
96600
|
-
|
|
97143
|
+
wrap = await wrapDekForPrf(vault.dek, prfOutput, envelope, vault.file);
|
|
96601
97144
|
} finally {
|
|
96602
97145
|
wipe(prfOutput);
|
|
96603
97146
|
}
|
|
96604
|
-
const sealed = { ...envelope, wrap
|
|
97147
|
+
const sealed = { ...envelope, wrap };
|
|
96605
97148
|
await commitVault(vault, { index: vault.index, envelopes: [...vault.file.envelopes, sealed] }, deps);
|
|
96606
97149
|
const written = await readVaultRaw(path);
|
|
96607
97150
|
if (written === null)
|
|
@@ -96797,13 +97340,13 @@ async function addTouchIdFactor(ctx, parsed, resolvedVault, hold) {
|
|
|
96797
97340
|
kek: { alg: SECURE_ENCLAVE_KEK_ALG, ciphertext },
|
|
96798
97341
|
wrap: { alg: VAULT_CIPHER, iv: "", ciphertext: "" }
|
|
96799
97342
|
};
|
|
96800
|
-
let
|
|
97343
|
+
let wrap;
|
|
96801
97344
|
try {
|
|
96802
|
-
|
|
97345
|
+
wrap = await wrapDekForKek(vault.dek, kek, envelope, vault.file);
|
|
96803
97346
|
} finally {
|
|
96804
97347
|
wipe(kek);
|
|
96805
97348
|
}
|
|
96806
|
-
sealed = { ...envelope, wrap
|
|
97349
|
+
sealed = { ...envelope, wrap };
|
|
96807
97350
|
committed = hold(await commitVault(vault, { index: vault.index, envelopes: [...vault.file.envelopes, sealed] }, deps));
|
|
96808
97351
|
} catch (error) {
|
|
96809
97352
|
await removeKey();
|
|
@@ -96918,8 +97461,8 @@ async function createVault(request2, clock) {
|
|
|
96918
97461
|
}
|
|
96919
97462
|
const dek = freshDek();
|
|
96920
97463
|
const file = await withSecret(dek, async (dekBytes) => {
|
|
96921
|
-
const
|
|
96922
|
-
const sealedEnvelope = { ...envelope, wrap
|
|
97464
|
+
const wrap = await wrapDekForPassphrase(dekBytes, request2.passphrase, envelope, { vaultId }, request2.notice);
|
|
97465
|
+
const sealedEnvelope = { ...envelope, wrap };
|
|
96923
97466
|
const payloadKey = await derivePayloadKey(dekBytes, unb64u(vaultId, "vaultId"));
|
|
96924
97467
|
const root = await seal(payloadKey, request2.rootEntropy, rootAad(vaultId));
|
|
96925
97468
|
let index = seedIndex;
|
|
@@ -97355,8 +97898,8 @@ async function vaultFactorAdd(args, ctx) {
|
|
|
97355
97898
|
strength: strengthFor(ownPassphrase),
|
|
97356
97899
|
wrap: { alg: VAULT_CIPHER, iv: "", ciphertext: "" }
|
|
97357
97900
|
};
|
|
97358
|
-
const
|
|
97359
|
-
const sealed = { ...envelope, wrap
|
|
97901
|
+
const wrap = await wrapDekForPassphrase(vault.dek, passphrase, envelope, vault.file, (line) => deps.stderr.write(line));
|
|
97902
|
+
const sealed = { ...envelope, wrap };
|
|
97360
97903
|
await commitVault(vault, { index: vault.index, envelopes: [...vault.file.envelopes, sealed] }, deps);
|
|
97361
97904
|
const written = await readVaultRaw(path);
|
|
97362
97905
|
if (written === null)
|
|
@@ -102299,7 +102842,7 @@ function solOf(lamports) {
|
|
|
102299
102842
|
const digits = lamports.padStart(10, "0");
|
|
102300
102843
|
return `${digits.slice(0, -9)}.${digits.slice(-9)}`.replace(/\.?0+$/, "");
|
|
102301
102844
|
}
|
|
102302
|
-
function
|
|
102845
|
+
function previewLines3(preview, walletName) {
|
|
102303
102846
|
const lines = [
|
|
102304
102847
|
`Close ${preview.accountCount} empty token account${preview.accountCount === 1 ? "" : "s"} on the ${walletName}`,
|
|
102305
102848
|
`Rent returned to that same wallet: ${safeText(preview.totalSol)} SOL in ${preview.transactions} transaction${preview.transactions === 1 ? "" : "s"}, network fee about ${solOf(preview.estimatedFeeLamports)} SOL, net ${safeText(preview.netSol)} SOL`
|
|
@@ -102335,7 +102878,7 @@ async function previewAndConfirm(ctx, preview, walletName, yes) {
|
|
|
102335
102878
|
const output = ctx.json ? ctx.deps.stderr : ctx.deps.stdout;
|
|
102336
102879
|
output.write(`No empty token accounts to close on the ${walletName}.
|
|
102337
102880
|
`);
|
|
102338
|
-
for (const line of
|
|
102881
|
+
for (const line of previewLines3(preview, walletName).slice(2))
|
|
102339
102882
|
output.write(`${line}
|
|
102340
102883
|
`);
|
|
102341
102884
|
return printTradingResult(ctx, {
|
|
@@ -102345,7 +102888,7 @@ async function previewAndConfirm(ctx, preview, walletName, yes) {
|
|
|
102345
102888
|
preview
|
|
102346
102889
|
});
|
|
102347
102890
|
}
|
|
102348
|
-
if (!await confirmClose(ctx,
|
|
102891
|
+
if (!await confirmClose(ctx, previewLines3(preview, walletName), yes))
|
|
102349
102892
|
return printTradingResult(ctx, {
|
|
102350
102893
|
success: true,
|
|
102351
102894
|
status: "cancelled",
|
|
@@ -102841,7 +103384,7 @@ async function maybeWriteUpdateNotice(deps, opts = {}) {
|
|
|
102841
103384
|
if (prior && prior.version === latest && deps.now() - prior.shownAt < DAY_MS)
|
|
102842
103385
|
return;
|
|
102843
103386
|
const line = `Update available: candle ${CLI_VERSION} -> ${latest}. Run: candle update`;
|
|
102844
|
-
const tty =
|
|
103387
|
+
const tty = terminalStyle(deps, "stderr", opts.json).color !== 0;
|
|
102845
103388
|
deps.stderr.write(tty ? `\x1B[2m${line}\x1B[0m
|
|
102846
103389
|
` : `${line}
|
|
102847
103390
|
`);
|
|
@@ -102872,6 +103415,10 @@ function extractGlobalFlags(argv) {
|
|
|
102872
103415
|
const arg = argv[i];
|
|
102873
103416
|
if (arg === "--json")
|
|
102874
103417
|
flags2.json = true;
|
|
103418
|
+
else if (arg === "--plain")
|
|
103419
|
+
flags2.plain = true;
|
|
103420
|
+
else if (arg === "--no-color")
|
|
103421
|
+
flags2.noColor = true;
|
|
102875
103422
|
else if (arg === "--help" || arg === "-h")
|
|
102876
103423
|
flags2.help = true;
|
|
102877
103424
|
else if (arg === "--version" || arg === "-v")
|
|
@@ -103081,6 +103628,16 @@ var NEVER_GUARDED = new Set([
|
|
|
103081
103628
|
"tui"
|
|
103082
103629
|
]);
|
|
103083
103630
|
async function run2(argv, deps) {
|
|
103631
|
+
const cosmetic = extractGlobalFlags(argv);
|
|
103632
|
+
if (!("error" in cosmetic)) {
|
|
103633
|
+
deps = {
|
|
103634
|
+
...deps,
|
|
103635
|
+
presentation: {
|
|
103636
|
+
plain: cosmetic.flags.plain ?? deps.presentation?.plain,
|
|
103637
|
+
noColor: cosmetic.flags.noColor ?? deps.presentation?.noColor
|
|
103638
|
+
}
|
|
103639
|
+
};
|
|
103640
|
+
}
|
|
103084
103641
|
let code;
|
|
103085
103642
|
try {
|
|
103086
103643
|
code = await runCommand(argv, deps);
|
|
@@ -103098,7 +103655,8 @@ async function run2(argv, deps) {
|
|
|
103098
103655
|
}
|
|
103099
103656
|
const extractedForNotice = extractGlobalFlags(argv);
|
|
103100
103657
|
const word3 = "error" in extractedForNotice ? undefined : canonicalCommand(extractedForNotice.rest[0] === "candle" ? extractedForNotice.rest[1] : extractedForNotice.rest[0]);
|
|
103101
|
-
|
|
103658
|
+
if (!("error" in extractedForNotice) && !extractedForNotice.flags.help && !extractedForNotice.flags.version && word3 !== undefined && word3 !== "help" && word3 !== "completion" && word3 !== "mcp" && !(word3 === "core" && extractedForNotice.rest.includes("mcp")))
|
|
103659
|
+
await maybeWriteUpdateNotice(deps, { command: word3, json: extractedForNotice.flags.json });
|
|
103102
103660
|
return code;
|
|
103103
103661
|
}
|
|
103104
103662
|
async function runCommand(argv, deps) {
|
|
@@ -103146,9 +103704,14 @@ async function runCommand(argv, deps) {
|
|
|
103146
103704
|
return 0;
|
|
103147
103705
|
}
|
|
103148
103706
|
if (flags2.help) {
|
|
103149
|
-
deps
|
|
103707
|
+
const style = terminalStyle(deps, "stdout", flags2.json);
|
|
103708
|
+
deps.stdout.write(renderTopic(canonicalCommand(tokens[0]) ?? "", style) ?? renderTopLevel(style));
|
|
103150
103709
|
return 0;
|
|
103151
103710
|
}
|
|
103711
|
+
if (tokens.length === 0) {
|
|
103712
|
+
deps.stderr.write(renderTopLevel(terminalStyle(deps, "stderr", flags2.json)));
|
|
103713
|
+
return 1;
|
|
103714
|
+
}
|
|
103152
103715
|
const [rawCmd, sub, ...cmdArgs] = tokens;
|
|
103153
103716
|
const cmd = canonicalCommand(rawCmd);
|
|
103154
103717
|
if (cmd === "help" || cmd === "completion") {
|
|
@@ -103378,6 +103941,7 @@ async function buildRealDeps() {
|
|
|
103378
103941
|
stdout: Boolean(process.stdout.isTTY),
|
|
103379
103942
|
stderr: Boolean(process.stderr.isTTY)
|
|
103380
103943
|
},
|
|
103944
|
+
columns: { stdout: process.stdout.columns, stderr: process.stderr.columns },
|
|
103381
103945
|
execPath: process.execPath,
|
|
103382
103946
|
argv1: process.argv[1] ?? "",
|
|
103383
103947
|
platformKey: platformKey(process.platform, process.arch),
|