@candledottv/cli 0.13.17 → 0.13.19
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 +18 -7
- package/dist/index.js +1148 -314
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -532,7 +532,7 @@ var init_terminal_theme = __esm(() => {
|
|
|
532
532
|
});
|
|
533
533
|
|
|
534
534
|
// src/version.ts
|
|
535
|
-
var CLI_VERSION = "0.13.
|
|
535
|
+
var CLI_VERSION = "0.13.19";
|
|
536
536
|
|
|
537
537
|
// src/presentation.ts
|
|
538
538
|
function terminalStyle(deps, stream, machine = false) {
|
|
@@ -10551,8 +10551,8 @@ function createSolanaRpc(url, fetchFn, sleep) {
|
|
|
10551
10551
|
const r = await read("simulateTransaction", [
|
|
10552
10552
|
txBase64,
|
|
10553
10553
|
withMinContextSlot({
|
|
10554
|
-
sigVerify: false,
|
|
10555
|
-
replaceRecentBlockhash:
|
|
10554
|
+
sigVerify: opts?.sigVerify ?? false,
|
|
10555
|
+
replaceRecentBlockhash: !opts?.sigVerify,
|
|
10556
10556
|
commitment: "finalized",
|
|
10557
10557
|
encoding: "base64",
|
|
10558
10558
|
accounts: { encoding: "base64", addresses }
|
|
@@ -10686,6 +10686,7 @@ function withContextFloor(rpc, floor) {
|
|
|
10686
10686
|
const finalized = (opts) => {
|
|
10687
10687
|
const base = required(opts);
|
|
10688
10688
|
return {
|
|
10689
|
+
...opts?.sigVerify !== undefined ? { sigVerify: opts.sigVerify } : {},
|
|
10689
10690
|
get minContextSlot() {
|
|
10690
10691
|
return base.minContextSlot;
|
|
10691
10692
|
},
|
|
@@ -11163,6 +11164,25 @@ var init_solana_alt = __esm(() => {
|
|
|
11163
11164
|
];
|
|
11164
11165
|
});
|
|
11165
11166
|
|
|
11167
|
+
// src/account.ts
|
|
11168
|
+
async function fetchAccount(deps, apiUrl, apiKey) {
|
|
11169
|
+
const identity = await apiRequest("/api/v1/agent/wallets/embedded", {
|
|
11170
|
+
auth: "key",
|
|
11171
|
+
credentials: { apiKey },
|
|
11172
|
+
apiUrl,
|
|
11173
|
+
fetch: deps.fetch,
|
|
11174
|
+
env: deps.env
|
|
11175
|
+
});
|
|
11176
|
+
const body = identity.ok ? identity.body : undefined;
|
|
11177
|
+
const account = body?.account;
|
|
11178
|
+
if (account)
|
|
11179
|
+
return { account, ...body?.username ? { username: body.username } : {} };
|
|
11180
|
+
return { failure: identity.ok ? "no account in the response" : identity.message };
|
|
11181
|
+
}
|
|
11182
|
+
var init_account = __esm(() => {
|
|
11183
|
+
init_client();
|
|
11184
|
+
});
|
|
11185
|
+
|
|
11166
11186
|
// src/profiles.ts
|
|
11167
11187
|
function assertOrdinaryProfile(config, profile) {
|
|
11168
11188
|
if (profile !== undefined && config.profiles?.[profile]?.apiKeyItem !== undefined) {
|
|
@@ -11297,7 +11317,7 @@ function credentialEnvOverrides(env) {
|
|
|
11297
11317
|
}
|
|
11298
11318
|
function identityLine(profile, account, apiUrl, overrides, username, options = {}) {
|
|
11299
11319
|
const address = account === undefined ? undefined : shortId(account, options.full);
|
|
11300
|
-
const shown = overrides?.length ? `unknown (${overrides.join(", ")} override)` : username && address ? `${username} (${address})` : address ?? "unknown";
|
|
11320
|
+
const shown = overrides?.length ? address ? `${address} (${overrides.join(", ")} override)` : `unknown (${overrides.join(", ")} override)` : username && address ? `${username} (${address})` : address ?? "unknown";
|
|
11301
11321
|
const name = profile ?? "none";
|
|
11302
11322
|
const host = apiUrl.replace(/^https:\/\//i, "").replace(/\/+$/, "");
|
|
11303
11323
|
const sep = options.style ? symbols(options.style).note : "·";
|
|
@@ -11308,7 +11328,20 @@ async function printIdentity(ctx) {
|
|
|
11308
11328
|
return;
|
|
11309
11329
|
const config = await ctx.deps.readConfig();
|
|
11310
11330
|
const fields2 = effectiveProfileFields(config, ctx.profile);
|
|
11311
|
-
const
|
|
11331
|
+
const overrides = credentialEnvOverrides(ctx.deps.env);
|
|
11332
|
+
let account = fields2.account;
|
|
11333
|
+
let username = fields2.username;
|
|
11334
|
+
if (overrides.length > 0) {
|
|
11335
|
+
account = undefined;
|
|
11336
|
+
username = undefined;
|
|
11337
|
+
const key = ctx.deps.env.CANDLE_API_KEY?.trim();
|
|
11338
|
+
if (key) {
|
|
11339
|
+
const lookup = await fetchAccount(ctx.deps, ctx.apiUrl, key);
|
|
11340
|
+
if (lookup.account)
|
|
11341
|
+
account = lookup.account;
|
|
11342
|
+
}
|
|
11343
|
+
}
|
|
11344
|
+
const line = identityLine(ctx.profile, account, ctx.apiUrl, overrides.length > 0 ? overrides : undefined, username, identityStyle(ctx.deps, "stdout"));
|
|
11312
11345
|
ctx.deps.stdout.write(`${line}
|
|
11313
11346
|
|
|
11314
11347
|
`);
|
|
@@ -11352,6 +11385,7 @@ function profileTable(config, now) {
|
|
|
11352
11385
|
}
|
|
11353
11386
|
var ProfileFleetOnlyError, PRE_PROFILE_FIELDS, API_KEY_PREFIXES, API_KEY_RANDOM_LENGTH = 43, INVALID_RPC_HOST = "invalid (fix with profile set)";
|
|
11354
11387
|
var init_profiles = __esm(() => {
|
|
11388
|
+
init_account();
|
|
11355
11389
|
init_presentation();
|
|
11356
11390
|
init_render();
|
|
11357
11391
|
ProfileFleetOnlyError = class ProfileFleetOnlyError extends Error {
|
|
@@ -12415,6 +12449,7 @@ var init_errors2 = __esm(() => {
|
|
|
12415
12449
|
"VAULT_FIELD_UNKNOWN",
|
|
12416
12450
|
"VAULT_KDF_OUT_OF_BOUNDS",
|
|
12417
12451
|
"VAULT_UNLOCK_FAILED",
|
|
12452
|
+
"VAULT_TERMINAL_REQUIRED",
|
|
12418
12453
|
"VAULT_BLOB_TAMPERED",
|
|
12419
12454
|
"VAULT_INDEX_INVALID",
|
|
12420
12455
|
"VAULT_OLDER_COPY",
|
|
@@ -15295,24 +15330,44 @@ function teeWalletList(rows) {
|
|
|
15295
15330
|
return rows.map(describeWallet).join("; ");
|
|
15296
15331
|
}
|
|
15297
15332
|
async function completeTradingWallet(ctx, row, appId, scope, chain2 = "solana", key) {
|
|
15333
|
+
const inspected = await inspectTradingWallet(ctx, row, appId, scope, chain2, key, true);
|
|
15334
|
+
if (inspected.failures[0])
|
|
15335
|
+
throw inspected.failures[0];
|
|
15336
|
+
return inspected.row;
|
|
15337
|
+
}
|
|
15338
|
+
async function inspectTradingWallet(ctx, row, appId, scope, chain2 = "solana", key, firstOnly = false) {
|
|
15339
|
+
const failures = [];
|
|
15298
15340
|
if (rowChain(row) !== chain2)
|
|
15299
|
-
|
|
15341
|
+
failures.push(chainMismatch(`TEE wallet ${describeWallet(row)}`, rowChain(row), chain2));
|
|
15342
|
+
if (firstOnly && failures.length)
|
|
15343
|
+
return { failures };
|
|
15300
15344
|
if (!row.active)
|
|
15301
|
-
|
|
15345
|
+
failures.push(new TradingError("TEE_WALLET_INACTIVE", `The payer must be a verified-active ${chainName(chain2)} TEE wallet.`));
|
|
15346
|
+
if (firstOnly && failures.length)
|
|
15347
|
+
return { failures };
|
|
15302
15348
|
if (scope === "launch:write" && row.allowLaunch !== true)
|
|
15303
|
-
|
|
15349
|
+
failures.push(new TradingError("LAUNCH_NOT_ALLOWED", `The account owner must turn on allowLaunch for wallet ${row.id}: run candle wallets allow-launch ${row.id} on a profile signed in with candle auth login. An agent key cannot.`));
|
|
15350
|
+
if (firstOnly && failures.length)
|
|
15351
|
+
return { failures };
|
|
15304
15352
|
if (!appId || !row.privyWalletId)
|
|
15305
|
-
|
|
15353
|
+
failures.push(new TradingError("SIGNER_UNAVAILABLE", "The server did not return its relay public identifiers."));
|
|
15354
|
+
if (firstOnly && failures.length)
|
|
15355
|
+
return { failures };
|
|
15306
15356
|
const signer = await localSignerFor(ctx.deps, row.id, row.signerQuorumId);
|
|
15307
15357
|
if (!signer)
|
|
15308
|
-
|
|
15358
|
+
failures.push(new TradingError("SIGNER_UNAVAILABLE", await signerUnavailableMessage(ctx, row, key)));
|
|
15359
|
+
if (failures.length || !signer || !row.privyWalletId)
|
|
15360
|
+
return { failures };
|
|
15309
15361
|
return {
|
|
15310
|
-
|
|
15311
|
-
|
|
15312
|
-
|
|
15313
|
-
|
|
15314
|
-
|
|
15315
|
-
|
|
15362
|
+
failures,
|
|
15363
|
+
row: {
|
|
15364
|
+
id: row.id,
|
|
15365
|
+
address: row.address,
|
|
15366
|
+
privyWalletId: row.privyWalletId,
|
|
15367
|
+
appId,
|
|
15368
|
+
signer: signer.pem,
|
|
15369
|
+
chain: row.chain === "evm" ? "evm" : "solana"
|
|
15370
|
+
}
|
|
15316
15371
|
};
|
|
15317
15372
|
}
|
|
15318
15373
|
async function signerUnavailableMessage(ctx, row, key) {
|
|
@@ -15345,7 +15400,18 @@ async function tradingWallet(ctx, key, name, scope, chain2 = "solana") {
|
|
|
15345
15400
|
return { ...wallet, scopes };
|
|
15346
15401
|
}
|
|
15347
15402
|
async function tradingPayer(ctx, key, name, scope = "swap:write", chain2 = "solana") {
|
|
15348
|
-
const
|
|
15403
|
+
const listing = await listTradingWallets(ctx, key, scope);
|
|
15404
|
+
const selected = await selectTradingPayer(ctx, key, name, listing, chain2);
|
|
15405
|
+
if (selected.kind === "embedded")
|
|
15406
|
+
return selected;
|
|
15407
|
+
const wallet = await completeTradingWallet(ctx, selected.row, listing.appId, scope, chain2, {
|
|
15408
|
+
apiKey: key,
|
|
15409
|
+
keyPrefix: listing.keyPrefix
|
|
15410
|
+
});
|
|
15411
|
+
return { kind: "tee", wallet, scopes: listing.scopes };
|
|
15412
|
+
}
|
|
15413
|
+
async function selectTradingPayer(ctx, key, name, listing, chain2 = "solana") {
|
|
15414
|
+
const { rows, scopes, keyPrefix } = listing;
|
|
15349
15415
|
const discovered = embeddedSchema.parse(await request(ctx, key, "/api/v1/agent/wallets/embedded"));
|
|
15350
15416
|
const wallets = discovered.wallets;
|
|
15351
15417
|
const embeddedOn = {
|
|
@@ -15364,14 +15430,7 @@ async function tradingPayer(ctx, key, name, scope = "swap:write", chain2 = "sola
|
|
|
15364
15430
|
].join("; ");
|
|
15365
15431
|
if (name === undefined) {
|
|
15366
15432
|
if (onChain.length === 1 && !embedded)
|
|
15367
|
-
return {
|
|
15368
|
-
kind: "tee",
|
|
15369
|
-
wallet: await completeTradingWallet(ctx, onChain[0], appId, scope, chain2, {
|
|
15370
|
-
apiKey: key,
|
|
15371
|
-
keyPrefix
|
|
15372
|
-
}),
|
|
15373
|
-
scopes
|
|
15374
|
-
};
|
|
15433
|
+
return { kind: "tee", row: onChain[0] };
|
|
15375
15434
|
if (onChain.length === 0 && embedded)
|
|
15376
15435
|
return asEmbedded();
|
|
15377
15436
|
if (onChain.length === 0 && deniedAddress !== undefined)
|
|
@@ -15380,14 +15439,7 @@ async function tradingPayer(ctx, key, name, scope = "swap:write", chain2 = "sola
|
|
|
15380
15439
|
}
|
|
15381
15440
|
const matches = rows.filter((row) => matchesName(row, name));
|
|
15382
15441
|
if (matches.length === 1)
|
|
15383
|
-
return {
|
|
15384
|
-
kind: "tee",
|
|
15385
|
-
wallet: await completeTradingWallet(ctx, matches[0], appId, scope, chain2, {
|
|
15386
|
-
apiKey: key,
|
|
15387
|
-
keyPrefix
|
|
15388
|
-
}),
|
|
15389
|
-
scopes
|
|
15390
|
-
};
|
|
15442
|
+
return { kind: "tee", row: matches[0] };
|
|
15391
15443
|
if (matches.length === 0) {
|
|
15392
15444
|
if (embedded !== undefined && sameAddress(embedded, name))
|
|
15393
15445
|
return asEmbedded();
|
|
@@ -16916,8 +16968,431 @@ var init_solana_verify = __esm(() => {
|
|
|
16916
16968
|
]);
|
|
16917
16969
|
});
|
|
16918
16970
|
|
|
16919
|
-
// src/
|
|
16971
|
+
// src/trading-checks.ts
|
|
16920
16972
|
import { randomUUID } from "node:crypto";
|
|
16973
|
+
function failure2(id, error) {
|
|
16974
|
+
if (error instanceof TradingUsage)
|
|
16975
|
+
throw error;
|
|
16976
|
+
return {
|
|
16977
|
+
id,
|
|
16978
|
+
status: "fail",
|
|
16979
|
+
code: error instanceof TradingError ? error.code : "INVALID_RESPONSE",
|
|
16980
|
+
message: error instanceof Error ? describeRpcFailure(error) : "The check could not complete.",
|
|
16981
|
+
...error instanceof TradingError && error.suggestion ? { suggestion: error.suggestion } : {}
|
|
16982
|
+
};
|
|
16983
|
+
}
|
|
16984
|
+
function balanceProblem(value) {
|
|
16985
|
+
return /InsufficientFunds(?:ForFee)?|AccountNotFound|insufficient[_ ](?:funds|balance|lamports)/i.test(value instanceof Error ? `${value instanceof TradingError ? value.code : ""} ${value.message}` : String(value));
|
|
16986
|
+
}
|
|
16987
|
+
function needsBalance(id) {
|
|
16988
|
+
return {
|
|
16989
|
+
id,
|
|
16990
|
+
status: "skipped",
|
|
16991
|
+
needs: "balance",
|
|
16992
|
+
message: "This check needs balance; fund the wallet and check again."
|
|
16993
|
+
};
|
|
16994
|
+
}
|
|
16995
|
+
async function diagnosticListing(ctx, key) {
|
|
16996
|
+
try {
|
|
16997
|
+
return await listTradingWallets(ctx, key);
|
|
16998
|
+
} catch (error) {
|
|
16999
|
+
if (error instanceof exports_external.ZodError)
|
|
17000
|
+
throw new TradingError("INVALID_RESPONSE", "Trading wallet discovery returned a malformed page.");
|
|
17001
|
+
throw error;
|
|
17002
|
+
}
|
|
17003
|
+
}
|
|
17004
|
+
async function readWalletRecords(ctx, key) {
|
|
17005
|
+
const rows = [];
|
|
17006
|
+
const seen = new Set;
|
|
17007
|
+
let cursor;
|
|
17008
|
+
for (let page = 0;page < 25; page++) {
|
|
17009
|
+
const result = exports_external.object({
|
|
17010
|
+
page: exports_external.array(linkedRow),
|
|
17011
|
+
isDone: exports_external.boolean(),
|
|
17012
|
+
continueCursor: exports_external.string().nullable().optional()
|
|
17013
|
+
}).safeParse(await request(ctx, key, `/api/v1/agent/wallets?limit=100${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}`));
|
|
17014
|
+
if (!result.success)
|
|
17015
|
+
throw new TradingError("INVALID_RESPONSE", "Wallet records did not return a valid page.");
|
|
17016
|
+
rows.push(...result.data.page);
|
|
17017
|
+
if (result.data.isDone)
|
|
17018
|
+
return rows;
|
|
17019
|
+
cursor = result.data.continueCursor ?? undefined;
|
|
17020
|
+
if (!cursor || seen.has(cursor))
|
|
17021
|
+
break;
|
|
17022
|
+
seen.add(cursor);
|
|
17023
|
+
}
|
|
17024
|
+
throw new TradingError("INVALID_RESPONSE", "Wallet records did not complete.");
|
|
17025
|
+
}
|
|
17026
|
+
function walletRecordCheck(row, trading, records) {
|
|
17027
|
+
const found = records.filter((record) => record.address === row.address);
|
|
17028
|
+
const differences = {};
|
|
17029
|
+
const other = found[0];
|
|
17030
|
+
const count = trading.filter((record) => record.address === row.address).length;
|
|
17031
|
+
if (count !== 1 || found.length !== 1)
|
|
17032
|
+
differences.addressCount = { trading: count, wallets: found.length };
|
|
17033
|
+
for (const [left, right] of [
|
|
17034
|
+
["id", "_id"],
|
|
17035
|
+
["signerQuorumId", "signerQuorumId"],
|
|
17036
|
+
["chain", "chain"],
|
|
17037
|
+
["label", "label"],
|
|
17038
|
+
["address", "address"]
|
|
17039
|
+
]) {
|
|
17040
|
+
if (!other || row[left] !== other[right])
|
|
17041
|
+
differences[left] = { trading: row[left] ?? null, wallets: other?.[right] ?? null };
|
|
17042
|
+
}
|
|
17043
|
+
if (!other || !["ember-tee", "ember-hot"].includes(other.profile ?? ""))
|
|
17044
|
+
differences.profile = { trading: "TEE", wallets: other?.profile ?? null };
|
|
17045
|
+
if (other?.revokedAt !== undefined)
|
|
17046
|
+
differences.revokedAt = { trading: null, wallets: other.revokedAt };
|
|
17047
|
+
return Object.keys(differences).length ? {
|
|
17048
|
+
id: "record",
|
|
17049
|
+
status: "fail",
|
|
17050
|
+
code: "WALLET_RECORD_MISMATCH",
|
|
17051
|
+
message: `Trading and wallet records differ: ${JSON.stringify(differences)}.`,
|
|
17052
|
+
differences
|
|
17053
|
+
} : pass("record");
|
|
17054
|
+
}
|
|
17055
|
+
async function inspectReport(ctx, key, row, listing, records, payable, name) {
|
|
17056
|
+
const checks = [pass("key"), payable ? failure2("payable", payable) : pass("payable")];
|
|
17057
|
+
checks.push(listing.scopes.includes("swap:write") ? pass("scope") : failure2("scope", new TradingError("SCOPE_MISSING", "The bound key needs swap:write.")));
|
|
17058
|
+
const report = {
|
|
17059
|
+
...row ? { id: row.id, address: row.address, label: row.label } : { name },
|
|
17060
|
+
ok: false,
|
|
17061
|
+
checks
|
|
17062
|
+
};
|
|
17063
|
+
if (!row) {
|
|
17064
|
+
checks.push(...["record", "active", "signer"].map((id) => skipped(id, "payable")));
|
|
17065
|
+
return { report };
|
|
17066
|
+
}
|
|
17067
|
+
checks.push(walletRecordCheck(row, listing.rows, records));
|
|
17068
|
+
const inspected = await inspectTradingWallet(ctx, row, listing.appId, "swap:write", "solana", {
|
|
17069
|
+
apiKey: key,
|
|
17070
|
+
keyPrefix: listing.keyPrefix
|
|
17071
|
+
});
|
|
17072
|
+
const active = inspected.failures.filter((error) => error.code !== "SIGNER_UNAVAILABLE");
|
|
17073
|
+
checks.push(...active.length ? active.map((error) => failure2("active", error)) : [pass("active")]);
|
|
17074
|
+
const signer = inspected.failures.find((error) => error.code === "SIGNER_UNAVAILABLE");
|
|
17075
|
+
checks.push(signer ? failure2("signer", signer) : pass("signer"));
|
|
17076
|
+
return { report, wallet: inspected.row };
|
|
17077
|
+
}
|
|
17078
|
+
function printWalletReports(ctx, reports, swap = false) {
|
|
17079
|
+
for (const report of reports)
|
|
17080
|
+
report.ok = !report.checks.some((check) => check.status === "fail");
|
|
17081
|
+
const failed = reports.filter((report) => !report.ok).length;
|
|
17082
|
+
const summary = { total: reports.length, pass: reports.length - failed, fail: failed };
|
|
17083
|
+
if (ctx.json) {
|
|
17084
|
+
ctx.deps.stdout.write(`${JSON.stringify({
|
|
17085
|
+
success: failed === 0,
|
|
17086
|
+
dryRun: true,
|
|
17087
|
+
wallets: reports,
|
|
17088
|
+
summary,
|
|
17089
|
+
...swap ? { checks: reports[0]?.checks, quote: reports[0]?.quote } : {}
|
|
17090
|
+
})}
|
|
17091
|
+
`);
|
|
17092
|
+
} else {
|
|
17093
|
+
for (const report of reports) {
|
|
17094
|
+
const first = report.checks.find((check) => check.status === "fail");
|
|
17095
|
+
ctx.deps.stdout.write(`${safeText(report.label ?? report.name ?? report.id ?? "wallet")} ${safeText(report.address ?? "")} ${report.ok ? "PASS" : "FAIL"}${first ? ` ${first.id}/${first.code}` : ""}
|
|
17096
|
+
`);
|
|
17097
|
+
if (reports.length === 1) {
|
|
17098
|
+
for (const check of report.checks)
|
|
17099
|
+
ctx.deps.stdout.write(` ${check.id} ${check.status}${check.code ? ` ${check.code}` : ""}${check.message ? ` ${safeText(check.message)}` : ""}${check.because ? ` (${check.because})` : ""}
|
|
17100
|
+
`);
|
|
17101
|
+
if (report.quote)
|
|
17102
|
+
ctx.deps.stdout.write(` quote ${safeText(JSON.stringify(report.quote))}
|
|
17103
|
+
`);
|
|
17104
|
+
}
|
|
17105
|
+
}
|
|
17106
|
+
if (reports.length > 1)
|
|
17107
|
+
ctx.deps.stdout.write(`${summary.pass} pass, ${summary.fail} fail
|
|
17108
|
+
`);
|
|
17109
|
+
}
|
|
17110
|
+
return failed ? 1 : 0;
|
|
17111
|
+
}
|
|
17112
|
+
async function buildCheck(ctx, key, wallet, report, from, to, amount, slippage, rpcUrl, sign3) {
|
|
17113
|
+
const checks = report.checks;
|
|
17114
|
+
const blocked = checks.find((check) => check.status === "fail");
|
|
17115
|
+
if (blocked) {
|
|
17116
|
+
checks.push(skipped("quote", blocked.id), skipped("spend", "quote"));
|
|
17117
|
+
if (sign3)
|
|
17118
|
+
checks.push(skipped("sign", "spend"));
|
|
17119
|
+
return;
|
|
17120
|
+
}
|
|
17121
|
+
const solana = lazySolanaClient(ctx, rpcUrl);
|
|
17122
|
+
let transaction;
|
|
17123
|
+
let quote;
|
|
17124
|
+
let amountRaw;
|
|
17125
|
+
let minimumRaw;
|
|
17126
|
+
let feeRaw;
|
|
17127
|
+
const fromBase = baseAsset(from);
|
|
17128
|
+
const toBase = baseAsset(to);
|
|
17129
|
+
const kind = fromBase && toBase ? "swap" : "trade";
|
|
17130
|
+
try {
|
|
17131
|
+
const decimals = await decimalsFor(ctx, from, solana);
|
|
17132
|
+
const outDecimals = await decimalsFor(ctx, to, solana);
|
|
17133
|
+
amountRaw = rawAmount(amount, decimals);
|
|
17134
|
+
if (BigInt(amountRaw) > BigInt(Number.MAX_SAFE_INTEGER))
|
|
17135
|
+
throw new TradingError("INVALID_AMOUNT", "Amount exceeds the venue's exact integer range.");
|
|
17136
|
+
const payer = { type: "linked", linkedWalletId: wallet.id };
|
|
17137
|
+
const clientTradeId = `swap-${randomUUID()}`;
|
|
17138
|
+
const built = kind === "swap" ? await request(ctx, key, "/api/v1/agent/swap/build", {
|
|
17139
|
+
clientTradeId,
|
|
17140
|
+
from,
|
|
17141
|
+
to,
|
|
17142
|
+
amountRaw,
|
|
17143
|
+
maxSlippageBps: slippage,
|
|
17144
|
+
payer
|
|
17145
|
+
}) : await request(ctx, key, "/api/v1/trade/agent/build", {
|
|
17146
|
+
clientTradeId,
|
|
17147
|
+
chain: "solana",
|
|
17148
|
+
mint: fromBase ? to : from,
|
|
17149
|
+
side: fromBase ? "buy" : "sell",
|
|
17150
|
+
quoteAsset: (fromBase ?? toBase)?.toLowerCase(),
|
|
17151
|
+
amountRaw,
|
|
17152
|
+
maxSlippageBps: slippage,
|
|
17153
|
+
payer
|
|
17154
|
+
});
|
|
17155
|
+
const parsed = swapBuildSchema.safeParse(kind === "swap" ? built.payload : built);
|
|
17156
|
+
if (!parsed.success || built.job || built.status === "executed")
|
|
17157
|
+
throw new TradingError("INVALID_RESPONSE", "Candle did not return a complete built quote.");
|
|
17158
|
+
const data = parsed.data;
|
|
17159
|
+
if (kind === "swap" && (data.venue !== "jupiter" || data.recipient !== wallet.address))
|
|
17160
|
+
throw new TradingError("INVALID_RESPONSE", "A base swap must use Jupiter and return to its payer.");
|
|
17161
|
+
if (kind === "trade" && (built.chain !== "solana" || built.walletAddress !== wallet.address))
|
|
17162
|
+
throw new TradingError("INVALID_RESPONSE", "The token build does not name the requested Solana payer.");
|
|
17163
|
+
const artifacts = kind === "swap" ? data : data.artifacts;
|
|
17164
|
+
if (!artifacts)
|
|
17165
|
+
throw new TradingError("INVALID_RESPONSE", "Missing quote artifacts.");
|
|
17166
|
+
if (kind === "trade" && data.artifacts?.quoteAsset !== (fromBase ?? toBase)?.toLowerCase())
|
|
17167
|
+
throw new TradingError("PAIR_UNSUPPORTED", "The token settles in a different quote asset.");
|
|
17168
|
+
if (data.expiresAt <= ctx.deps.now())
|
|
17169
|
+
throw new TradingError("QUOTE_EXPIRED", "The quote expired; build a fresh check.");
|
|
17170
|
+
minimumRaw = !fromBase && artifacts.venue === "curve" ? (BigInt(data.minOutRaw) > BigInt(data.fee.feeRaw) ? BigInt(data.minOutRaw) - BigInt(data.fee.feeRaw) : 0n).toString() : data.minOutRaw;
|
|
17171
|
+
quote = {
|
|
17172
|
+
venue: (kind === "trade" ? data.artifacts?.quoteSource : undefined) ?? artifacts.venue,
|
|
17173
|
+
priceImpactPct: artifacts.priceImpactPct ?? null,
|
|
17174
|
+
fee: data.fee,
|
|
17175
|
+
minimumReceived: `${decimalAmount(minimumRaw, outDecimals)} ${to}`,
|
|
17176
|
+
minOutRaw: data.minOutRaw,
|
|
17177
|
+
minimumReceivedRaw: minimumRaw,
|
|
17178
|
+
tokenRisks: artifacts.tokenRisks ?? []
|
|
17179
|
+
};
|
|
17180
|
+
const tx = kind === "swap" ? data.transactionsBase64?.[0] : data.artifacts?.transactionBase64;
|
|
17181
|
+
if (!tx || kind === "swap" && data.transactionsBase64?.length !== 1)
|
|
17182
|
+
throw new TradingError("INVALID_RESPONSE", "A TEE swap must contain exactly one same-chain transaction.");
|
|
17183
|
+
transaction = tx;
|
|
17184
|
+
feeRaw = BigInt(data.fee.feeRaw);
|
|
17185
|
+
report.quote = quote;
|
|
17186
|
+
checks.push(pass("quote"));
|
|
17187
|
+
} catch (error) {
|
|
17188
|
+
checks.push(balanceProblem(error) ? needsBalance("quote") : failure2("quote", error), skipped("spend", "quote"));
|
|
17189
|
+
if (sign3)
|
|
17190
|
+
checks.push(skipped("sign", "spend"));
|
|
17191
|
+
return;
|
|
17192
|
+
}
|
|
17193
|
+
const assetOf2 = (asset) => asset === "SOL" ? "SOL" : BASES[asset]?.mint ?? asset;
|
|
17194
|
+
let reader;
|
|
17195
|
+
try {
|
|
17196
|
+
reader = await solana();
|
|
17197
|
+
const problem = await tradingRead(ctx, reader, () => solanaSpendProblem(transaction, wallet.address, reader.rpc, {
|
|
17198
|
+
kind: "swap",
|
|
17199
|
+
spend: assetOf2(from),
|
|
17200
|
+
maxSpendRaw: BigInt(amountRaw) + (kind === "trade" && fromBase ? feeRaw : 0n),
|
|
17201
|
+
receive: { asset: assetOf2(to), minRaw: BigInt(minimumRaw) }
|
|
17202
|
+
}));
|
|
17203
|
+
if (problem) {
|
|
17204
|
+
checks.push(balanceProblem(problem) ? needsBalance("spend") : failure2("spend", new TradingError("INVALID_RESPONSE", `The transaction Candle built does not match the quote (${safeText(problem)}); nothing was signed.`)));
|
|
17205
|
+
if (sign3)
|
|
17206
|
+
checks.push(skipped("sign", "spend"));
|
|
17207
|
+
return;
|
|
17208
|
+
}
|
|
17209
|
+
checks.push(pass("spend"));
|
|
17210
|
+
} catch (error) {
|
|
17211
|
+
checks.push(balanceProblem(error) ? needsBalance("spend") : failure2("spend", error));
|
|
17212
|
+
if (sign3)
|
|
17213
|
+
checks.push(skipped("sign", "spend"));
|
|
17214
|
+
return;
|
|
17215
|
+
}
|
|
17216
|
+
if (!sign3)
|
|
17217
|
+
return;
|
|
17218
|
+
try {
|
|
17219
|
+
const signed = await relaySign(ctx, key, wallet, transaction);
|
|
17220
|
+
const decoded = decodeTransaction(decodeStrictBase64(signed));
|
|
17221
|
+
const original = decodeTransaction(decodeStrictBase64(transaction));
|
|
17222
|
+
if (decoded.message.staticKeys[0] !== wallet.address || !Buffer.from(decoded.message.bytes).equals(Buffer.from(original.message.bytes)) || !ed25519.verify(decoded.signatures[0], decoded.message.bytes, base58.decode(wallet.address)))
|
|
17223
|
+
throw new TradingError("SIGNATURE_INVALID", "The relay did not return the payer's signature over the built message.");
|
|
17224
|
+
checks.push(pass("sign"));
|
|
17225
|
+
try {
|
|
17226
|
+
const simulation = await tradingRead(ctx, reader, () => reader.rpc.simulateTransaction(signed, [], { sigVerify: true }));
|
|
17227
|
+
const problem = simulation.err == null ? undefined : JSON.stringify(simulation.err);
|
|
17228
|
+
if (!problem)
|
|
17229
|
+
checks.push(pass("simulation"));
|
|
17230
|
+
else if (balanceProblem(problem))
|
|
17231
|
+
checks.push(needsBalance("simulation"));
|
|
17232
|
+
else if (/BlockhashNotFound|blockhash not found|blockhash.*expir|block.?height.*exceed/i.test(problem))
|
|
17233
|
+
checks.push({ id: "simulation", status: "skipped", message: "The blockhash expired; run a fresh check." });
|
|
17234
|
+
else
|
|
17235
|
+
checks.push({
|
|
17236
|
+
...failure2("simulation", new TradingError("SIGN_FAILED", "Signed simulation failed.")),
|
|
17237
|
+
cause: problem
|
|
17238
|
+
});
|
|
17239
|
+
} catch (error) {
|
|
17240
|
+
const problem = error instanceof Error ? error.message : String(error);
|
|
17241
|
+
if (balanceProblem(problem))
|
|
17242
|
+
checks.push(needsBalance("simulation"));
|
|
17243
|
+
else if (/BlockhashNotFound|blockhash not found|blockhash.*expir|block.?height.*exceed/i.test(problem))
|
|
17244
|
+
checks.push({ id: "simulation", status: "skipped", message: "The blockhash expired; run a fresh check." });
|
|
17245
|
+
else if (error instanceof TradingError && error.code === "RPC_RATE_LIMITED")
|
|
17246
|
+
checks.push(failure2("simulation", error));
|
|
17247
|
+
else
|
|
17248
|
+
checks.push({
|
|
17249
|
+
...failure2("simulation", new TradingError("SIGN_FAILED", "Signed simulation could not complete.")),
|
|
17250
|
+
cause: error instanceof TradingError ? error.code : "RPC_FAILED"
|
|
17251
|
+
});
|
|
17252
|
+
}
|
|
17253
|
+
} catch (error) {
|
|
17254
|
+
checks.push({
|
|
17255
|
+
...failure2("sign", new TradingError("SIGN_FAILED", "The signer did not produce a verified signature.")),
|
|
17256
|
+
cause: error instanceof TradingError ? error.code : "INVALID_RESPONSE"
|
|
17257
|
+
});
|
|
17258
|
+
}
|
|
17259
|
+
}
|
|
17260
|
+
async function teeCheck(args, ctx) {
|
|
17261
|
+
const parsed = parseArgs(args, { valueFlags: ["--label-prefix", "--max", "--rpc-url"], booleanFlags: ["--sign"] });
|
|
17262
|
+
if ("error" in parsed) {
|
|
17263
|
+
writeUsageFailure(ctx.deps, parsed.error, ctx.json);
|
|
17264
|
+
return 2;
|
|
17265
|
+
}
|
|
17266
|
+
const max = Number(parsed.values["--max"] ?? "10");
|
|
17267
|
+
if (!parsed.positionals.length && parsed.values["--label-prefix"] === undefined || !Number.isSafeInteger(max) || max < 1) {
|
|
17268
|
+
writeUsageFailure(ctx.deps, "Usage: candle tee check <address|id|label>... [--label-prefix <p>] [--sign] [--max <n>] [--rpc-url <url>]", ctx.json);
|
|
17269
|
+
return 2;
|
|
17270
|
+
}
|
|
17271
|
+
try {
|
|
17272
|
+
const key = await tradingKey(ctx);
|
|
17273
|
+
const listing = await diagnosticListing(ctx, key);
|
|
17274
|
+
const names = [...new Set(parsed.positionals)];
|
|
17275
|
+
const prefix = parsed.values["--label-prefix"];
|
|
17276
|
+
if (prefix !== undefined) {
|
|
17277
|
+
const selected2 = listing.rows.filter((row) => row.label?.startsWith(prefix));
|
|
17278
|
+
if (!selected2.length)
|
|
17279
|
+
throw new TradingError("TEE_WALLET_REQUIRED", `No TEE wallet matches this prefix. Bound to this key: ${listing.rows.map(describeWallet).join("; ") || "none"}.`);
|
|
17280
|
+
for (const row of selected2)
|
|
17281
|
+
names.push(row.id);
|
|
17282
|
+
}
|
|
17283
|
+
const selected = new Set;
|
|
17284
|
+
const targets = names.filter((name) => {
|
|
17285
|
+
const matches = listing.rows.filter((row) => matchesName(row, name));
|
|
17286
|
+
const id = matches.length === 1 ? matches[0].id : `name:${name}`;
|
|
17287
|
+
if (selected.has(id))
|
|
17288
|
+
return false;
|
|
17289
|
+
selected.add(id);
|
|
17290
|
+
return true;
|
|
17291
|
+
});
|
|
17292
|
+
const sign3 = parsed.booleans.has("--sign");
|
|
17293
|
+
if (sign3 && targets.length > max)
|
|
17294
|
+
throw new TradingError("VALIDATION_FAILED", `Signing ${targets.length} wallets exceeds the batch cap ${max}; raise it explicitly with --max.`);
|
|
17295
|
+
const records = await readWalletRecords(ctx, key);
|
|
17296
|
+
const reports = [];
|
|
17297
|
+
for (const name of targets) {
|
|
17298
|
+
const matches = listing.rows.filter((row) => matchesName(row, name));
|
|
17299
|
+
const payable = matches.length === 1 ? undefined : new TradingError("TEE_WALLET_REQUIRED", `"${safeText(name)}" matches ${matches.length} TEE wallets. Bound to this key: ${listing.rows.map(describeWallet).join("; ") || "no TEE wallets bound to this key"}.`);
|
|
17300
|
+
const { report, wallet } = await inspectReport(ctx, key, matches.length === 1 ? matches[0] : undefined, listing, records, payable, name);
|
|
17301
|
+
if (sign3 && wallet)
|
|
17302
|
+
await buildCheck(ctx, key, wallet, report, "SOL", "USDC", "0.000001", 50, parsed.values["--rpc-url"], true);
|
|
17303
|
+
else
|
|
17304
|
+
report.checks.push(skipped("sign", sign3 ? report.checks.find((check) => check.status === "fail")?.id ?? "signer" : "--sign not set"));
|
|
17305
|
+
reports.push(report);
|
|
17306
|
+
}
|
|
17307
|
+
return printWalletReports(ctx, reports);
|
|
17308
|
+
} catch (error) {
|
|
17309
|
+
return tradingFailure(ctx, error);
|
|
17310
|
+
}
|
|
17311
|
+
}
|
|
17312
|
+
async function swapCheck(args, ctx) {
|
|
17313
|
+
const parsed = parseArgs(args, {
|
|
17314
|
+
valueFlags: ["--amount", "--percent", "--wallet", "--to", "--client-trade-id", "--slippage-bps", "--rpc-url"],
|
|
17315
|
+
booleanFlags: ["--dry-run", "--yes", "--wait"]
|
|
17316
|
+
});
|
|
17317
|
+
if ("error" in parsed) {
|
|
17318
|
+
writeUsageFailure(ctx.deps, parsed.error, ctx.json);
|
|
17319
|
+
return 2;
|
|
17320
|
+
}
|
|
17321
|
+
try {
|
|
17322
|
+
if (parsed.booleans.has("--yes") || parsed.values["--client-trade-id"] !== undefined || parsed.values["--percent"] !== undefined)
|
|
17323
|
+
throw new TradingError("VALIDATION_FAILED", "--dry-run refuses --yes, --client-trade-id and --percent.");
|
|
17324
|
+
if (parsed.positionals.length !== 2 || !parsed.values["--amount"]) {
|
|
17325
|
+
writeUsageFailure(ctx.deps, "Usage: candle swap <from> <to> --amount <n> [--wallet <w>] --dry-run", ctx.json);
|
|
17326
|
+
return 2;
|
|
17327
|
+
}
|
|
17328
|
+
const slippage = Number(parsed.values["--slippage-bps"] ?? "50");
|
|
17329
|
+
if (!Number.isInteger(slippage) || slippage < 0 || slippage > 5000) {
|
|
17330
|
+
writeUsageFailure(ctx.deps, "Slippage must be an integer from 0 to 5000 bps.", ctx.json);
|
|
17331
|
+
return 2;
|
|
17332
|
+
}
|
|
17333
|
+
const from = classifyAsset(parsed.positionals[0]);
|
|
17334
|
+
const to = classifyAsset(parsed.positionals[1]);
|
|
17335
|
+
if (from.chain !== "solana" || to.chain !== "solana")
|
|
17336
|
+
throw new TradingError("VALIDATION_FAILED", "Dry run checks Solana TEE pairs; Hood and bridge pairs are unsupported.");
|
|
17337
|
+
if (parsed.values["--to"] !== undefined || parsed.booleans.has("--wait")) {
|
|
17338
|
+
writeUsageFailure(ctx.deps, "--to and --wait are for a bridge.", ctx.json);
|
|
17339
|
+
return 2;
|
|
17340
|
+
}
|
|
17341
|
+
if (from.asset === to.asset || !from.base && !to.base)
|
|
17342
|
+
throw new TradingError("PAIR_UNSUPPORTED", "Choose distinct assets with SOL, USDC or CNDL on one side.");
|
|
17343
|
+
rawAmount(parsed.values["--amount"], 18);
|
|
17344
|
+
const key = await tradingKey(ctx);
|
|
17345
|
+
const listing = await diagnosticListing(ctx, key);
|
|
17346
|
+
let row;
|
|
17347
|
+
let payable;
|
|
17348
|
+
try {
|
|
17349
|
+
const selected = await selectTradingPayer(ctx, key, parsed.values["--wallet"], listing);
|
|
17350
|
+
if (selected.kind === "embedded")
|
|
17351
|
+
throw new TradingError("VALIDATION_FAILED", "dry run checks TEE payers");
|
|
17352
|
+
row = selected.row;
|
|
17353
|
+
} catch (error) {
|
|
17354
|
+
if (error instanceof TradingError && error.code === "VALIDATION_FAILED")
|
|
17355
|
+
throw error;
|
|
17356
|
+
if (!(error instanceof TradingError))
|
|
17357
|
+
throw error;
|
|
17358
|
+
payable = error;
|
|
17359
|
+
}
|
|
17360
|
+
const records = await readWalletRecords(ctx, key);
|
|
17361
|
+
const { report, wallet } = await inspectReport(ctx, key, row, listing, records, payable, parsed.values["--wallet"]);
|
|
17362
|
+
if (wallet)
|
|
17363
|
+
await buildCheck(ctx, key, wallet, report, from.asset, to.asset, parsed.values["--amount"], slippage, parsed.values["--rpc-url"], false);
|
|
17364
|
+
else
|
|
17365
|
+
report.checks.push(skipped("quote", report.checks.find((check) => check.status === "fail")?.id ?? "signer"), skipped("spend", "quote"));
|
|
17366
|
+
return printWalletReports(ctx, [report], true);
|
|
17367
|
+
} catch (error) {
|
|
17368
|
+
return tradingFailure(ctx, error);
|
|
17369
|
+
}
|
|
17370
|
+
}
|
|
17371
|
+
var pass = (id) => ({ id, status: "pass" }), skipped = (id, because) => ({ id, status: "skipped", because }), linkedRow;
|
|
17372
|
+
var init_trading_checks = __esm(() => {
|
|
17373
|
+
init_ed25519();
|
|
17374
|
+
init_esm();
|
|
17375
|
+
init_zod();
|
|
17376
|
+
init_args();
|
|
17377
|
+
init_swap();
|
|
17378
|
+
init_render();
|
|
17379
|
+
init_solana_alt();
|
|
17380
|
+
init_solana_endpoint();
|
|
17381
|
+
init_solana_verify();
|
|
17382
|
+
init_trading();
|
|
17383
|
+
linkedRow = exports_external.object({
|
|
17384
|
+
_id: exports_external.string(),
|
|
17385
|
+
address: exports_external.string(),
|
|
17386
|
+
chain: exports_external.string(),
|
|
17387
|
+
label: exports_external.string().optional(),
|
|
17388
|
+
signerQuorumId: exports_external.string().optional(),
|
|
17389
|
+
profile: exports_external.string().optional(),
|
|
17390
|
+
revokedAt: exports_external.number().optional()
|
|
17391
|
+
});
|
|
17392
|
+
});
|
|
17393
|
+
|
|
17394
|
+
// src/commands/swap.ts
|
|
17395
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
16921
17396
|
function tradingFailure(ctx, error, id) {
|
|
16922
17397
|
if (error instanceof TradingUsage) {
|
|
16923
17398
|
writeUsageFailure(ctx.deps, error.message, ctx.json);
|
|
@@ -17151,6 +17626,8 @@ async function assertDeferredExecuteSupported(ctx, key, id) {
|
|
|
17151
17626
|
throw new TradingError("INVALID_RESPONSE", "The execute route answered for a trade that does not exist.");
|
|
17152
17627
|
}
|
|
17153
17628
|
async function swap(args, ctx) {
|
|
17629
|
+
if (args.includes("--dry-run"))
|
|
17630
|
+
return swapCheck(args, ctx);
|
|
17154
17631
|
const parsed = parseArgs(args, {
|
|
17155
17632
|
valueFlags: ["--amount", "--percent", "--wallet", "--to", "--client-trade-id", "--slippage-bps", "--rpc-url"],
|
|
17156
17633
|
booleanFlags: ["--yes", "--wait"]
|
|
@@ -17160,7 +17637,7 @@ async function swap(args, ctx) {
|
|
|
17160
17637
|
return 2;
|
|
17161
17638
|
}
|
|
17162
17639
|
const flags = parsed.values;
|
|
17163
|
-
const id = flags["--client-trade-id"] ?? `swap-${
|
|
17640
|
+
const id = flags["--client-trade-id"] ?? `swap-${randomUUID2()}`;
|
|
17164
17641
|
const slippage = Number(flags["--slippage-bps"] ?? "50");
|
|
17165
17642
|
if (!Number.isInteger(slippage) || slippage < 0 || slippage > AGENT_MAX_SLIPPAGE_BPS) {
|
|
17166
17643
|
writeUsageFailure(ctx.deps, SLIPPAGE_RANGE_MESSAGE, ctx.json);
|
|
@@ -17381,17 +17858,17 @@ async function hoodDecimals(asset, rpc) {
|
|
|
17381
17858
|
return decimals;
|
|
17382
17859
|
}
|
|
17383
17860
|
async function recordTradedToken(ctx, wallet, token) {
|
|
17384
|
-
const
|
|
17861
|
+
const skipped2 = (reason) => `Notice: the sealed EVM record was not updated for ${token} (${reason}). The leg landed. A later sweep still finds this token with --token ${token}, or with --from-block.`;
|
|
17385
17862
|
const append = ctx.deps.appendEvmRecord;
|
|
17386
17863
|
let notice2;
|
|
17387
17864
|
if (!append)
|
|
17388
|
-
notice2 =
|
|
17865
|
+
notice2 = skipped2("this CLI build has no sealed EVM record writer");
|
|
17389
17866
|
else {
|
|
17390
17867
|
try {
|
|
17391
17868
|
const outcome = await append(ctx, { kind: "token", wallet: toChecksumAddress(wallet), token });
|
|
17392
|
-
notice2 = outcome.appended ? outcome.notice :
|
|
17869
|
+
notice2 = outcome.appended ? outcome.notice : skipped2(outcome.notice);
|
|
17393
17870
|
} catch (error) {
|
|
17394
|
-
notice2 =
|
|
17871
|
+
notice2 = skipped2(error instanceof Error ? error.message : "the append failed");
|
|
17395
17872
|
}
|
|
17396
17873
|
}
|
|
17397
17874
|
if (notice2)
|
|
@@ -17839,6 +18316,7 @@ var init_swap = __esm(() => {
|
|
|
17839
18316
|
init_solana_lite();
|
|
17840
18317
|
init_solana_verify();
|
|
17841
18318
|
init_trading();
|
|
18319
|
+
init_trading_checks();
|
|
17842
18320
|
SLIPPAGE_RANGE_MESSAGE = `maxSlippageBps must be an integer in bps, between 0 and ${AGENT_MAX_SLIPPAGE_BPS}`;
|
|
17843
18321
|
settlementSchema = exports_external.object({
|
|
17844
18322
|
state: exports_external.enum(["settled", "pending", "failed", "uncertain"]),
|
|
@@ -17892,11 +18370,11 @@ function writeScreenFailure(deps, result, context, json) {
|
|
|
17892
18370
|
`)}
|
|
17893
18371
|
`);
|
|
17894
18372
|
}
|
|
17895
|
-
function writeScreenLocalFailure(deps,
|
|
18373
|
+
function writeScreenLocalFailure(deps, failure3, json) {
|
|
17896
18374
|
if (json)
|
|
17897
|
-
writeLocalFailure(deps,
|
|
18375
|
+
writeLocalFailure(deps, failure3, json);
|
|
17898
18376
|
else
|
|
17899
|
-
writeScreenMessage(deps,
|
|
18377
|
+
writeScreenMessage(deps, failure3.message, failure3.suggestion);
|
|
17900
18378
|
}
|
|
17901
18379
|
function writeScreenUsageFailure(deps, message, json, screen) {
|
|
17902
18380
|
if (json)
|
|
@@ -18000,14 +18478,14 @@ async function readSignerRoles(rpc, addresses, opts) {
|
|
|
18000
18478
|
let requests = 0;
|
|
18001
18479
|
let rateLimited = 0;
|
|
18002
18480
|
let settled = 0;
|
|
18003
|
-
let
|
|
18481
|
+
let skipped2 = 0;
|
|
18004
18482
|
let next = 0;
|
|
18005
18483
|
let active = 0;
|
|
18006
18484
|
const progress = () => {
|
|
18007
18485
|
opts.onProgress?.({
|
|
18008
|
-
completed: settled +
|
|
18486
|
+
completed: settled + skipped2,
|
|
18009
18487
|
settled,
|
|
18010
|
-
skipped,
|
|
18488
|
+
skipped: skipped2,
|
|
18011
18489
|
total: planned,
|
|
18012
18490
|
refusedGroups: failed.size,
|
|
18013
18491
|
elapsedMs: opts.now() - startedAt
|
|
@@ -18152,7 +18630,7 @@ async function readSignerRoles(rpc, addresses, opts) {
|
|
|
18152
18630
|
while (active < cap && next < tasks.length) {
|
|
18153
18631
|
const task = tasks[next++];
|
|
18154
18632
|
if (failed.has(task.group.id)) {
|
|
18155
|
-
|
|
18633
|
+
skipped2 += 1;
|
|
18156
18634
|
skippedAny = true;
|
|
18157
18635
|
continue;
|
|
18158
18636
|
}
|
|
@@ -28989,13 +29467,13 @@ __export(exports_vault_support, {
|
|
|
28989
29467
|
runVaultCommand: () => runVaultCommand,
|
|
28990
29468
|
requireVaultRaw: () => requireVaultRaw,
|
|
28991
29469
|
requireTty: () => requireTty,
|
|
28992
|
-
requirePromptStreams: () => requirePromptStreams,
|
|
28993
29470
|
refusePromptsWithoutTerminal: () => refusePromptsWithoutTerminal,
|
|
28994
29471
|
refuseEnvPassphrase: () => refuseEnvPassphrase,
|
|
28995
29472
|
openWithTypedPassphrase: () => openWithTypedPassphrase,
|
|
28996
29473
|
nonDefaultVaultFooter: () => nonDefaultVaultFooter,
|
|
28997
29474
|
missingVault: () => missingVault,
|
|
28998
29475
|
menuSafeText: () => menuSafeText,
|
|
29476
|
+
isNoTerminalRefusal: () => isNoTerminalRefusal,
|
|
28999
29477
|
groupAddress: () => groupAddress,
|
|
29000
29478
|
findExternalEntry: () => findExternalEntry,
|
|
29001
29479
|
factorMenu: () => factorMenu,
|
|
@@ -29034,27 +29512,28 @@ async function askForOwnPassphrase(ctx, promptText) {
|
|
|
29034
29512
|
function requireTty(ctx, what) {
|
|
29035
29513
|
if (ctx.deps.isTTY.stdin && ctx.deps.isTTY.stdout)
|
|
29036
29514
|
return true;
|
|
29037
|
-
writeVaultFailure(ctx, new VaultError("
|
|
29515
|
+
writeVaultFailure(ctx, new VaultError("VAULT_TERMINAL_REQUIRED", `${what} needs a terminal: this CLI reads a vault passphrase or a security key PIN from a hidden prompt and from nowhere else.`, {
|
|
29038
29516
|
suggestion: "There is no environment variable and no flag that supplies one."
|
|
29039
29517
|
}));
|
|
29040
29518
|
return false;
|
|
29041
29519
|
}
|
|
29042
|
-
function requirePromptStreams(ctx, what) {
|
|
29043
|
-
if (ctx.deps.isTTY.stdin && ctx.deps.isTTY.stderr)
|
|
29044
|
-
return true;
|
|
29045
|
-
writeVaultFailure(ctx, new VaultError("VAULT_UNLOCK_FAILED", `${what} needs a terminal for the passphrase prompt: standard input and standard error must both be a terminal. Standard output may be redirected; that is where the listing goes.`));
|
|
29046
|
-
return false;
|
|
29047
|
-
}
|
|
29048
29520
|
function refusePromptsWithoutTerminal(ctx, what) {
|
|
29049
29521
|
if (ctx.deps.isTTY.stdin && ctx.deps.isTTY.stderr)
|
|
29050
29522
|
return ctx;
|
|
29051
29523
|
const refuse2 = async () => {
|
|
29052
|
-
throw new VaultError("
|
|
29524
|
+
throw tagNoTerminal(new VaultError("VAULT_TERMINAL_REQUIRED", `${what} needs a terminal here: this unlock reads a vault passphrase, a security key PIN or a choice from a prompt, and standard input and standard error must both be a terminal for that. Standard output may be redirected.`, {
|
|
29053
29525
|
suggestion: "Run it from a terminal, or pass --factor touch-id or --factor passkey to unlock with a factor that reads no prompt. No environment variable or flag supplies a passphrase or PIN."
|
|
29054
|
-
});
|
|
29526
|
+
}));
|
|
29055
29527
|
};
|
|
29056
29528
|
return { ...ctx, deps: { ...ctx.deps, promptSecret: refuse2, promptLine: refuse2 } };
|
|
29057
29529
|
}
|
|
29530
|
+
function tagNoTerminal(error) {
|
|
29531
|
+
noTerminalRefusals.add(error);
|
|
29532
|
+
return error;
|
|
29533
|
+
}
|
|
29534
|
+
function isNoTerminalRefusal(error) {
|
|
29535
|
+
return error instanceof VaultError && noTerminalRefusals.has(error);
|
|
29536
|
+
}
|
|
29058
29537
|
function vaultPathFor(ctx, parsed) {
|
|
29059
29538
|
const flag = parsed.values["--keystore"];
|
|
29060
29539
|
if (flag !== undefined)
|
|
@@ -29674,7 +30153,7 @@ function takeRepeatedFlag(args, flag) {
|
|
|
29674
30153
|
}
|
|
29675
30154
|
return { values, rest };
|
|
29676
30155
|
}
|
|
29677
|
-
var PASSPHRASE_ONLY_PREFIX = "Passphrase only: ";
|
|
30156
|
+
var noTerminalRefusals, PASSPHRASE_ONLY_PREFIX = "Passphrase only: ";
|
|
29678
30157
|
var init_vault_support = __esm(() => {
|
|
29679
30158
|
init_args();
|
|
29680
30159
|
init_presentation();
|
|
@@ -29690,6 +30169,7 @@ var init_vault_support = __esm(() => {
|
|
|
29690
30169
|
init_platform();
|
|
29691
30170
|
init_sidecar();
|
|
29692
30171
|
init_store();
|
|
30172
|
+
noTerminalRefusals = new WeakSet;
|
|
29693
30173
|
});
|
|
29694
30174
|
|
|
29695
30175
|
// src/vault/ed25519.ts
|
|
@@ -29825,9 +30305,9 @@ async function reconcileGrant(ctx, entry, opts = {}) {
|
|
|
29825
30305
|
if (!stranded.complete) {
|
|
29826
30306
|
return { kind: "unreadable", reason: stranded.incompleteReason ?? "the stranded-import read was incomplete" };
|
|
29827
30307
|
}
|
|
29828
|
-
const
|
|
29829
|
-
if (
|
|
29830
|
-
return { kind: "strand-final", account: wallets.account, failure:
|
|
30308
|
+
const failure3 = stranded.failures.find((candidate) => sameWalletAddress(candidate.address, entry.address));
|
|
30309
|
+
if (failure3?.stage === "convex_link") {
|
|
30310
|
+
return { kind: "strand-final", account: wallets.account, failure: failure3 };
|
|
29831
30311
|
}
|
|
29832
30312
|
return { kind: "unresolved", account: wallets.account };
|
|
29833
30313
|
}
|
|
@@ -33959,23 +34439,23 @@ async function walletsImport(args, ctx) {
|
|
|
33959
34439
|
deps
|
|
33960
34440
|
});
|
|
33961
34441
|
if (!flow.ok) {
|
|
33962
|
-
const
|
|
33963
|
-
if (
|
|
33964
|
-
writeScreenFailure(deps,
|
|
34442
|
+
const failure3 = flow.failure;
|
|
34443
|
+
if (failure3.kind === "api") {
|
|
34444
|
+
writeScreenFailure(deps, failure3.response, { apiUrl, authType: "key" }, json);
|
|
33965
34445
|
return 1;
|
|
33966
34446
|
}
|
|
33967
|
-
if (
|
|
34447
|
+
if (failure3.kind === "signer-store") {
|
|
33968
34448
|
writeScreenLocalFailure(deps, {
|
|
33969
34449
|
code: "SIGNER_STORE_FAILED",
|
|
33970
|
-
message: `Could not store the new signer in the ${deps.backend} store: ${
|
|
34450
|
+
message: `Could not store the new signer in the ${deps.backend} store: ${failure3.error instanceof Error ? failure3.error.message : failure3.error}`,
|
|
33971
34451
|
suggestion: "Nothing was imported. Unlock the store (or fix the error above) and run the command again."
|
|
33972
34452
|
}, json);
|
|
33973
34453
|
return 1;
|
|
33974
34454
|
}
|
|
33975
34455
|
writeScreenLocalFailure(deps, {
|
|
33976
34456
|
code: "SIGNER_COMMIT_FAILED",
|
|
33977
|
-
message: `Wallet ${
|
|
33978
|
-
suggestion: `The signer is not lost: it is in the ${deps.backend} store under "${
|
|
34457
|
+
message: `Wallet ${failure3.walletId} was imported, but its signer could not be stored under the wallet's own ` + `ref: ${failure3.error instanceof Error ? failure3.error.message : failure3.error}`,
|
|
34458
|
+
suggestion: `The signer is not lost: it is in the ${deps.backend} store under "${failure3.pendingRef}". Copy it to ` + `"${walletSignerRef(failure3.walletId)}", or revoke the wallet with: candle wallets revoke ${failure3.walletId}`
|
|
33979
34459
|
}, json);
|
|
33980
34460
|
return 1;
|
|
33981
34461
|
}
|
|
@@ -35451,8 +35931,8 @@ function releaseActiveTee(active) {
|
|
|
35451
35931
|
if (active.source === "vault")
|
|
35452
35932
|
releaseResolvedTee(active.resolved);
|
|
35453
35933
|
}
|
|
35454
|
-
function writeCommitFailure(ctx,
|
|
35455
|
-
writeLocalFailure(ctx.deps, { code:
|
|
35934
|
+
function writeCommitFailure(ctx, failure3, consequence) {
|
|
35935
|
+
writeLocalFailure(ctx.deps, { code: failure3.code, message: failure3.message, suggestion: consequence }, ctx.json);
|
|
35456
35936
|
}
|
|
35457
35937
|
async function openExistingTeeStore(ctx, parsed) {
|
|
35458
35938
|
const { deps, json } = ctx;
|
|
@@ -35753,13 +36233,13 @@ async function teeEnable(args, ctx) {
|
|
|
35753
36233
|
vaultDestination: vault
|
|
35754
36234
|
});
|
|
35755
36235
|
if (!flow.ok) {
|
|
35756
|
-
const
|
|
35757
|
-
if (
|
|
35758
|
-
writeFailure(deps,
|
|
36236
|
+
const failure3 = flow.failure;
|
|
36237
|
+
if (failure3.kind === "api")
|
|
36238
|
+
writeFailure(deps, failure3.response, { apiUrl, authType: "key" }, json);
|
|
35759
36239
|
else {
|
|
35760
36240
|
writeLocalFailure(deps, {
|
|
35761
|
-
code:
|
|
35762
|
-
message: `${address}: ${
|
|
36241
|
+
code: failure3.kind === "signer-store" ? "SIGNER_STORE_FAILED" : "SIGNER_COMMIT_FAILED",
|
|
36242
|
+
message: `${address}: ${failure3.error instanceof Error ? failure3.error.message : failure3.error}`,
|
|
35763
36243
|
suggestion: "The key is still sealed locally. Fix the error above and run enable again."
|
|
35764
36244
|
}, json);
|
|
35765
36245
|
}
|
|
@@ -38566,7 +39046,7 @@ var init_token_clusters = __esm(() => {
|
|
|
38566
39046
|
});
|
|
38567
39047
|
|
|
38568
39048
|
// ../mcp/src/version.ts
|
|
38569
|
-
var SERVER_VERSION = "0.10.
|
|
39049
|
+
var SERVER_VERSION = "0.10.30";
|
|
38570
39050
|
|
|
38571
39051
|
// ../mcp/src/update-notice.ts
|
|
38572
39052
|
function newer(a, b) {
|
|
@@ -40298,7 +40778,7 @@ var init_market_disclosure = __esm(() => {
|
|
|
40298
40778
|
});
|
|
40299
40779
|
|
|
40300
40780
|
// ../mcp/src/orchestrate.ts
|
|
40301
|
-
import { randomUUID as
|
|
40781
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
40302
40782
|
function requireApiKey(cfg) {
|
|
40303
40783
|
if (!cfg.apiKey) {
|
|
40304
40784
|
throw new Error("CANDLE_AGENT_API_KEY is required for this tool. Set it in the environment or MCP client config.");
|
|
@@ -40411,7 +40891,7 @@ async function readPaperInventory(cfg, doFetch, extra) {
|
|
|
40411
40891
|
}
|
|
40412
40892
|
async function executeTrade(args, cfg, doFetch) {
|
|
40413
40893
|
const apiKey = requireApiKey(cfg);
|
|
40414
|
-
const clientTradeId = args.clientTradeId ??
|
|
40894
|
+
const clientTradeId = args.clientTradeId ?? randomUUID4();
|
|
40415
40895
|
if (args.amount !== undefined && args.percent !== undefined) {
|
|
40416
40896
|
return errText("pass exactly one of amount or percent, not both", { clientTradeId });
|
|
40417
40897
|
}
|
|
@@ -40424,6 +40904,12 @@ async function executeTrade(args, cfg, doFetch) {
|
|
|
40424
40904
|
let amountRaw;
|
|
40425
40905
|
let resolved = {};
|
|
40426
40906
|
let paper = isPaperFlag(args.paper);
|
|
40907
|
+
const deferExecution = isPaperFlag(args.deferExecution);
|
|
40908
|
+
if (deferExecution && paper) {
|
|
40909
|
+
return errText("deferExecution cannot be combined with paper; a paper trade records a quote and never executes", {
|
|
40910
|
+
clientTradeId
|
|
40911
|
+
});
|
|
40912
|
+
}
|
|
40427
40913
|
try {
|
|
40428
40914
|
if (args.side === "buy") {
|
|
40429
40915
|
const amount = args.amount;
|
|
@@ -40532,6 +41018,9 @@ async function executeTrade(args, cfg, doFetch) {
|
|
|
40532
41018
|
} catch (err) {
|
|
40533
41019
|
return errText(err instanceof Error ? err.message : String(err), { clientTradeId });
|
|
40534
41020
|
}
|
|
41021
|
+
if (deferExecution && paper) {
|
|
41022
|
+
return errText("deferExecution needs a live holding, and this sell resolved to the paper book (the wallet holds none of the mint); a paper trade never executes", { clientTradeId });
|
|
41023
|
+
}
|
|
40535
41024
|
const posted = await postJson(`${base(cfg)}/api/v1/trade/agent/build`, apiKey, {
|
|
40536
41025
|
clientTradeId,
|
|
40537
41026
|
mint: args.mint,
|
|
@@ -40541,7 +41030,8 @@ async function executeTrade(args, cfg, doFetch) {
|
|
|
40541
41030
|
payer: { type: "main" },
|
|
40542
41031
|
...args.quoteAsset !== undefined ? { quoteAsset: args.quoteAsset } : {},
|
|
40543
41032
|
...args.maxSlippageBps !== undefined ? { maxSlippageBps: args.maxSlippageBps } : {},
|
|
40544
|
-
...paper ? { paper: true } : {}
|
|
41033
|
+
...paper ? { paper: true } : {},
|
|
41034
|
+
...deferExecution ? { deferExecution: true } : {}
|
|
40545
41035
|
}, doFetch);
|
|
40546
41036
|
if ("thrown" in posted)
|
|
40547
41037
|
return transportError("clientTradeId", clientTradeId, posted.thrown);
|
|
@@ -40550,7 +41040,7 @@ async function executeTrade(args, cfg, doFetch) {
|
|
|
40550
41040
|
}
|
|
40551
41041
|
async function executeLaunchAndSeed(args, cfg, doFetch, imageDir, imageFs, opts = {}) {
|
|
40552
41042
|
const apiKey = requireApiKey(cfg);
|
|
40553
|
-
const clientLaunchId = args.clientLaunchId ??
|
|
41043
|
+
const clientLaunchId = args.clientLaunchId ?? randomUUID4();
|
|
40554
41044
|
const socialsProblem = socialsRefusal(args.socials);
|
|
40555
41045
|
if (socialsProblem)
|
|
40556
41046
|
return { text: imageRefusalBody(socialsProblem, { clientLaunchId }), isError: true };
|
|
@@ -40779,7 +41269,26 @@ async function resolveToken(args, cfg, doFetch) {
|
|
|
40779
41269
|
return read.err;
|
|
40780
41270
|
return { text: JSON.stringify({ success: true, chain: chain2, mint: args.mint, market: read.market }, null, 2) };
|
|
40781
41271
|
}
|
|
40782
|
-
|
|
41272
|
+
function teePayers(body) {
|
|
41273
|
+
const page = body;
|
|
41274
|
+
if (!Array.isArray(page?.page))
|
|
41275
|
+
return;
|
|
41276
|
+
const paused = page.paused === true;
|
|
41277
|
+
const scopes = Array.isArray(page.scopes) ? page.scopes.filter((s) => typeof s === "string") : [];
|
|
41278
|
+
const spendAuthorized = TEE_SPEND_SCOPES.some((scope) => scopes.includes(scope));
|
|
41279
|
+
const wallets2 = page.page.map((row2) => ({
|
|
41280
|
+
id: String(row2.id),
|
|
41281
|
+
address: String(row2.address),
|
|
41282
|
+
chain: String(row2.chain),
|
|
41283
|
+
...typeof row2.label === "string" ? { label: row2.label } : {},
|
|
41284
|
+
active: row2.active === true,
|
|
41285
|
+
allowLaunch: row2.allowLaunch === true,
|
|
41286
|
+
spendable: row2.active === true && !paused && spendAuthorized
|
|
41287
|
+
}));
|
|
41288
|
+
return { paused, scopes, wallets: wallets2, complete: page.isDone === true };
|
|
41289
|
+
}
|
|
41290
|
+
async function executionStatus(cfg, doFetch, options = {}) {
|
|
41291
|
+
const readTee = options.teeWallets !== false;
|
|
40783
41292
|
const apiKey = requireApiKey(cfg);
|
|
40784
41293
|
const get = async (path) => {
|
|
40785
41294
|
const res = await doFetch(`${base(cfg)}${path}`, { method: "GET", headers: headers(apiKey) });
|
|
@@ -40793,14 +41302,17 @@ async function executionStatus(cfg, doFetch) {
|
|
|
40793
41302
|
}
|
|
40794
41303
|
return { ok: res.ok, status: res.status, body };
|
|
40795
41304
|
};
|
|
40796
|
-
const [wallets2, tier, limits, risk] = await Promise.all([
|
|
41305
|
+
const [wallets2, trading, tier, limits, risk] = await Promise.all([
|
|
40797
41306
|
get("/api/v1/agent/wallets/embedded"),
|
|
41307
|
+
readTee ? get("/api/v1/agent/wallets/trading") : Promise.resolve(undefined),
|
|
40798
41308
|
get("/api/v1/agent/tier"),
|
|
40799
41309
|
get("/api/v1/agent/keys/self/limits"),
|
|
40800
41310
|
get("/api/v1/agent/keys/self/risk")
|
|
40801
41311
|
]);
|
|
41312
|
+
const tee = trading?.ok ? teePayers(trading.body) : undefined;
|
|
40802
41313
|
const unreadable2 = [
|
|
40803
41314
|
["wallets", wallets2],
|
|
41315
|
+
...trading ? [["tee", tee ? trading : { ok: false }]] : [],
|
|
40804
41316
|
["tier", tier],
|
|
40805
41317
|
["limits", limits],
|
|
40806
41318
|
["risk", risk]
|
|
@@ -40817,6 +41329,11 @@ async function executionStatus(cfg, doFetch) {
|
|
|
40817
41329
|
updateAvailable: updateAvailable() ?? undefined,
|
|
40818
41330
|
unreadable: unreadable2.length > 0 ? unreadable2.map(([name]) => name) : undefined,
|
|
40819
41331
|
wallets: wallets2.body,
|
|
41332
|
+
tee: tee?.wallets,
|
|
41333
|
+
teeScopes: tee ? tee.scopes : undefined,
|
|
41334
|
+
teePaused: tee ? tee.paused : undefined,
|
|
41335
|
+
teeComplete: tee ? tee.complete : undefined,
|
|
41336
|
+
teeError: trading && !tee ? trading.body : undefined,
|
|
40820
41337
|
tier: tier.body,
|
|
40821
41338
|
limits: limits.body,
|
|
40822
41339
|
risk: risk.body
|
|
@@ -40824,7 +41341,7 @@ async function executionStatus(cfg, doFetch) {
|
|
|
40824
41341
|
...unreadable2.length > 0 ? { isError: true } : {}
|
|
40825
41342
|
};
|
|
40826
41343
|
}
|
|
40827
|
-
var SOLANA_SEED_CEILINGS, MAIN_PAYER, SWEEP_BASE_ASSETS, STOP_CLEARED_BY = "An account owner reviews the profile and resumes it from the agent console; an API key cannot resume it.";
|
|
41344
|
+
var SOLANA_SEED_CEILINGS, MAIN_PAYER, SWEEP_BASE_ASSETS, TEE_SPEND_SCOPES, STOP_CLEARED_BY = "An account owner reviews the profile and resumes it from the agent console; an API key cannot resume it.";
|
|
40828
41345
|
var init_orchestrate = __esm(() => {
|
|
40829
41346
|
init_convert();
|
|
40830
41347
|
init_image();
|
|
@@ -40840,6 +41357,7 @@ var init_orchestrate = __esm(() => {
|
|
|
40840
41357
|
solana: ["USDC", "CNDL", "SOL"],
|
|
40841
41358
|
hood: ["USDG", "ETH"]
|
|
40842
41359
|
};
|
|
41360
|
+
TEE_SPEND_SCOPES = ["swap:write", "transfer:write", "launch:write"];
|
|
40843
41361
|
});
|
|
40844
41362
|
|
|
40845
41363
|
// ../mcp/src/hyperliquid.ts
|
|
@@ -44560,6 +45078,9 @@ function planTableRows(table) {
|
|
|
44560
45078
|
row2("Uploads per minute", (e) => count(e.limits.uploadsPerMin)),
|
|
44561
45079
|
row2("Linked wallets", (e) => count(e.limits.linkedWallets))
|
|
44562
45080
|
];
|
|
45081
|
+
if (plans.some((e) => typeof e.limits.labReplaysPerDay === "number")) {
|
|
45082
|
+
rows.push(row2("Strategy lab replays", (e) => typeof e.limits.labReplaysPerDay === "number" ? `${count(e.limits.labReplaysPerDay)} a day` : "-"));
|
|
45083
|
+
}
|
|
44563
45084
|
const known = Object.keys(PLAN_CAPABILITY_LABELS);
|
|
44564
45085
|
const served = new Set;
|
|
44565
45086
|
for (const entry of plans)
|
|
@@ -44604,13 +45125,14 @@ var init_plans = __esm(() => {
|
|
|
44604
45125
|
createLinkedWallets: "Create linked wallets",
|
|
44605
45126
|
importLinkedWallets: "Import linked wallets",
|
|
44606
45127
|
limitOrders: "Limit orders",
|
|
44607
|
-
quant: "Quant deep analyses (Max)"
|
|
45128
|
+
quant: "Quant deep analyses (Max)",
|
|
45129
|
+
strategyLab: "Strategy lab (saved strategies stay Max)"
|
|
44608
45130
|
};
|
|
44609
45131
|
PLAN_LABELS = { free: "Free", believer: "Believer", pro: "Pro", max: "Max" };
|
|
44610
45132
|
});
|
|
44611
45133
|
|
|
44612
45134
|
// ../mcp/src/token-metadata.ts
|
|
44613
|
-
import { randomUUID as
|
|
45135
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
44614
45136
|
function editSocialsSchema() {
|
|
44615
45137
|
const shape = {};
|
|
44616
45138
|
for (const key of SOCIAL_KEYS) {
|
|
@@ -44661,7 +45183,7 @@ function tokenEditBody(args) {
|
|
|
44661
45183
|
return { body };
|
|
44662
45184
|
}
|
|
44663
45185
|
function tokenEditRequest(base2, apiKey, args, body) {
|
|
44664
|
-
const key = typeof args.idempotencyKey === "string" && args.idempotencyKey ? args.idempotencyKey :
|
|
45186
|
+
const key = typeof args.idempotencyKey === "string" && args.idempotencyKey ? args.idempotencyKey : randomUUID5();
|
|
44665
45187
|
return {
|
|
44666
45188
|
url: `${base2}/api/v1/tokens/${encodeURIComponent(String(args.chain))}/${encodeURIComponent(String(args.mint))}/metadata`,
|
|
44667
45189
|
init: {
|
|
@@ -44889,7 +45411,7 @@ __export(exports_tools, {
|
|
|
44889
45411
|
OPERATION_NOT_FOUND_RULE: () => OPERATION_NOT_FOUND_RULE,
|
|
44890
45412
|
LAB_TOOL_NAMES: () => LAB_TOOL_NAMES
|
|
44891
45413
|
});
|
|
44892
|
-
import { randomUUID as
|
|
45414
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
44893
45415
|
function resolveToolAllowlist(env) {
|
|
44894
45416
|
const raw = env.CANDLE_MCP_TOOLS?.trim();
|
|
44895
45417
|
if (!raw)
|
|
@@ -44990,6 +45512,26 @@ function buildRequest(name, args, cfg) {
|
|
|
44990
45512
|
init: { method: "GET", headers: jsonHeaders() }
|
|
44991
45513
|
};
|
|
44992
45514
|
}
|
|
45515
|
+
case "candle_quote": {
|
|
45516
|
+
const apiKey = requireApiKey2(cfg);
|
|
45517
|
+
const { chain: chain2, mint, side, amountRaw, quoteAsset, poolKey, includeRoundTrip } = args;
|
|
45518
|
+
return {
|
|
45519
|
+
url: `${base2}/api/v1/trade/agent/quote`,
|
|
45520
|
+
init: {
|
|
45521
|
+
method: "POST",
|
|
45522
|
+
headers: jsonHeaders(apiKey),
|
|
45523
|
+
body: JSON.stringify({
|
|
45524
|
+
chain: chain2,
|
|
45525
|
+
mint,
|
|
45526
|
+
side,
|
|
45527
|
+
amountRaw,
|
|
45528
|
+
...quoteAsset !== undefined && quoteAsset !== "" ? { quoteAsset } : {},
|
|
45529
|
+
...poolKey !== undefined ? { poolKey } : {},
|
|
45530
|
+
...includeRoundTrip !== undefined ? { includeRoundTrip } : {}
|
|
45531
|
+
})
|
|
45532
|
+
}
|
|
45533
|
+
};
|
|
45534
|
+
}
|
|
44993
45535
|
case "candle_get_price_history": {
|
|
44994
45536
|
const { chain: chain2, mint, interval, limit, to } = args;
|
|
44995
45537
|
const query2 = new URLSearchParams;
|
|
@@ -45166,6 +45708,13 @@ function buildRequest(name, args, cfg) {
|
|
|
45166
45708
|
const path = kind === "launch" ? `/api/v1/launch/headless/jobs/${id}` : kind === "swap" ? `/api/v1/agent/swap/by-client-id/${id}` : `/api/v1/trade/agent/jobs/${id}`;
|
|
45167
45709
|
return { url: `${base2}${path}`, init: { method: "GET", headers: jsonHeaders(apiKey) } };
|
|
45168
45710
|
}
|
|
45711
|
+
case "candle_trade_execute": {
|
|
45712
|
+
const { clientTradeId } = args;
|
|
45713
|
+
return {
|
|
45714
|
+
url: `${base2}/api/v1/trade/agent/execute`,
|
|
45715
|
+
init: { method: "POST", headers: jsonHeaders(requireApiKey2(cfg)), body: JSON.stringify({ clientTradeId }) }
|
|
45716
|
+
};
|
|
45717
|
+
}
|
|
45169
45718
|
case "candle_get_staking": {
|
|
45170
45719
|
return { url: `${base2}/api/v1/agent/staking`, init: { method: "GET", headers: jsonHeaders(requireApiKey2(cfg)) } };
|
|
45171
45720
|
}
|
|
@@ -45220,6 +45769,28 @@ function buildRequest(name, args, cfg) {
|
|
|
45220
45769
|
init: { method: "GET", headers: jsonHeaders(apiKey) }
|
|
45221
45770
|
};
|
|
45222
45771
|
}
|
|
45772
|
+
case "candle_get_trading_wallets": {
|
|
45773
|
+
const apiKey = requireApiKey2(cfg);
|
|
45774
|
+
const { cursor } = args;
|
|
45775
|
+
const query2 = cursor === undefined ? "" : `?cursor=${encodeURIComponent(cursor)}`;
|
|
45776
|
+
return {
|
|
45777
|
+
url: `${base2}/api/v1/agent/wallets/trading${query2}`,
|
|
45778
|
+
init: { method: "GET", headers: jsonHeaders(apiKey) }
|
|
45779
|
+
};
|
|
45780
|
+
}
|
|
45781
|
+
case "candle_get_spendable_balances": {
|
|
45782
|
+
const apiKey = requireApiKey2(cfg);
|
|
45783
|
+
const { cursor, mints } = args;
|
|
45784
|
+
const params = new URLSearchParams;
|
|
45785
|
+
if (cursor !== undefined)
|
|
45786
|
+
params.set("cursor", cursor);
|
|
45787
|
+
if (mints !== undefined && mints.length > 0)
|
|
45788
|
+
params.set("mints", mints.join(","));
|
|
45789
|
+
return {
|
|
45790
|
+
url: `${base2}/api/v1/agent/wallets/self/balances${params.size ? `?${params}` : ""}`,
|
|
45791
|
+
init: { method: "GET", headers: jsonHeaders(apiKey) }
|
|
45792
|
+
};
|
|
45793
|
+
}
|
|
45223
45794
|
case "candle_get_profile_wallets": {
|
|
45224
45795
|
const apiKey = requireApiKey2(cfg);
|
|
45225
45796
|
const { keyPrefix } = args;
|
|
@@ -45350,7 +45921,7 @@ async function getOperation(args, cfg, doFetch) {
|
|
|
45350
45921
|
};
|
|
45351
45922
|
}
|
|
45352
45923
|
async function swap2(args, cfg, doFetch) {
|
|
45353
|
-
const clientSwapId = typeof args.clientSwapId === "string" && args.clientSwapId.length > 0 ? args.clientSwapId :
|
|
45924
|
+
const clientSwapId = typeof args.clientSwapId === "string" && args.clientSwapId.length > 0 ? args.clientSwapId : randomUUID6();
|
|
45354
45925
|
const { url, init } = buildRequest("candle_swap", { ...args, clientSwapId }, cfg);
|
|
45355
45926
|
let ok;
|
|
45356
45927
|
let body;
|
|
@@ -45525,6 +46096,12 @@ function registerToolSubset(server, options) {
|
|
|
45525
46096
|
description: "Read Candle and indexed external markets, including indexed-but-not-routable tokens. No key needed. " + EXTERNAL_MARKET_DISCLOSURE + " " + "Read candleLaunched, launchpad, venue and trade.routable; jupiterOk and discovery flags are distinct. " + "Routability is stored eligibility, not a quote or permission. General quotes use POST /api/v1/trade/agent/quote. " + "Curve quotes and lifecycle describe Candle launches. MARKET_NOT_FOUND is a legacy code: read " + "error.routing.reason, error.discovery and sibling error.retryable. A curve-only 404 does not mean untradeable.",
|
|
45526
46097
|
inputSchema: getMarketShape
|
|
45527
46098
|
}, async (args) => callAndRelay("candle_get_market", args, getConfig(), doFetch));
|
|
46099
|
+
register("candle_quote", {
|
|
46100
|
+
title: "Quote a trade",
|
|
46101
|
+
description: "Price a buy or sell before making it. Moves nothing. Needs an API key with the swap:write scope: a read-only key (account:read) is refused, so use candle_get_market for state instead. " + "Returns amountOut (base units), venue, quoteAsset, quoteDecimals and quotedAt; Hood answers also carry fee and price impact. " + "route is present only on some answers, so do not assume it. A quote is a snapshot (Hood quotes are cached for 10 seconds), not a promise of the fill. " + "Limited to 600 quotes a minute per key, and counted against the key's own limit. Pass amountRaw in base units; candle_trade and candle_swap take decimals.",
|
|
46102
|
+
inputSchema: quoteShape,
|
|
46103
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
46104
|
+
}, async (args) => callAndRelay("candle_quote", args, getConfig(), doFetch));
|
|
45528
46105
|
register("candle_token_forensics", {
|
|
45529
46106
|
title: "Token forensics",
|
|
45530
46107
|
description: "Gate a buy before making it: who launched it (resolved on-chain; pump.fun's shared updateAuthority is never the developer; when no developer is on chain, deployer.attribution names the launchpad or issuer instead, e.g. launched via stonk.fun or issued by xStocks), their went-to-zero rate and last coins, who bought in the deploy window (the creator's own wallets are marked disclosed; strangers in the same slot are the bundle signal), same-funder insider share, same-funder deployer cluster, and safety.summary with six sourced flags (mintAuthority, freezeAuthority, tokenExtensions, lpLock, sellability, liquidityDrain). Refuse an unprompted buy when flagged; incomplete or unknown is not clearance. deployer.priorLaunches is counted (pump.fun curves for a curve creator; record.count says the source); deployer.launcher is the wallet that sent the launch, often a launch tool's signer, never the developer; launch.deployerLaunches is that wallet's inclusive informational count, never a warning. Every measurement carries a coverage note -- 'unavailable' is not 'clean'. No key needed.\n\nMARKET_NOT_FOUND means Candle has no market for that token and this could not run. That is also not 'clean': report that you could not check it, rather than reporting the token as safe. That refusal now carries error.coverage -- covered:false, a reason ('external_launchpad' when the token launched somewhere else, 'unknown_mint' when nobody has indexed it), the launchpad when known, and every check that consequently did not run. Read it instead of guessing. Most of the feed, and any Solana mint Jupiter's index knows, now answers with a partial report instead. On Hood, a token Candle did not launch names its launch account (deployer.method cvc_launch_account) for pons.family and pools.trade launches, with no record of earlier coins; other Hood launchpads return no developer.",
|
|
@@ -45715,6 +46292,16 @@ function registerToolSubset(server, options) {
|
|
|
45715
46292
|
description: "The account's EMBEDDED wallets, one per chain, with their delegation state. These are " + "the wallets candle_trade, candle_swap and candle_transfer spend from, so this is how an " + "agent finds its own funding addresses. Reads only; moves nothing. Not the same as the " + "account's LINKED wallets, which are the owner's own wallets and are not spent from here. " + "Balances are not included: read a specific one with the market and balance endpoints.",
|
|
45716
46293
|
inputSchema: {}
|
|
45717
46294
|
}, async () => callAndRelay("candle_get_wallets", {}, getConfig(), doFetch));
|
|
46295
|
+
register("candle_get_trading_wallets", {
|
|
46296
|
+
title: "List the TEE wallets bound to this key",
|
|
46297
|
+
description: "The TEE wallets bound to THIS key, on Solana (`chain: solana`) and Hood (`chain: evm`): the wallets " + "it may pay from beside the embedded ones in candle_get_wallets. Each row has its id, address, label, " + "`active` (true when the wallet's signing authority is verified active; a false row cannot pay) and " + "`allowLaunch`. The body also carries this key's `scopes` and `paused` (true while the owner has paused " + "the key, which then spends from nothing; spending also needs one of the spend scopes — " + "swap:write, transfer:write or launch:write — and `scopes` says which this key holds). A TEE wallet is signed on your own machine with the key " + "signer: the perps tools here and the candle CLI (`candle swap --wallet <id|address|label>`) pay from " + "it, while candle_trade and candle_swap spend the embedded wallet. Answers for any scope and on a " + "paused key. Paged: pass `continueCursor` back as `cursor` until `isDone`. Reads only; moves nothing.",
|
|
46298
|
+
inputSchema: tradingWalletsShape
|
|
46299
|
+
}, async (args) => callAndRelay("candle_get_trading_wallets", args, getConfig(), doFetch));
|
|
46300
|
+
register("candle_get_spendable_balances", {
|
|
46301
|
+
title: "Read the balances of every wallet this key may spend from",
|
|
46302
|
+
description: "Every wallet THIS key may spend from, and no other, with its balances: the embedded wallets when " + "the owner allows this key to spend them, the TEE wallets bound to it with an active signing " + "authority, and the linked wallets its wallet scope admits. Each row has `kind` (embedded, tee or " + "linked), `chain` (solana or evm), `address`, and `id` and `label` for a linked or TEE wallet. " + "`balances` always holds the base assets (SOL, USDC and CNDL on Solana; ETH and USDG on Hood) plus " + "the `mints` you name (at most 10), each in raw units with the slot or block it was read at. A read " + "that failed is in `unavailable`, never reported as zero. A key with no spend scope (swap:write, " + "transfer:write or launch:write) can spend from nothing and gets an empty page, not an error. " + "`complete: false` means the account's wallet list was cut off. 50 wallets a page: pass " + "`continueCursor` back as `cursor` until `isDone`. Needs no account:read; candle_get_portfolio is " + "the whole-account read. Reads only; moves nothing.",
|
|
46303
|
+
inputSchema: spendableBalancesShape
|
|
46304
|
+
}, async (args) => callAndRelay("candle_get_spendable_balances", args, getConfig(), doFetch));
|
|
45718
46305
|
register("candle_get_profile_wallets", {
|
|
45719
46306
|
title: "Read which wallets an agent profile can spend from",
|
|
45720
46307
|
description: "An agent profile (API key) either spends from EVERY wallet on its account or only from " + "the ones assigned to it. Read walletScope before drawing any conclusion from the list: " + "an empty list means 'every wallet' under scope 'all' and 'none at all' under 'selected'. " + "Reads only; moves nothing.",
|
|
@@ -45732,7 +46319,7 @@ function registerToolSubset(server, options) {
|
|
|
45732
46319
|
}, async (args) => callAndRelay("candle_get_profile_pnl", args, getConfig(), doFetch));
|
|
45733
46320
|
register("candle_get_profile_trades", {
|
|
45734
46321
|
title: "Read an agent profile's trade history",
|
|
45735
|
-
description: "Orders, actual fills, fees, timestamps and transaction hashes for this profile. Page by time with " + "`since`, `until` and `cursor` (continue with the response's `nextCursor`). Includes FAILED " + "trades, with an errorCode saying why each did not go through, so this answers 'what happened to " + "my order' as well as 'what did I trade'. Reads only; moves nothing.",
|
|
46322
|
+
description: "Orders, actual fills, fees, timestamps and transaction hashes for this profile. Page by time with " + "`since`, `until` and `cursor` (continue with the response's `nextCursor`). Includes FAILED " + "trades, with an errorCode saying why each did not go through, so this answers 'what happened to " + "my order' as well as 'what did I trade'. The profile's swaps are listed among the trades by " + "time, each with kind 'swap', rail ('swap' for candle_swap, 'build' for the CLI's build and " + "submit), fromAsset, toAsset and amountRaw; a candle_swap made without a clientSwapId is not " + "recorded, so it is not listed. Reads only; moves nothing.",
|
|
45736
46323
|
inputSchema: profileTradesShape
|
|
45737
46324
|
}, async (args) => callAndRelay("candle_get_profile_trades", args, getConfig(), doFetch));
|
|
45738
46325
|
register("candle_get_paper_inventory", {
|
|
@@ -45771,10 +46358,12 @@ function registerToolSubset(server, options) {
|
|
|
45771
46358
|
});
|
|
45772
46359
|
register("candle_execution_status", {
|
|
45773
46360
|
title: "Can this key execute right now",
|
|
45774
|
-
description: "One call before trading: the embedded wallets to spend from, the
|
|
46361
|
+
description: "One call before trading: the embedded wallets to spend from, the TEE wallets bound to this " + "key, the tier that decides what may be traded, this key's own spend limits, and its loss " + "limits and stop state (risk: lossLimits, state, stopped, tripped). `wallets` is the embedded " + "read. `tee` lists the first page of TEE wallets with `chain` (solana or evm), `active` and " + "`spendable` (active, the key not paused — `teePaused` says whether it is — and the key " + "holding a spend scope: swap:write, transfer:write or launch:write; `teeScopes` lists the " + "key's scopes, so a read-only key shows its wallets with `spendable: false`); " + "`teeComplete: false` means more, which candle_get_trading_wallets lists. candle_trade and " + "candle_swap spend the embedded wallet; a TEE wallet is signed locally (the perps tools, the " + "candle CLI's --wallet). A stopped key reports ready: false and a top-level stopped naming " + "the bound that broke; every trade answers RISK_LIMIT_REACHED until an account owner resumes " + "it, so stop trading and say why rather than retrying. Any scope can read its own. Reads " + "only; moves nothing. Call it when " + "a run starts, or after an authorization error, rather than inferring readiness from a " + "failed trade. If a read could not be completed the tool says which one and does NOT " + "claim the account is unready: an unreachable endpoint and a missing tier are different " + "problems with different fixes.",
|
|
45775
46362
|
inputSchema: {}
|
|
45776
46363
|
}, async () => {
|
|
45777
|
-
const result = await executionStatus(getConfig(), doFetch
|
|
46364
|
+
const result = await executionStatus(getConfig(), doFetch, {
|
|
46365
|
+
teeWallets: options.embeddedExecutionStatus !== true
|
|
46366
|
+
});
|
|
45778
46367
|
return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
|
|
45779
46368
|
});
|
|
45780
46369
|
register("candle_swap", {
|
|
@@ -45830,6 +46419,13 @@ function registerToolSubset(server, options) {
|
|
|
45830
46419
|
const result = await executeTrade(args, getConfig(), doFetch);
|
|
45831
46420
|
return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
|
|
45832
46421
|
});
|
|
46422
|
+
register("candle_trade_execute", {
|
|
46423
|
+
title: "Execute a deferred trade",
|
|
46424
|
+
description: "Execute a trade you built with candle_trade and deferExecution: true. MOVES REAL FUNDS: Candle signs " + "the plan it kept back from the build with the account's own delegation and broadcasts it. Takes the " + "clientTradeId and nothing else, so nothing you send can change what gets signed; to change the trade, " + `build a new one.
|
|
46425
|
+
|
|
46426
|
+
` + "Exactly one execution per build. Calling again never broadcasts a second time: it returns the stored " + "result once the first has confirmed, and a 409 IDEMPOTENCY_CONFLICT while the outcome is still " + "unknown (read candle_get_operation with the clientTradeId, kind trade, before doing anything else). " + "More than 30 minutes after the build it is 409 QUOTE_EXPIRED: build again for a fresh quote. A " + "trade built without deferExecution has already executed and is refused here.",
|
|
46427
|
+
inputSchema: tradeExecuteShape
|
|
46428
|
+
}, async (args) => callAndRelay("candle_trade_execute", args, getConfig(), doFetch));
|
|
45833
46429
|
register("candle_atomic_launch_dry_run", {
|
|
45834
46430
|
title: "Dry-run an atomic Solana launch",
|
|
45835
46431
|
description: "Check a Solana launch bundled with first buys and price each buy, without building anything. Runs the " + "same checks as the atomic build, in the same order (payers, Believer check, fee and spend limits, the " + "key's transaction cap), then returns the launch preview (curveTerms) and one fill per first buy: the " + "tokens it would receive and the floor the build would sign it at. Reads only; moves nothing, reserves " + "nothing, and the clientLaunchId stays unused. It does not check balances, the daily cap, or the " + "transaction's size, so the real launch can still refuse. Needs launch:write, swap:write and Pro or Max.",
|
|
@@ -46038,7 +46634,7 @@ async function registerEligibleProfileTool(server, env = process.env, doFetch =
|
|
|
46038
46634
|
}
|
|
46039
46635
|
return eligible;
|
|
46040
46636
|
}
|
|
46041
|
-
var VAMP_CANDIDATES_DESCRIPTION, localProfileTools, STAKING_DISCLOSURE = "No instant unstake. Read pool.withdrawalTimelockSeconds for the wait. Solana rewards accrue to the holding wallet. sCNDL in any eligible account wallet counts toward Pro and earns the account's Hood staking reward, except a wallet another account also claims, wallets past the 200-wallet cap, and an account with no EVM wallet. CNDL in a pending withdrawal counts toward nothing for Pro.", stakingWriteShape, TOOL_NAMES, LAB_TOOL_NAMES, PROFILE_EDIT_TOOL_NAMES, isProfileEditTool = (name) => PROFILE_EDIT_TOOL_NAMES.includes(name), OPERATION_NOT_FOUND_RULE, getMarketShape, priceHistoryShape, TOKEN_EVENTS_LIMIT_MESSAGE = "limit must be a whole number from 1 to 1000", tokenEventsLimit, TOKEN_EVENTS_KIND_NOTE = "kept back from the request and applied to the returned page by this server, so a filtered page can be shorter than limit while older events of other kinds remain: page with nextCursor as before until it is null", tokenCardShape, tokenEventsShape, tokenForensicsShape, FEED_LIMIT_MESSAGE = "limit must be a whole number from 1 to 200", feedLimit, getFeedShape, reportActivityShape, getAgentProfileShape, listAgentsShape, createPostShape, deletePostShape, listAgentPostsShape, getAgentPositionsShape, getOperationShape, vampCandidatesShape, getFeesShape, atomicPayerShape, atomicLaunchDryRunShape, claimFeesShape, resolveTokenShape, profileWalletsShape, profilePnlShape, profileTradesShape, noArgsShape, setProfileWalletsShape, swapShape, tradeShape, tokenImageUrl, tokenBannerUrl, localImageShape, localLaunchTokenShape, localLaunchAndSeedShape, IMAGE_PATH_DISCLOSURE, transferShape, sweepShape, PROFILE_PNL_HYPERLIQUID;
|
|
46637
|
+
var VAMP_CANDIDATES_DESCRIPTION, localProfileTools, STAKING_DISCLOSURE = "No instant unstake. Read pool.withdrawalTimelockSeconds for the wait. Solana rewards accrue to the holding wallet. sCNDL in any eligible account wallet counts toward Pro and earns the account's Hood staking reward, except a wallet another account also claims, wallets past the 200-wallet cap, and an account with no EVM wallet. CNDL in a pending withdrawal counts toward nothing for Pro.", stakingWriteShape, TOOL_NAMES, LAB_TOOL_NAMES, PROFILE_EDIT_TOOL_NAMES, isProfileEditTool = (name) => PROFILE_EDIT_TOOL_NAMES.includes(name), OPERATION_NOT_FOUND_RULE, getMarketShape, quoteShape, priceHistoryShape, TOKEN_EVENTS_LIMIT_MESSAGE = "limit must be a whole number from 1 to 1000", tokenEventsLimit, TOKEN_EVENTS_KIND_NOTE = "kept back from the request and applied to the returned page by this server, so a filtered page can be shorter than limit while older events of other kinds remain: page with nextCursor as before until it is null", tokenCardShape, tokenEventsShape, tokenForensicsShape, FEED_LIMIT_MESSAGE = "limit must be a whole number from 1 to 200", feedLimit, getFeedShape, reportActivityShape, getAgentProfileShape, listAgentsShape, createPostShape, deletePostShape, listAgentPostsShape, getAgentPositionsShape, getOperationShape, vampCandidatesShape, getFeesShape, atomicPayerShape, atomicLaunchDryRunShape, claimFeesShape, resolveTokenShape, tradingWalletsShape, spendableBalancesShape, profileWalletsShape, profilePnlShape, profileTradesShape, noArgsShape, setProfileWalletsShape, swapShape, tradeShape, tradeExecuteShape, tokenImageUrl, tokenBannerUrl, localImageShape, localLaunchTokenShape, localLaunchAndSeedShape, IMAGE_PATH_DISCLOSURE, transferShape, sweepShape, PROFILE_PNL_HYPERLIQUID;
|
|
46042
46638
|
var init_tools = __esm(() => {
|
|
46043
46639
|
init_zod();
|
|
46044
46640
|
init_client2();
|
|
@@ -46067,6 +46663,7 @@ var init_tools = __esm(() => {
|
|
|
46067
46663
|
TOOL_NAMES = [
|
|
46068
46664
|
"candle_launch_token",
|
|
46069
46665
|
"candle_get_market",
|
|
46666
|
+
"candle_quote",
|
|
46070
46667
|
"candle_get_feed",
|
|
46071
46668
|
"candle_vamp_candidates",
|
|
46072
46669
|
"candle_token_events",
|
|
@@ -46103,12 +46700,15 @@ var init_tools = __esm(() => {
|
|
|
46103
46700
|
"candle_get_agent_positions",
|
|
46104
46701
|
"candle_get_plans",
|
|
46105
46702
|
"candle_trade",
|
|
46703
|
+
"candle_trade_execute",
|
|
46106
46704
|
"candle_launch_and_seed",
|
|
46107
46705
|
"candle_atomic_launch_dry_run",
|
|
46108
46706
|
"candle_swap",
|
|
46109
46707
|
"candle_transfer",
|
|
46110
46708
|
"candle_sweep",
|
|
46111
46709
|
"candle_get_wallets",
|
|
46710
|
+
"candle_get_trading_wallets",
|
|
46711
|
+
"candle_get_spendable_balances",
|
|
46112
46712
|
"candle_get_profile_wallets",
|
|
46113
46713
|
"candle_set_profile_wallets",
|
|
46114
46714
|
"candle_get_profile_pnl",
|
|
@@ -46172,6 +46772,15 @@ var init_tools = __esm(() => {
|
|
|
46172
46772
|
chain: exports_external.string().describe('"solana" or "hood"'),
|
|
46173
46773
|
mint: exports_external.string().describe("Token mint (solana) or contract address (hood)")
|
|
46174
46774
|
};
|
|
46775
|
+
quoteShape = {
|
|
46776
|
+
chain: exports_external.enum(["solana", "hood"]),
|
|
46777
|
+
mint: exports_external.string().describe("Token mint (solana) or contract address (hood)"),
|
|
46778
|
+
side: exports_external.enum(["buy", "sell"]),
|
|
46779
|
+
amountRaw: exports_external.string().describe("Positive integer in base units: the quote asset's for a buy, the token's for a sell"),
|
|
46780
|
+
quoteAsset: exports_external.string().optional().describe('Asset to price in, e.g. "sol" or "usdc" on Solana, "eth" or "usdg" on Hood'),
|
|
46781
|
+
poolKey: exports_external.unknown().optional().describe("Hood only: a pool hint for a pool nobody has indexed yet"),
|
|
46782
|
+
includeRoundTrip: exports_external.boolean().optional().describe("Hood only: also return the buy-then-sell cost in basis points")
|
|
46783
|
+
};
|
|
46175
46784
|
priceHistoryShape = {
|
|
46176
46785
|
chain: exports_external.string().optional().describe('"solana" (the default). Hood is refused with the REST route that serves it'),
|
|
46177
46786
|
mint: exports_external.string().describe("Token mint"),
|
|
@@ -46213,7 +46822,7 @@ var init_tools = __esm(() => {
|
|
|
46213
46822
|
return /^[0-9]+$/.test(trimmed) ? Number(trimmed) : value;
|
|
46214
46823
|
}, exports_external.number({ invalid_type_error: FEED_LIMIT_MESSAGE }).int(FEED_LIMIT_MESSAGE).min(1, FEED_LIMIT_MESSAGE).max(200, FEED_LIMIT_MESSAGE).optional().describe("Max rows to return, an integer from 1 to 200."));
|
|
46215
46824
|
getFeedShape = {
|
|
46216
|
-
bucket: exports_external.enum(["new", "graduated", "onfire", "bluechip", "finalstretch"]).describe(
|
|
46825
|
+
bucket: exports_external.enum(["new", "graduated", "onfire", "bluechip", "finalstretch"]).describe('finalstretch: pump.fun tokens at 50% or more of their bonding curve, most complete first, at most the 200 most complete (limit 200 reads all of them); no delta, read the whole list. Narrow it to tokens near graduation with where {"progress":{"gte":0.9}}. Where it is not switched on it is refused as an unknown bucket: use another bucket. bluechip is deprecated and will be removed.'),
|
|
46217
46826
|
chain: exports_external.string().optional().describe("Optional chain filter"),
|
|
46218
46827
|
where: exports_external.string().optional().describe('JSON filter, e.g. {"marketCap":{"lt":150000},"liquidityUsd":{"gte":25000},"mintAuthorityDisabled":{"eq":true}}. ' + "Comparators: eq, ne, lt, lte, gt, gte, present. An ABSENT field satisfies none of them except " + "present:false, so a filter for mintAuthorityDisabled eq true returns only tokens that actually say " + "true, never ones where the flag is simply missing. Use present:false to find the tokens with no data."),
|
|
46219
46828
|
sort: exports_external.string().optional().describe('Sort as "field" or "field:asc" / "field:desc". A bare field means desc.'),
|
|
@@ -46289,6 +46898,13 @@ var init_tools = __esm(() => {
|
|
|
46289
46898
|
resolveTokenShape = {
|
|
46290
46899
|
mint: exports_external.string().describe("Token mint (Solana, base58) or contract address (Hood, 0x-prefixed)")
|
|
46291
46900
|
};
|
|
46901
|
+
tradingWalletsShape = {
|
|
46902
|
+
cursor: exports_external.string().optional().describe("The continueCursor of the previous page. Omit for the first page")
|
|
46903
|
+
};
|
|
46904
|
+
spendableBalancesShape = {
|
|
46905
|
+
cursor: exports_external.string().optional().describe("The continueCursor of the previous page. Omit for the first page"),
|
|
46906
|
+
mints: exports_external.array(exports_external.string()).max(10).optional().describe("Up to 10 extra tokens to read beside the base assets: Solana mints or Hood (0x) token addresses. " + "Each is read on the wallets of its own chain")
|
|
46907
|
+
};
|
|
46292
46908
|
profileWalletsShape = {
|
|
46293
46909
|
keyPrefix: exports_external.string().describe("The profile's API key prefix, as listed by candle keys list or in the dashboard")
|
|
46294
46910
|
};
|
|
@@ -46324,7 +46940,11 @@ var init_tools = __esm(() => {
|
|
|
46324
46940
|
quoteAsset: exports_external.string().optional().describe('What the wallet spends on a buy or receives on a sell: "sol", "usdc" or "cndl" on Solana, ' + '"eth" or "usdg" on Hood. Echoing the `quoteAsset` a quote (POST /api/v1/trade/agent/quote) returned is safe. On Solana it applies only ' + "to an arbitrary mint Candle never launched (a buy or sell works on any plan) and is ignored for a Candle token, " + "whose quote comes from the token itself. On Hood it is the settlement asset of a DEX " + "trade; a USDG buy adds an approval transaction an ETH buy does not. It is not the route: " + "the cheapest path to the asset is chosen separately. Defaults to sol / ETH settlement."),
|
|
46325
46941
|
maxSlippageBps: exports_external.number().int().min(0).max(5000).optional().describe("Slippage bound in bps, 0-5000 (50%). Server defaults to 100 (1%)"),
|
|
46326
46942
|
clientTradeId: exports_external.string().optional().describe("Idempotency key. Auto-generated when omitted and echoed in the result. Retrying with the " + "SAME id is safe (idempotent replay); a new id is a SECOND trade."),
|
|
46327
|
-
paper: exports_external.preprocess((value) => value === "true" || value === 1 || value === "1" ? true : value === "false" || value === 0 || value === "0" ? false : value, exports_external.boolean().optional().describe("Rehearse instead of trading. The request passes every admission rule a live trade passes " + "-- the same planner, spend gate, key cap and loss limits -- and records the quote, but " + "nothing is ever broadcast and no funds move. Use it to check that a strategy is admitted " + "before risking anything on it. A paper fill is optimistic by construction: it books the " + "quoted price, so the gap between a paper arm and a live one IS the execution cost. " + "A sell of a mint this key already paper-bought also closes that paper book when the " + "live wallet is empty, even if this flag is omitted."))
|
|
46943
|
+
paper: exports_external.preprocess((value) => value === "true" || value === 1 || value === "1" ? true : value === "false" || value === 0 || value === "0" ? false : value, exports_external.boolean().optional().describe("Rehearse instead of trading. The request passes every admission rule a live trade passes " + "-- the same planner, spend gate, key cap and loss limits -- and records the quote, but " + "nothing is ever broadcast and no funds move. Use it to check that a strategy is admitted " + "before risking anything on it. A paper fill is optimistic by construction: it books the " + "quoted price, so the gap between a paper arm and a live one IS the execution cost. " + "A sell of a mint this key already paper-bought also closes that paper book when the " + "live wallet is empty, even if this flag is omitted.")),
|
|
46944
|
+
deferExecution: exports_external.preprocess((value) => value === "true" || value === 1 || value === "1" ? true : value === "false" || value === 0 || value === "0" ? false : value, exports_external.boolean().optional().describe("Quote first, spend later. The trade is built and the result carries the quote with " + 'status "built"; NOTHING is signed or spent until candle_trade_execute is called with the same ' + "clientTradeId. The build holds for 30 minutes; executing after that is QUOTE_EXPIRED and you " + "build again for a fresh quote. Rebuilding with the same clientTradeId re-quotes it, until it has " + "been sent to candle_trade_execute. There is no cancel: let an unwanted build expire. Cannot be " + "combined with `paper`."))
|
|
46945
|
+
};
|
|
46946
|
+
tradeExecuteShape = {
|
|
46947
|
+
clientTradeId: exports_external.string().min(1).describe("The clientTradeId of a candle_trade built with deferExecution: true. Nothing else is accepted.")
|
|
46328
46948
|
};
|
|
46329
46949
|
tokenImageUrl = launchSchemaField(launchTokenShape, "imageUrl");
|
|
46330
46950
|
tokenBannerUrl = launchSchemaField(launchTokenShape, "bannerUrl");
|
|
@@ -74149,29 +74769,11 @@ ${USAGE3}` : USAGE3, ctx.json);
|
|
|
74149
74769
|
}
|
|
74150
74770
|
|
|
74151
74771
|
// src/commands/auth.ts
|
|
74152
|
-
|
|
74153
|
-
import { join as join9 } from "node:path";
|
|
74154
|
-
|
|
74155
|
-
// src/account.ts
|
|
74156
|
-
init_client();
|
|
74157
|
-
async function fetchAccount(deps, apiUrl, apiKey) {
|
|
74158
|
-
const identity = await apiRequest("/api/v1/agent/wallets/embedded", {
|
|
74159
|
-
auth: "key",
|
|
74160
|
-
credentials: { apiKey },
|
|
74161
|
-
apiUrl,
|
|
74162
|
-
fetch: deps.fetch,
|
|
74163
|
-
env: deps.env
|
|
74164
|
-
});
|
|
74165
|
-
const body = identity.ok ? identity.body : undefined;
|
|
74166
|
-
const account = body?.account;
|
|
74167
|
-
if (account)
|
|
74168
|
-
return { account, ...body?.username ? { username: body.username } : {} };
|
|
74169
|
-
return { failure: identity.ok ? "no account in the response" : identity.message };
|
|
74170
|
-
}
|
|
74171
|
-
|
|
74172
|
-
// src/commands/auth.ts
|
|
74772
|
+
init_account();
|
|
74173
74773
|
init_agent_key_access();
|
|
74174
74774
|
init_args();
|
|
74775
|
+
import { homedir as homedir3 } from "node:os";
|
|
74776
|
+
import { join as join9 } from "node:path";
|
|
74175
74777
|
|
|
74176
74778
|
// src/checks.ts
|
|
74177
74779
|
init_client();
|
|
@@ -76145,7 +76747,7 @@ var HELP = {
|
|
|
76145
76747
|
group: "Trade",
|
|
76146
76748
|
intro: "Quote, swap and bridge on Solana or Hood; read an operation",
|
|
76147
76749
|
summary: "Quote, confirm and swap on Solana or Hood, bridge between them; read an operation by id",
|
|
76148
|
-
description: "Swaps run through a TEE wallet's bound key: the quote is shown and confirmed before anything is sent. The first buy after a launch is this command, unless the launch carried it with candle launch --buy. The assets decide the chain (ETH, USDG or a 0x token is Hood; SOL, USDC, CNDL or a mint is Solana) and a named wallet must be on it. A Hood TEE wallet signs one leg at a time: approve, Permit2, trade, then the fee, each only after the one before it landed. SOL or USDC to ETH or USDG, or back, is a bridge through Relay from a TEE wallet into this key's own TEE wallet on the other chain (--to names it when there are several); Candle charges no fee, only configured raw caps and USD limits apply (unset means unlimited, zero still binds), and this machine checks Relay's deposit before it is signed. The bridge prints its deposit and returns while Relay fills; --wait follows it for up to ten minutes. While it is open, a sweep of either wallet refuses BRIDGE_IN_FLIGHT for two hours. Vault to vault: fund the Solana TEE wallet from the vault, bridge, then disable and sweep the Hood TEE wallet into its vault. Every plan can trade Candle-launched tokens and base assets, and buy or sell a token that was not launched on Candle. Each trade pays the plan's agent fee on top; a base-pair swap or a bridge between this key's own wallets pays none. candle plans shows the fees in force. With --json, stdout is one JSON value for every swap, trade, replay and status read: kind, status, clientTradeId, signature (the primary transaction, or null), signatures (a list) and raw (the original response); every human line is on stderr. The keys older versions printed stay beside them.",
|
|
76750
|
+
description: "Swaps run through a TEE wallet's bound key: the quote is shown and confirmed before anything is sent. The first buy after a launch is this command, unless the launch carried it with candle launch --buy. The assets decide the chain (ETH, USDG or a 0x token is Hood; SOL, USDC, CNDL or a mint is Solana) and a named wallet must be on it. A Hood TEE wallet signs one leg at a time: approve, Permit2, trade, then the fee, each only after the one before it landed. SOL or USDC to ETH or USDG, or back, is a bridge through Relay from a TEE wallet into this key's own TEE wallet on the other chain (--to names it when there are several); Candle charges no fee, only configured raw caps and USD limits apply (unset means unlimited, zero still binds), and this machine checks Relay's deposit before it is signed. The bridge prints its deposit and returns while Relay fills; --wait follows it for up to ten minutes. While it is open, a sweep of either wallet refuses BRIDGE_IN_FLIGHT for two hours. Vault to vault: fund the Solana TEE wallet from the vault, bridge, then disable and sweep the Hood TEE wallet into its vault. Every plan can trade Candle-launched tokens and base assets, and buy or sell a token that was not launched on Candle. Each trade pays the plan's agent fee on top; a base-pair swap or a bridge between this key's own wallets pays none. candle plans shows the fees in force. With --dry-run, stdout reports per-check results and the quote; nothing is signed or sent. Otherwise with --json, stdout is one JSON value for every swap, trade, replay and status read: kind, status, clientTradeId, signature (the primary transaction, or null), signatures (a list) and raw (the original response); every human line is on stderr. The keys older versions printed stay beside them.",
|
|
76149
76751
|
usage: ["candle swap <from> <to> [flags]", "candle swap status <id>"],
|
|
76150
76752
|
rows: [
|
|
76151
76753
|
{
|
|
@@ -76157,7 +76759,14 @@ var HELP = {
|
|
|
76157
76759
|
description: "Read an operation without resending it; --wait follows a bridge until it ends"
|
|
76158
76760
|
}
|
|
76159
76761
|
],
|
|
76762
|
+
flags: [
|
|
76763
|
+
{
|
|
76764
|
+
invocation: "--dry-run",
|
|
76765
|
+
description: "Check a Solana TEE payer, build the quote and check spend, then stop. Never signs, sends, prompts or saves an operation. Refuses --yes, --percent and --client-trade-id; embedded, Hood and bridge payers are unsupported"
|
|
76766
|
+
}
|
|
76767
|
+
],
|
|
76160
76768
|
examples: [
|
|
76769
|
+
"candle swap SOL USDC --amount 0.001 --wallet AgentOne --dry-run --json",
|
|
76161
76770
|
"candle swap SOL USDC --amount 0.5 --wallet AgentOne",
|
|
76162
76771
|
"candle swap USDC SOL --percent 100 --wallet AgentOne",
|
|
76163
76772
|
"candle swap ETH USDG --amount 0.05 --wallet HoodOne",
|
|
@@ -76458,9 +77067,10 @@ var HELP = {
|
|
|
76458
77067
|
group: "Trade",
|
|
76459
77068
|
intro: "Move funds out of a TEE wallet to your wallets or its vault",
|
|
76460
77069
|
summary: "Move funds out of a TEE wallet to your own wallets or its vault, through its bound key",
|
|
76461
|
-
description: "Moves one asset out of a TEE wallet this machine can sign for. The bound key must be a Withdraw key (transfer:bound); from that wallet, Candle allows only its own pinned vault or another of the account's wallets you linked while signed in or marked trusted, and, where the server has TEE_WITHDRAWALS_ENABLED on, an address on the account's withdrawal allowlist (named by its label, id or address). To a linked wallet or an approved address only configured raw caps and USD limits apply (unset means unlimited, zero still binds), and the asset must be a base asset; to the vault any token and max are allowed. Candle builds the transaction, this machine approves the relay, Privy signs, Candle broadcasts. The destination and its kind are shown before you confirm. The asset decides the chain: ETH, USDG or --token is Hood, from a Hood TEE wallet, where this machine checks the one leg moves exactly what you confirmed before it is signed, and ETH max leaves the gas reserve.",
|
|
77070
|
+
description: "Moves one asset out of a TEE wallet this machine can sign for. The bound key must be a Withdraw key (transfer:bound); from that wallet, Candle allows only its own pinned vault or another of the account's wallets you linked while signed in or marked trusted, and, where the server has TEE_WITHDRAWALS_ENABLED on, an address on the account's withdrawal allowlist (named by its label, id or address). To a linked wallet or an approved address only configured raw caps and USD limits apply (unset means unlimited, zero still binds), and the asset must be a base asset; to the vault any token and max are allowed. Candle builds the transaction, this machine approves the relay, Privy signs, Candle broadcasts. The destination and its kind are shown before you confirm. candle transfer status <id> reads a transfer by its id or your --client-id: built, expired, submitted with its signature, unconfirmed (check the chain or candle activity before using a new client id), or failed (kept for 24 hours; older transfers are in candle activity). The asset decides the chain: ETH, USDG or --token is Hood, from a Hood TEE wallet, where this machine checks the one leg moves exactly what you confirmed before it is signed, and ETH max leaves the gas reserve.",
|
|
76462
77071
|
usage: [
|
|
76463
|
-
"candle transfer --to <address|wallet name|allowlist label|vault> --asset <SOL|USDC|CNDL|ETH|USDG>|--mint <mint>|--token <0x...> --amount <decimal|max> [--wallet <tee>] [--yes] [--json]"
|
|
77072
|
+
"candle transfer --to <address|wallet name|allowlist label|vault> --asset <SOL|USDC|CNDL|ETH|USDG>|--mint <mint>|--token <0x...> --amount <decimal|max> [--wallet <tee>] [--client-id <id>] [--yes] [--json]",
|
|
77073
|
+
"candle transfer status <transfer id|client id> [--json]"
|
|
76464
77074
|
],
|
|
76465
77075
|
rows: [],
|
|
76466
77076
|
flags: [
|
|
@@ -76480,10 +77090,15 @@ var HELP = {
|
|
|
76480
77090
|
invocation: "--rpc-url <url>",
|
|
76481
77091
|
description: "Your own Solana RPC, or on Hood the EVM RPC a --token's decimals are read over (optional)"
|
|
76482
77092
|
},
|
|
77093
|
+
{
|
|
77094
|
+
invocation: "--client-id <id>",
|
|
77095
|
+
description: "Your own id for this transfer (1 to 128 of letters, digits, . _ : -). An id already used for a built or sent transfer is refused with IDEMPOTENCY_CONFLICT naming that transfer, so a retry never sends twice; read it with candle transfer status <id>"
|
|
77096
|
+
},
|
|
76483
77097
|
{ invocation: "--yes", description: "Skip the confirmation prompt (the destination is still printed)" }
|
|
76484
77098
|
],
|
|
76485
77099
|
examples: [
|
|
76486
|
-
"candle transfer --to vault --asset SOL --amount max --wallet AgentOne",
|
|
77100
|
+
"candle transfer --to vault --asset SOL --amount max --wallet AgentOne --client-id sweep-2026-11-07",
|
|
77101
|
+
"candle transfer status sweep-2026-11-07",
|
|
76487
77102
|
"candle transfer --to treasury --asset USDC --amount 250 --wallet AgentOne --yes --json",
|
|
76488
77103
|
"candle transfer --to vault --asset ETH --amount max --wallet HoodAgent"
|
|
76489
77104
|
],
|
|
@@ -76567,9 +77182,9 @@ var HELP = {
|
|
|
76567
77182
|
group: "Account",
|
|
76568
77183
|
intro: "Holdings, prices and value of every wallet",
|
|
76569
77184
|
summary: "Every wallet's holdings, prices and value on Solana and Hood (vault read over your own RPCs)",
|
|
76570
|
-
description: "Vault, TEE and embedded wallets on Solana and Hood, each token with its chain, amount, price and value, then subtotals per group and per chain and one total. TEE and embedded balances, on both chains, come from Candle. Solana vault and external wallets are read over your own Solana RPC, and Candle is sent only the mints they hold, for prices. EVM vault keys are read over your own EVM RPC for ETH and USDG only (the CLI cannot list other tokens), which must be Hood's (chain id 4663); any other chain is refused. Their prices come from Candle by asset (ETH, USDG), never by address. Unpriced tokens are shown as unpriced with the reason (no-market-row, stale-mark, unusable-price, source-unavailable) and left out of the total. A wallet that could not be read in full is marked not read, the footer names its chain, and the exit is 3. Where Candle serves LP, each Solana TEE wallet's DAMM v2 positions follow the tokens (share of the pool plus unclaimed fees, valued by Candle at the same marks) and count in the total; a position whose pool could not be read is shown as not read and the total says it is partial. A bridge moves value between the chain subtotals; it is not a trade. With a filter, only the wallets whose label or address contains it are read and shown, the subtotals and total are for those wallets, and the first line says how many matched. When Candle listed only part of the account's wallets, the result stays partial even if every wallet shown was read, and no match says so. After the totals, a Hyperliquid section (live from Hyperliquid, not in the ledger, and never in the total) lists each EVM TEE wallet's Hyperliquid account value, unrealized P&L, positions and risk at the default thresholds, read from Hyperliquid's public API on this machine; a failed read there is not read and the exit is 3. --chain solana and --no-perps read nothing from Hyperliquid.",
|
|
77185
|
+
description: "Vault, TEE and embedded wallets on Solana and Hood, each token with its chain, amount, price and value, then subtotals per group and per chain and one total. TEE and embedded balances, on both chains, come from Candle. Solana vault and external wallets are read over your own Solana RPC, and Candle is sent only the mints they hold, for prices. EVM vault keys are read over your own EVM RPC for ETH and USDG only (the CLI cannot list other tokens), which must be Hood's (chain id 4663); any other chain is refused. Their prices come from Candle by asset (ETH, USDG), never by address. Unpriced tokens are shown as unpriced with the reason (no-market-row, stale-mark, unusable-price, source-unavailable) and left out of the total. A wallet that could not be read in full is marked not read, the footer names its chain, and the exit is 3. Where Candle serves LP, each Solana TEE wallet's DAMM v2 positions follow the tokens (share of the pool plus unclaimed fees, valued by Candle at the same marks) and count in the total; a position whose pool could not be read is shown as not read and the total says it is partial. A bridge moves value between the chain subtotals; it is not a trade. With a filter, only the wallets whose label or address contains it are read and shown, the subtotals and total are for those wallets, and the first line says how many matched. When Candle listed only part of the account's wallets, the result stays partial even if every wallet shown was read, and no match says so. After the totals, a Hyperliquid section (live from Hyperliquid, not in the ledger, and never in the total) lists each EVM TEE wallet's Hyperliquid account value, unrealized P&L, positions and risk at the default thresholds, read from Hyperliquid's public API on this machine; a failed read there is not read and the exit is 3. --chain solana and --no-perps read nothing from Hyperliquid. The vault is read only for its addresses, so the command needs no terminal: without one, a factor that asks nothing (--factor touch-id or passkey) still unlocks it, and otherwise the vault row says not read and the other groups are shown, but the result is partial: the total says so and the exit is 3. --no-vault leaves the vault out on purpose, and then the result can be complete (exit 0).",
|
|
76571
77186
|
usage: [
|
|
76572
|
-
"candle portfolio [<filter>] [--chain solana|hood] [--rpc-url <url>] [--evm-rpc-url <url>] [--no-perps] [--json]"
|
|
77187
|
+
"candle portfolio [<filter>] [--chain solana|hood] [--rpc-url <url>] [--evm-rpc-url <url>] [--no-perps] [--no-vault] [--json]"
|
|
76573
77188
|
],
|
|
76574
77189
|
rows: [
|
|
76575
77190
|
{
|
|
@@ -76591,6 +77206,10 @@ var HELP = {
|
|
|
76591
77206
|
invocation: "--no-perps",
|
|
76592
77207
|
description: "Skip the Hyperliquid section: send no EVM TEE address to Hyperliquid's public API"
|
|
76593
77208
|
},
|
|
77209
|
+
{
|
|
77210
|
+
invocation: "--no-vault",
|
|
77211
|
+
description: "Leave the vault out: no unlock, and no vault address is sent to your RPC"
|
|
77212
|
+
},
|
|
76594
77213
|
KEYSTORE_FLAG
|
|
76595
77214
|
],
|
|
76596
77215
|
examples: [
|
|
@@ -77043,6 +77662,10 @@ var HELP = {
|
|
|
77043
77662
|
description: "Give agents access to the account's embedded wallets: opens the Agents page's grant step in your browser (no key is created) and, with an API key, waits until it lands"
|
|
77044
77663
|
},
|
|
77045
77664
|
{ invocation: "revoke <wallet-id>", description: "Revoke a linked wallet" },
|
|
77665
|
+
{
|
|
77666
|
+
invocation: "allowlist [--all]",
|
|
77667
|
+
description: "The account's withdrawal allowlist (the destinations candle transfer --to <label> resolves by). Any key reads it; approving an address is the owner's, in the agent console's Wallets tab (an API key cannot approve); an acting key can revoke one, a read-only key cannot. --all adds removed rows"
|
|
77668
|
+
},
|
|
77046
77669
|
{
|
|
77047
77670
|
invocation: "trust <label|address|id|prefix*>...",
|
|
77048
77671
|
description: "Mark linked wallets yours, so agents can move funds into them (owner only; typed confirm)"
|
|
@@ -77076,6 +77699,7 @@ var HELP = {
|
|
|
77076
77699
|
"candle wallet authorize",
|
|
77077
77700
|
"candle wallet import --chain solana --key-file ./signer.json",
|
|
77078
77701
|
"candle wallet revoke wal_123",
|
|
77702
|
+
"candle wallet allowlist",
|
|
77079
77703
|
"candle wallet trust 'tr-*' 'dest-*'",
|
|
77080
77704
|
"candle wallet allow-launch launcher-1",
|
|
77081
77705
|
"candle wallet close-empty --keep EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
@@ -77273,6 +77897,10 @@ var HELP = {
|
|
|
77273
77897
|
description: "A dedicated, capped Solana wallet for one agent. The key is generated and sealed on this machine; enable delegates it to this profile's API key with a sweep vault pinned, and sweep moves everything back to that vault, signed locally.",
|
|
77274
77898
|
usage: ["candle tee <subcommand> [flags]"],
|
|
77275
77899
|
rows: [
|
|
77900
|
+
{
|
|
77901
|
+
invocation: "check <address|id|label>... [--label-prefix <p>] [--sign] [--max <n>] [--rpc-url <url>]",
|
|
77902
|
+
description: "Check payable membership, record parity and this machine's signer without spending. --sign builds a tiny SOL/USDC quote, checks spend, signs and simulates; never broadcasts (default cap 10). Needs swap:write"
|
|
77903
|
+
},
|
|
77276
77904
|
{
|
|
77277
77905
|
invocation: "new [--label <name>]",
|
|
77278
77906
|
description: "Add a Solana key to an existing tee-wallets.enc. With no store, refuses: candle vault new-key --chain solana, then candle vault promote --from <label> --to-key <prefix>. --factor does not apply"
|
|
@@ -77335,6 +77963,8 @@ var HELP = {
|
|
|
77335
77963
|
}
|
|
77336
77964
|
],
|
|
77337
77965
|
examples: [
|
|
77966
|
+
"candle tee check --label-prefix pool- --json",
|
|
77967
|
+
"candle tee check AgentOne --sign --rpc-url <url>",
|
|
77338
77968
|
"candle tee new --label AgentOne",
|
|
77339
77969
|
"candle tee status AgentOneAddress",
|
|
77340
77970
|
"candle tee sweep AgentOneAddress",
|
|
@@ -77901,13 +78531,14 @@ class AgentProfiles {
|
|
|
77901
78531
|
// src/core/cards.ts
|
|
77902
78532
|
init_render();
|
|
77903
78533
|
init_canonical_json();
|
|
77904
|
-
import { createHash as createHash3, randomBytes as randomBytes5, randomUUID as
|
|
78534
|
+
import { createHash as createHash3, randomBytes as randomBytes5, randomUUID as randomUUID3 } from "node:crypto";
|
|
77905
78535
|
|
|
77906
78536
|
// src/core/classify.ts
|
|
77907
78537
|
var READ = { class: "read" };
|
|
77908
78538
|
var card = (kind, idKey = "coreOpId") => ({ class: "card", kind, idKey });
|
|
77909
78539
|
var TOOL_CLASSES = {
|
|
77910
78540
|
candle_get_market: READ,
|
|
78541
|
+
candle_quote: READ,
|
|
77911
78542
|
candle_get_feed: READ,
|
|
77912
78543
|
candle_vamp_candidates: READ,
|
|
77913
78544
|
candle_token_events: READ,
|
|
@@ -77931,6 +78562,8 @@ var TOOL_CLASSES = {
|
|
|
77931
78562
|
candle_get_agent_positions: READ,
|
|
77932
78563
|
candle_get_plans: READ,
|
|
77933
78564
|
candle_get_wallets: READ,
|
|
78565
|
+
candle_get_trading_wallets: READ,
|
|
78566
|
+
candle_get_spendable_balances: READ,
|
|
77934
78567
|
candle_get_profile_wallets: READ,
|
|
77935
78568
|
candle_get_profile_pnl: READ,
|
|
77936
78569
|
candle_get_profile_trades: READ,
|
|
@@ -77956,6 +78589,7 @@ var TOOL_CLASSES = {
|
|
|
77956
78589
|
candle_perps_risk: READ,
|
|
77957
78590
|
candle_perps_thresholds: READ,
|
|
77958
78591
|
candle_trade: card("trade", "clientTradeId"),
|
|
78592
|
+
candle_trade_execute: card("trade"),
|
|
77959
78593
|
candle_swap: card("swap", "clientSwapId"),
|
|
77960
78594
|
candle_launch_token: card("launch", "clientLaunchId"),
|
|
77961
78595
|
candle_launch_and_seed: card("launch", "clientLaunchId"),
|
|
@@ -78045,7 +78679,8 @@ async function readOnce(executor, target, writeKey) {
|
|
|
78045
78679
|
if (!readsById(target))
|
|
78046
78680
|
return { readable: false };
|
|
78047
78681
|
const kind = target.kind === "launch" ? "launch" : "trade";
|
|
78048
|
-
const
|
|
78682
|
+
const clientId = target.tool === "candle_trade_execute" ? target.params.clientTradeId : target.opId;
|
|
78683
|
+
const outcome = await executor.execute("candle_get_operation", { clientId, kind }, writeKey);
|
|
78049
78684
|
if (outcome.state === "succeeded" || outcome.state === "pending") {
|
|
78050
78685
|
const job = outcome.result?.job ?? outcome.result;
|
|
78051
78686
|
const status = typeof job?.status === "string" ? job.status : "";
|
|
@@ -79763,8 +80398,8 @@ class CardBook {
|
|
|
79763
80398
|
if (idKey === "clientId")
|
|
79764
80399
|
return `0x${randomBytes5(16).toString("hex")}`;
|
|
79765
80400
|
if (idKey === "clientDepositId")
|
|
79766
|
-
return `deposit-${
|
|
79767
|
-
return
|
|
80401
|
+
return `deposit-${randomUUID3()}`;
|
|
80402
|
+
return randomUUID3();
|
|
79768
80403
|
}
|
|
79769
80404
|
view(card2) {
|
|
79770
80405
|
return JSON.parse(JSON.stringify(card2));
|
|
@@ -80623,18 +81258,18 @@ async function readJournal(path) {
|
|
|
80623
81258
|
const tornTail = text2.length > 0 && !text2.endsWith(`
|
|
80624
81259
|
`);
|
|
80625
81260
|
const rows = [];
|
|
80626
|
-
let
|
|
81261
|
+
let skipped2 = 0;
|
|
80627
81262
|
for (const line of text2.split(`
|
|
80628
81263
|
`)) {
|
|
80629
81264
|
if (line.trim() === "")
|
|
80630
81265
|
continue;
|
|
80631
81266
|
const row2 = parseRow(line);
|
|
80632
81267
|
if (row2 === undefined)
|
|
80633
|
-
|
|
81268
|
+
skipped2++;
|
|
80634
81269
|
else
|
|
80635
81270
|
rows.push(row2);
|
|
80636
81271
|
}
|
|
80637
|
-
return { rows, skipped, tornTail };
|
|
81272
|
+
return { rows, skipped: skipped2, tornTail };
|
|
80638
81273
|
}
|
|
80639
81274
|
function parseRow(line) {
|
|
80640
81275
|
let value;
|
|
@@ -80695,10 +81330,10 @@ function registerLaunchHandlers(server, deps) {
|
|
|
80695
81330
|
server.handle("launch.dryRun", async (params, { client }) => deps.cards.dryRun({ cardId: params.cardId, factor: parseFactor(params.factor) }, client.clientId));
|
|
80696
81331
|
}
|
|
80697
81332
|
function coded(error, fallback) {
|
|
80698
|
-
const
|
|
80699
|
-
if (
|
|
81333
|
+
const failure3 = error;
|
|
81334
|
+
if (failure3 !== null && typeof failure3 === "object" && "rpcCode" in failure3)
|
|
80700
81335
|
return error;
|
|
80701
|
-
return refused(typeof
|
|
81336
|
+
return refused(typeof failure3?.code === "string" ? failure3.code : fallback, typeof failure3?.message === "string" ? failure3.message : String(error));
|
|
80702
81337
|
}
|
|
80703
81338
|
|
|
80704
81339
|
// src/commands/core.ts
|
|
@@ -81102,7 +81737,7 @@ ${text2}` : text2;
|
|
|
81102
81737
|
const sleep = this.deps.sleep ?? defaultSleep;
|
|
81103
81738
|
let tooMany = 0;
|
|
81104
81739
|
let lost = 0;
|
|
81105
|
-
let
|
|
81740
|
+
let failure3;
|
|
81106
81741
|
while (!poll.stopped) {
|
|
81107
81742
|
const answer2 = await client.events(poll.turnId, poll.next, { signal: poll.abort.signal });
|
|
81108
81743
|
if (poll.stopped)
|
|
@@ -81137,7 +81772,7 @@ ${text2}` : text2;
|
|
|
81137
81772
|
if (refusal2.code === "too_many_polls") {
|
|
81138
81773
|
tooMany++;
|
|
81139
81774
|
if (tooMany > TOO_MANY_POLLS_RETRIES) {
|
|
81140
|
-
|
|
81775
|
+
failure3 = "too_many_polls";
|
|
81141
81776
|
break;
|
|
81142
81777
|
}
|
|
81143
81778
|
await sleep(TOO_MANY_POLLS_WAIT_MS);
|
|
@@ -81146,13 +81781,13 @@ ${text2}` : text2;
|
|
|
81146
81781
|
if (refusal2.shape === "transport") {
|
|
81147
81782
|
lost++;
|
|
81148
81783
|
if (lost > LOST_POLL_RETRIES) {
|
|
81149
|
-
|
|
81784
|
+
failure3 = "QUANT_UNREACHABLE";
|
|
81150
81785
|
break;
|
|
81151
81786
|
}
|
|
81152
81787
|
await sleep(Math.min(1000 * lost, 5000));
|
|
81153
81788
|
continue;
|
|
81154
81789
|
}
|
|
81155
|
-
|
|
81790
|
+
failure3 = refusal2.code;
|
|
81156
81791
|
break;
|
|
81157
81792
|
}
|
|
81158
81793
|
if (this.poll !== poll)
|
|
@@ -81163,11 +81798,11 @@ ${text2}` : text2;
|
|
|
81163
81798
|
this.ended.delete(this.ended.values().next().value);
|
|
81164
81799
|
if (this.turn?.turnId === poll.turnId)
|
|
81165
81800
|
this.turn.running = false;
|
|
81166
|
-
if (
|
|
81801
|
+
if (failure3 !== undefined) {
|
|
81167
81802
|
this.publish({
|
|
81168
81803
|
turnId: poll.turnId,
|
|
81169
81804
|
kind: "poll_failed",
|
|
81170
|
-
data: { code:
|
|
81805
|
+
data: { code: failure3 },
|
|
81171
81806
|
seq: poll.next,
|
|
81172
81807
|
origin: poll.origin
|
|
81173
81808
|
});
|
|
@@ -83204,8 +83839,8 @@ async function prepare(params, call2, deps) {
|
|
|
83204
83839
|
try {
|
|
83205
83840
|
preview = await deps.reads.previewVault(parsed, wallet3);
|
|
83206
83841
|
} catch (error) {
|
|
83207
|
-
const
|
|
83208
|
-
throw refused(typeof
|
|
83842
|
+
const failure3 = error;
|
|
83843
|
+
throw refused(typeof failure3.code === "string" ? failure3.code : "TRANSFER_PREVIEW_FAILED", `${terminalText(typeof failure3.message === "string" ? failure3.message : String(error))} Nothing was prepared.`);
|
|
83209
83844
|
}
|
|
83210
83845
|
} else {
|
|
83211
83846
|
const withdraw = deps.trading.withdrawState();
|
|
@@ -84579,8 +85214,8 @@ async function readTargetSigner(ctx, deviceToken, keyPrefix) {
|
|
|
84579
85214
|
}
|
|
84580
85215
|
async function preflightToKey(ctx, raw, opts) {
|
|
84581
85216
|
const { deps, json } = ctx;
|
|
84582
|
-
const refuse3 = (
|
|
84583
|
-
writeLocalFailure(deps,
|
|
85217
|
+
const refuse3 = (failure3) => {
|
|
85218
|
+
writeLocalFailure(deps, failure3, json);
|
|
84584
85219
|
return { ok: false, exit: 1 };
|
|
84585
85220
|
};
|
|
84586
85221
|
const deviceToken = await resolveDeviceToken(deps, ctx.profile);
|
|
@@ -84732,8 +85367,8 @@ async function rebindPromoted(ctx, deviceToken, toKeyPrefix, wallets2, pinnedSpk
|
|
|
84732
85367
|
if (!committed.ok) {
|
|
84733
85368
|
run2.ok = false;
|
|
84734
85369
|
if ("missing" in committed) {
|
|
84735
|
-
const
|
|
84736
|
-
run2.failure = { ...
|
|
85370
|
+
const failure3 = missingSignerFailure(committed.missing);
|
|
85371
|
+
run2.failure = { ...failure3, status: 409, chunk: at2 + 1 };
|
|
84737
85372
|
} else if ("changed" in committed) {
|
|
84738
85373
|
run2.failure = { ...committed.changed, status: 409, chunk: at2 + 1 };
|
|
84739
85374
|
} else {
|
|
@@ -85517,8 +86152,8 @@ async function moveToLiveKey(ctx, deviceToken, source, toKeyRaw) {
|
|
|
85517
86152
|
for (const chunk of chunkWallets(ids)) {
|
|
85518
86153
|
const preview = await postRebind(ctx, deviceToken, { dryRun: true, toKeyPrefix: target.keyPrefix, wallets: chunk });
|
|
85519
86154
|
if (!preview.ok) {
|
|
85520
|
-
const
|
|
85521
|
-
writeScreenLocalFailure(deps,
|
|
86155
|
+
const failure3 = rebindFailureDetails(preview, {});
|
|
86156
|
+
writeScreenLocalFailure(deps, failure3, json);
|
|
85522
86157
|
return 1;
|
|
85523
86158
|
}
|
|
85524
86159
|
const shown = preview.body;
|
|
@@ -89536,8 +90171,7 @@ async function externalList(args, ctx) {
|
|
|
89536
90171
|
return usage(ctx, `Unexpected argument: ${parsed.positionals[0]}`);
|
|
89537
90172
|
if (!refuseEnvPassphrase(ctx))
|
|
89538
90173
|
return 1;
|
|
89539
|
-
|
|
89540
|
-
return 1;
|
|
90174
|
+
const promptCtx = refusePromptsWithoutTerminal(ctx, "external list");
|
|
89541
90175
|
const { deps } = ctx;
|
|
89542
90176
|
const resolvedVault = vaultPathFor(ctx, parsed);
|
|
89543
90177
|
if ("error" in resolvedVault)
|
|
@@ -89545,7 +90179,7 @@ async function externalList(args, ctx) {
|
|
|
89545
90179
|
const path = resolvedVault.path;
|
|
89546
90180
|
return runVaultCommand(ctx, async ({ hold }) => {
|
|
89547
90181
|
const raw = await requireVaultRaw(ctx, resolvedVault);
|
|
89548
|
-
const vault = hold((await unlockInteractively(
|
|
90182
|
+
const vault = hold((await unlockInteractively(promptCtx, path, raw, { acceptOlderCopy: parsed.booleans.has("--accept-older-copy") })).vault);
|
|
89549
90183
|
const externals = vault.index.entries.filter((entry) => entry.role === "external");
|
|
89550
90184
|
const rows = externals.map((entry) => ({
|
|
89551
90185
|
address: entry.address,
|
|
@@ -90433,7 +91067,7 @@ init_secret_store();
|
|
|
90433
91067
|
init_key_output();
|
|
90434
91068
|
init_keys();
|
|
90435
91069
|
init_tee_rebind();
|
|
90436
|
-
import { randomUUID as
|
|
91070
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
90437
91071
|
|
|
90438
91072
|
// src/commands/token-pages.ts
|
|
90439
91073
|
init_esm();
|
|
@@ -90443,7 +91077,7 @@ init_deps();
|
|
|
90443
91077
|
init_human_output();
|
|
90444
91078
|
init_presentation();
|
|
90445
91079
|
init_render();
|
|
90446
|
-
import { createHash as createHash5, randomUUID as
|
|
91080
|
+
import { createHash as createHash5, randomUUID as randomUUID7 } from "node:crypto";
|
|
90447
91081
|
var EXIT_OUTCOME_UNKNOWN = 3;
|
|
90448
91082
|
var EXIT_REVISION_CONFLICT = 4;
|
|
90449
91083
|
var TOKEN_PAGE_LIMITS = { thesis: 280, tokenomics: 5000, lore: 5000, update: 1000, comment: 500 };
|
|
@@ -90526,8 +91160,8 @@ function writeApiFailure(ctx, result, authType) {
|
|
|
90526
91160
|
writeRefusal(ctx, refusal3);
|
|
90527
91161
|
return 1;
|
|
90528
91162
|
}
|
|
90529
|
-
const
|
|
90530
|
-
writeFailure(ctx.deps,
|
|
91163
|
+
const failure3 = result;
|
|
91164
|
+
writeFailure(ctx.deps, failure3.status === 404 ? { ...failure3, message: REFUSALS.not_found } : failure3, { apiUrl: ctx.apiUrl, authType }, ctx.json);
|
|
90531
91165
|
return 1;
|
|
90532
91166
|
}
|
|
90533
91167
|
function dataOf(result) {
|
|
@@ -90590,7 +91224,7 @@ function writeUnknown(ctx, idempotencyKey) {
|
|
|
90590
91224
|
function idempotencyKeyOf(values, usage4) {
|
|
90591
91225
|
const given2 = values["--idempotency-key"];
|
|
90592
91226
|
if (given2 === undefined)
|
|
90593
|
-
return
|
|
91227
|
+
return randomUUID7();
|
|
90594
91228
|
if (given2.length < 1 || given2.length > 128)
|
|
90595
91229
|
return usage4("--idempotency-key must be 1 to 128 characters.");
|
|
90596
91230
|
return given2;
|
|
@@ -91306,7 +91940,7 @@ async function keysRotate(args, ctx) {
|
|
|
91306
91940
|
writeScreenUsageFailure(deps, "--idempotency-key must be 1 to 128 characters.", json);
|
|
91307
91941
|
return 2;
|
|
91308
91942
|
}
|
|
91309
|
-
const idempotencyKey = keyFlag ??
|
|
91943
|
+
const idempotencyKey = keyFlag ?? randomUUID8();
|
|
91310
91944
|
let graceSeconds = now ? 0 : DEFAULT_GRACE_SECONDS;
|
|
91311
91945
|
if (graceFlag !== undefined) {
|
|
91312
91946
|
const grace = parseGrace(graceFlag);
|
|
@@ -91864,7 +92498,7 @@ async function client(ctx) {
|
|
|
91864
92498
|
}, ctx.json);
|
|
91865
92499
|
}
|
|
91866
92500
|
}
|
|
91867
|
-
function
|
|
92501
|
+
function failure3(answer2, ctx, statusRoute = false) {
|
|
91868
92502
|
const { refusal: refusal3 } = answer2;
|
|
91869
92503
|
if (ctx.json && refusal3.body !== undefined)
|
|
91870
92504
|
ctx.deps.stdout.write(`${JSON.stringify(refusal3.body)}
|
|
@@ -91932,7 +92566,7 @@ async function labStatus(args, ctx) {
|
|
|
91932
92566
|
return 1;
|
|
91933
92567
|
const answer2 = await connected.lab.status();
|
|
91934
92568
|
if (!answer2.ok)
|
|
91935
|
-
return
|
|
92569
|
+
return failure3(answer2, ctx, true);
|
|
91936
92570
|
if (ctx.json)
|
|
91937
92571
|
ctx.deps.stdout.write(`${JSON.stringify(answer2.body)}
|
|
91938
92572
|
`);
|
|
@@ -91977,7 +92611,7 @@ async function labReplay(args, ctx) {
|
|
|
91977
92611
|
const id = parsed2.positionals[0];
|
|
91978
92612
|
const answer2 = action === "get" ? await connected2.lab.replayGet(id) : await connected2.lab.replayCancel(id);
|
|
91979
92613
|
if (!answer2.ok)
|
|
91980
|
-
return
|
|
92614
|
+
return failure3(answer2, ctx);
|
|
91981
92615
|
if (action === "get")
|
|
91982
92616
|
return replayOutput(answer2.body, ctx);
|
|
91983
92617
|
if (ctx.json)
|
|
@@ -92043,7 +92677,7 @@ ${REPLAY_USAGE}`);
|
|
|
92043
92677
|
};
|
|
92044
92678
|
const started = await connected.lab.replay(body);
|
|
92045
92679
|
if (!started.ok)
|
|
92046
|
-
return
|
|
92680
|
+
return failure3(started, ctx);
|
|
92047
92681
|
if (parsed.booleans.has("--no-wait")) {
|
|
92048
92682
|
if (ctx.json)
|
|
92049
92683
|
ctx.deps.stdout.write(`${JSON.stringify(started.body)}
|
|
@@ -92053,7 +92687,7 @@ ${REPLAY_USAGE}`);
|
|
|
92053
92687
|
return 0;
|
|
92054
92688
|
}
|
|
92055
92689
|
const result = await connected.lab.waitForReplay(started.body.job_id);
|
|
92056
|
-
return result.ok ? replayOutput(result.body, ctx) :
|
|
92690
|
+
return result.ok ? replayOutput(result.body, ctx) : failure3(result, ctx);
|
|
92057
92691
|
}
|
|
92058
92692
|
async function report(args, ctx, kind) {
|
|
92059
92693
|
const flags = kind === "runners" ? ["--hours", "--min-wallets"] : ["--days", "--min-n"];
|
|
@@ -92078,7 +92712,7 @@ async function report(args, ctx, kind) {
|
|
|
92078
92712
|
return 1;
|
|
92079
92713
|
const answer2 = kind === "runners" ? await connected.lab.runners(q) : await connected.lab.edges(q);
|
|
92080
92714
|
if (!answer2.ok)
|
|
92081
|
-
return
|
|
92715
|
+
return failure3(answer2, ctx);
|
|
92082
92716
|
if (ctx.json)
|
|
92083
92717
|
ctx.deps.stdout.write(`${JSON.stringify(answer2.body)}
|
|
92084
92718
|
`);
|
|
@@ -92116,7 +92750,7 @@ init_zod();
|
|
|
92116
92750
|
init_args();
|
|
92117
92751
|
init_client();
|
|
92118
92752
|
init_evm_lite();
|
|
92119
|
-
import { randomUUID as
|
|
92753
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
92120
92754
|
|
|
92121
92755
|
// src/fee-wallet.ts
|
|
92122
92756
|
init_solana_lite();
|
|
@@ -92206,6 +92840,8 @@ init_solana_lite();
|
|
|
92206
92840
|
var NATIVE_MINT2 = "So11111111111111111111111111111111111111112";
|
|
92207
92841
|
var LAMPORTS_PER_SIGNATURE = 5000n;
|
|
92208
92842
|
var TOKEN_ACCOUNT_RENT_LAMPORTS = 2039280n;
|
|
92843
|
+
var BUYER_TALLY_RENT_LAMPORTS = 1461600n;
|
|
92844
|
+
var REGISTER_BUYER = [144, 92, 137, 46, 209, 220, 27, 164];
|
|
92209
92845
|
var DEFAULT_UNITS_PER_INSTRUCTION = 200000n;
|
|
92210
92846
|
var MAX_COMPUTE_UNITS = 1400000n;
|
|
92211
92847
|
|
|
@@ -92289,7 +92925,7 @@ function readLeg(tx, index, wallet3) {
|
|
|
92289
92925
|
if (program === MEMO_PROGRAM_ID)
|
|
92290
92926
|
continue;
|
|
92291
92927
|
if (program !== undefined)
|
|
92292
|
-
others.push({ program, data: ix.data });
|
|
92928
|
+
others.push({ program, data: ix.data, accounts: accounts.map(key) });
|
|
92293
92929
|
}
|
|
92294
92930
|
if (unitPrice > 0n) {
|
|
92295
92931
|
const units = unitLimit ?? (computed * DEFAULT_UNITS_PER_INSTRUCTION > MAX_COMPUTE_UNITS ? MAX_COMPUTE_UNITS : computed * DEFAULT_UNITS_PER_INSTRUCTION);
|
|
@@ -92297,12 +92933,36 @@ function readLeg(tx, index, wallet3) {
|
|
|
92297
92933
|
}
|
|
92298
92934
|
return { lamports, tokenOut, systemTransfers, others };
|
|
92299
92935
|
}
|
|
92936
|
+
function isRegisterBuyer(data) {
|
|
92937
|
+
return data.length >= 8 && REGISTER_BUYER.every((byte, i) => data[i] === byte);
|
|
92938
|
+
}
|
|
92939
|
+
function readRegisterBuyer(register, swap3, wallet3) {
|
|
92940
|
+
if (register.program !== swap3.program)
|
|
92941
|
+
throw new BundleReadError("leg 1 registers the buyer with another program");
|
|
92942
|
+
if (register.data.length !== 12 || u323(register.data, 8) !== 0n)
|
|
92943
|
+
throw new BundleReadError("leg 1 registers the buyer with a proof");
|
|
92944
|
+
const [buyer, rentPayer, rules, tally] = register.accounts;
|
|
92945
|
+
if (buyer !== wallet3)
|
|
92946
|
+
throw new BundleReadError("leg 1 registers a buyer other than this wallet");
|
|
92947
|
+
if (rules === undefined || tally === undefined || !swap3.accounts.includes(rules) || !swap3.accounts.includes(tally))
|
|
92948
|
+
throw new BundleReadError("leg 1 registers the buyer for another pool than the one it buys");
|
|
92949
|
+
return rentPayer === wallet3 ? BUYER_TALLY_RENT_LAMPORTS : 0n;
|
|
92950
|
+
}
|
|
92300
92951
|
function readLaunchBundle(args) {
|
|
92301
92952
|
const launch = readLeg(decodeLeg(args.legs[0], 0), 0, args.wallet);
|
|
92302
92953
|
const buy = readLeg(decodeLeg(args.legs[1], 1), 1, args.wallet);
|
|
92303
|
-
|
|
92954
|
+
let registerLamports = 0n;
|
|
92955
|
+
const first = buy.others[0];
|
|
92956
|
+
if (first && isRegisterBuyer(first.data)) {
|
|
92957
|
+
if (!args.rules)
|
|
92958
|
+
throw new BundleReadError("leg 1 registers a buyer on a launch without rules");
|
|
92959
|
+
const swap4 = buy.others[1];
|
|
92960
|
+
if (buy.others.length !== 2 || !swap4 || isRegisterBuyer(swap4.data))
|
|
92961
|
+
throw new BundleReadError("leg 1 is not one curve swap");
|
|
92962
|
+
registerLamports = readRegisterBuyer(first, swap4, args.wallet);
|
|
92963
|
+
} else if (buy.others.length !== 1)
|
|
92304
92964
|
throw new BundleReadError("leg 1 is not one curve swap");
|
|
92305
|
-
const swap3 = buy.others[
|
|
92965
|
+
const swap3 = buy.others[buy.others.length - 1];
|
|
92306
92966
|
if (u64(swap3.data, 8) !== args.buyRaw)
|
|
92307
92967
|
throw new BundleReadError("leg 1 does not swap the confirmed --buy");
|
|
92308
92968
|
if (args.minOutRaw !== undefined && u64(swap3.data, 16) !== args.minOutRaw)
|
|
@@ -92322,7 +92982,7 @@ function readLaunchBundle(args) {
|
|
|
92322
92982
|
feeRaw = buy.tokenOut;
|
|
92323
92983
|
quoteRaw = args.buyRaw + buy.tokenOut + launch.tokenOut;
|
|
92324
92984
|
}
|
|
92325
|
-
return { lamports: launch.lamports + buy.lamports, quoteRaw, feeRaw };
|
|
92985
|
+
return { lamports: launch.lamports + buy.lamports + registerLamports, quoteRaw, feeRaw };
|
|
92326
92986
|
}
|
|
92327
92987
|
|
|
92328
92988
|
// src/launch-check.ts
|
|
@@ -92645,7 +93305,7 @@ async function launch(args, ctx) {
|
|
|
92645
93305
|
const flags = parsed.values;
|
|
92646
93306
|
if (flags["--quote-asset"])
|
|
92647
93307
|
flags["--quote-asset"] = flags["--quote-asset"].toLowerCase();
|
|
92648
|
-
const id = flags["--client-trade-id"] ?? `launch-${
|
|
93308
|
+
const id = flags["--client-trade-id"] ?? `launch-${randomUUID9()}`;
|
|
92649
93309
|
const embedded = EMBEDDED_WALLET_NAMES.has(flags["--wallet"] ?? "");
|
|
92650
93310
|
const dryRun = parsed.booleans.has("--dry-run");
|
|
92651
93311
|
const check = parsed.booleans.has("--check");
|
|
@@ -93131,7 +93791,8 @@ async function atomicLaunch(ctx, key, args) {
|
|
|
93131
93791
|
wallet: wallet3.address,
|
|
93132
93792
|
quote: quoteAsset,
|
|
93133
93793
|
buyRaw: BigInt(buy.raw),
|
|
93134
|
-
...minOut !== undefined ? { minOutRaw: BigInt(minOut) } : {}
|
|
93794
|
+
...minOut !== undefined ? { minOutRaw: BigInt(minOut) } : {},
|
|
93795
|
+
rules: args.rules !== undefined
|
|
93135
93796
|
});
|
|
93136
93797
|
} catch (error) {
|
|
93137
93798
|
if (!(error instanceof BundleReadError))
|
|
@@ -93633,7 +94294,7 @@ init_render();
|
|
|
93633
94294
|
init_solana_endpoint();
|
|
93634
94295
|
init_solana_lite();
|
|
93635
94296
|
init_trading();
|
|
93636
|
-
import { randomUUID as
|
|
94297
|
+
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
93637
94298
|
init_swap();
|
|
93638
94299
|
var LP_SCOPE = "lp:write";
|
|
93639
94300
|
var CONFIRM_POLL_MS3 = 2000;
|
|
@@ -93936,7 +94597,7 @@ async function lpAdd(args, ctx) {
|
|
|
93936
94597
|
if ("error" in parsed)
|
|
93937
94598
|
return usage5(ctx, parsed.error);
|
|
93938
94599
|
const flags = parsed.values;
|
|
93939
|
-
const id = flags["--client-trade-id"] ?? `lp-${
|
|
94600
|
+
const id = flags["--client-trade-id"] ?? `lp-${randomUUID10()}`;
|
|
93940
94601
|
if (parsed.positionals.length !== 2 || !flags["--amount"] || !flags["--wallet"] || !validClientId(id))
|
|
93941
94602
|
return usage5(ctx, "Usage: candle lp add <pool> --amount <decimal> <token> --wallet <tee> [--position <nft-mint>] [--slippage-bps 100] [--client-trade-id <id>] [--rpc-url <url>] [--yes]");
|
|
93942
94603
|
try {
|
|
@@ -93972,7 +94633,7 @@ async function lpRemove(args, ctx) {
|
|
|
93972
94633
|
if ("error" in parsed)
|
|
93973
94634
|
return usage5(ctx, parsed.error);
|
|
93974
94635
|
const flags = parsed.values;
|
|
93975
|
-
const id = flags["--client-trade-id"] ?? `lp-${
|
|
94636
|
+
const id = flags["--client-trade-id"] ?? `lp-${randomUUID10()}`;
|
|
93976
94637
|
const percent2 = Number(flags["--percent"]);
|
|
93977
94638
|
if (parsed.positionals.length !== 1 || !flags["--percent"] || !/^\d+(\.\d{1,2})?$/.test(flags["--percent"]) || !(percent2 > 0 && percent2 <= 100) || !validClientId(id))
|
|
93978
94639
|
return usage5(ctx, "Usage: candle lp remove <position> --percent <0.01-100> [--wallet <tee>] [--slippage-bps 100] [--client-trade-id <id>] [--rpc-url <url>] [--yes]");
|
|
@@ -94004,7 +94665,7 @@ async function lpClaim(args, ctx) {
|
|
|
94004
94665
|
if ("error" in parsed)
|
|
94005
94666
|
return usage5(ctx, parsed.error);
|
|
94006
94667
|
const flags = parsed.values;
|
|
94007
|
-
const id = flags["--client-trade-id"] ?? `lp-${
|
|
94668
|
+
const id = flags["--client-trade-id"] ?? `lp-${randomUUID10()}`;
|
|
94008
94669
|
if (parsed.positionals.length !== 1 || !validClientId(id))
|
|
94009
94670
|
return usage5(ctx, "Usage: candle lp claim <position> [--wallet <tee>] [--client-trade-id <id>] [--rpc-url <url>] [--yes]");
|
|
94010
94671
|
try {
|
|
@@ -94038,6 +94699,7 @@ var MCP_TOOL_NAMES = [
|
|
|
94038
94699
|
"candle_launch_token",
|
|
94039
94700
|
"candle_launch_and_seed",
|
|
94040
94701
|
"candle_get_market",
|
|
94702
|
+
"candle_quote",
|
|
94041
94703
|
"candle_get_feed",
|
|
94042
94704
|
"candle_vamp_candidates",
|
|
94043
94705
|
"candle_token_events",
|
|
@@ -94074,10 +94736,13 @@ var MCP_TOOL_NAMES = [
|
|
|
94074
94736
|
"candle_get_plans",
|
|
94075
94737
|
"candle_report_activity",
|
|
94076
94738
|
"candle_trade",
|
|
94739
|
+
"candle_trade_execute",
|
|
94077
94740
|
"candle_swap",
|
|
94078
94741
|
"candle_transfer",
|
|
94079
94742
|
"candle_sweep",
|
|
94080
94743
|
"candle_get_wallets",
|
|
94744
|
+
"candle_get_trading_wallets",
|
|
94745
|
+
"candle_get_spendable_balances",
|
|
94081
94746
|
"candle_get_profile_wallets",
|
|
94082
94747
|
"candle_set_profile_wallets",
|
|
94083
94748
|
"candle_get_profile_pnl",
|
|
@@ -94206,7 +94871,8 @@ async function mcp(args, ctx) {
|
|
|
94206
94871
|
}
|
|
94207
94872
|
const identityConfig = await deps.readConfig();
|
|
94208
94873
|
const identityFields = effectiveProfileFields(identityConfig, ctx.profile);
|
|
94209
|
-
|
|
94874
|
+
const identityOverrides = credentialEnvOverrides(deps.env);
|
|
94875
|
+
deps.stderr.write(`${identityLine(ctx.profile, identityOverrides.length > 0 ? undefined : identityFields.account, apiUrl, identityOverrides.length > 0 ? identityOverrides : undefined, identityFields.username, identityStyle(deps, "stderr"))}
|
|
94210
94876
|
|
|
94211
94877
|
`);
|
|
94212
94878
|
if (parsed.booleans.has("--print-config")) {
|
|
@@ -94253,7 +94919,7 @@ init_bridge();
|
|
|
94253
94919
|
init_client();
|
|
94254
94920
|
init_evm_lite();
|
|
94255
94921
|
init_human_output();
|
|
94256
|
-
import { randomUUID as
|
|
94922
|
+
import { randomUUID as randomUUID11, sign as sign4 } from "node:crypto";
|
|
94257
94923
|
|
|
94258
94924
|
// src/hyperliquid.ts
|
|
94259
94925
|
init_sha3();
|
|
@@ -98166,7 +98832,7 @@ async function perpsDeposit(args, ctx) {
|
|
|
98166
98832
|
if (parsed.positionals.length !== 2 || !amount || !DEC_RE3.test(amount) || !isDepositAsset(asset))
|
|
98167
98833
|
return usage8(ctx, DEPOSIT_USAGE);
|
|
98168
98834
|
const { values } = parsed;
|
|
98169
|
-
const id = values["--id"] ?? `deposit-${
|
|
98835
|
+
const id = values["--id"] ?? `deposit-${randomUUID11()}`;
|
|
98170
98836
|
if (!DEPOSIT_ID_RE.test(id))
|
|
98171
98837
|
return usage8(ctx, "--id must be 1 to 128 letters, digits, '.', '_', ':' or '-'.");
|
|
98172
98838
|
const slippageBps = Number(values["--slippage-bps"] ?? "100");
|
|
@@ -99305,8 +99971,8 @@ async function read2(ctx, argv, command) {
|
|
|
99305
99971
|
writeUsageFailure(ctx.deps, error.message, ctx.json);
|
|
99306
99972
|
return 2;
|
|
99307
99973
|
}
|
|
99308
|
-
const
|
|
99309
|
-
writeLocalFailure(ctx.deps,
|
|
99974
|
+
const failure4 = { code: error.code, message: error.message, retryable: error.retryable };
|
|
99975
|
+
writeLocalFailure(ctx.deps, failure4, ctx.json);
|
|
99310
99976
|
return 1;
|
|
99311
99977
|
}
|
|
99312
99978
|
return tradingFailure(ctx, error);
|
|
@@ -99320,7 +99986,7 @@ var perpsCandles = (argv, ctx) => read2(ctx, argv, "candles");
|
|
|
99320
99986
|
// src/commands/perps-withdraw.ts
|
|
99321
99987
|
init_args();
|
|
99322
99988
|
init_human_output();
|
|
99323
|
-
import { randomUUID as
|
|
99989
|
+
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
99324
99990
|
|
|
99325
99991
|
// src/hyperliquid-withdraw.ts
|
|
99326
99992
|
var CANDLE_PERPS_WITHDRAW_PINS = {
|
|
@@ -99564,7 +100230,7 @@ async function perpsWithdraw(args, ctx) {
|
|
|
99564
100230
|
const amountMicros = parseWithdrawAmount(amount);
|
|
99565
100231
|
if (amountMicros === null)
|
|
99566
100232
|
return usage10(ctx, "The amount must be a positive USDC decimal with at most 6 decimals.");
|
|
99567
|
-
const id = parsed.values["--id"] ?? `withdraw-${
|
|
100233
|
+
const id = parsed.values["--id"] ?? `withdraw-${randomUUID12()}`;
|
|
99568
100234
|
if (!ID_RE.test(id))
|
|
99569
100235
|
return usage10(ctx, "--id must be 1 to 128 letters, digits, '.', '_', ':' or '-'.");
|
|
99570
100236
|
if (!refuseEnvPassphrase(ctx))
|
|
@@ -99703,7 +100369,7 @@ init_render();
|
|
|
99703
100369
|
init_secret_store();
|
|
99704
100370
|
init_solana_lite();
|
|
99705
100371
|
init_errors2();
|
|
99706
|
-
import { generateKeyPairSync as generateKeyPairSync3, randomUUID as
|
|
100372
|
+
import { generateKeyPairSync as generateKeyPairSync3, randomUUID as randomUUID14 } from "node:crypto";
|
|
99707
100373
|
|
|
99708
100374
|
// src/x402/codec.ts
|
|
99709
100375
|
var PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED";
|
|
@@ -100313,7 +100979,7 @@ init_deps();
|
|
|
100313
100979
|
init_render();
|
|
100314
100980
|
init_solana_endpoint();
|
|
100315
100981
|
init_solana_lite();
|
|
100316
|
-
import { randomUUID as
|
|
100982
|
+
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
100317
100983
|
|
|
100318
100984
|
// src/x402/pay.ts
|
|
100319
100985
|
init_client();
|
|
@@ -100661,7 +101327,7 @@ async function runPaidRead(ctx, input) {
|
|
|
100661
101327
|
blockhash: blockhashFor(ctx),
|
|
100662
101328
|
env: ctx.deps.env,
|
|
100663
101329
|
now: ctx.deps.now,
|
|
100664
|
-
newId:
|
|
101330
|
+
newId: randomUUID13
|
|
100665
101331
|
});
|
|
100666
101332
|
const line = paymentLine(paid);
|
|
100667
101333
|
if (line && !ctx.json)
|
|
@@ -101273,7 +101939,7 @@ async function runPlanPayment(ctx, run2) {
|
|
|
101273
101939
|
const transaction = serializePartiallySigned(transfer, signer.sign(transfer.message));
|
|
101274
101940
|
const at3 = new Date(deps.now()).toISOString();
|
|
101275
101941
|
const pending = {
|
|
101276
|
-
id:
|
|
101942
|
+
id: randomUUID14(),
|
|
101277
101943
|
plan: run2.plan,
|
|
101278
101944
|
renew,
|
|
101279
101945
|
path,
|
|
@@ -101584,7 +102250,8 @@ var PLAN_CAPABILITY_LABELS2 = {
|
|
|
101584
102250
|
createLinkedWallets: "Create linked wallets",
|
|
101585
102251
|
importLinkedWallets: "Import linked wallets",
|
|
101586
102252
|
limitOrders: "Limit orders",
|
|
101587
|
-
quant: "Quant deep analyses (Max)"
|
|
102253
|
+
quant: "Quant deep analyses (Max)",
|
|
102254
|
+
strategyLab: "Strategy lab (saved strategies stay Max)"
|
|
101588
102255
|
};
|
|
101589
102256
|
var PLAN_LABELS2 = { free: "Free", believer: "Believer", pro: "Pro", max: "Max" };
|
|
101590
102257
|
function planLabel2(plan2) {
|
|
@@ -101613,6 +102280,9 @@ function planTableRows2(table) {
|
|
|
101613
102280
|
row2("Uploads per minute", (e) => count2(e.limits.uploadsPerMin)),
|
|
101614
102281
|
row2("Linked wallets", (e) => count2(e.limits.linkedWallets))
|
|
101615
102282
|
];
|
|
102283
|
+
if (plans.some((e) => typeof e.limits.labReplaysPerDay === "number")) {
|
|
102284
|
+
rows.push(row2("Strategy lab replays", (e) => typeof e.limits.labReplaysPerDay === "number" ? `${count2(e.limits.labReplaysPerDay)} a day` : "-"));
|
|
102285
|
+
}
|
|
101616
102286
|
const known = Object.keys(PLAN_CAPABILITY_LABELS2);
|
|
101617
102287
|
const served = new Set;
|
|
101618
102288
|
for (const entry of plans)
|
|
@@ -102036,15 +102706,14 @@ async function runPlugin(name2, rawArgs, ctx) {
|
|
|
102036
102706
|
if (split2.wallets.length > 0) {
|
|
102037
102707
|
if (!refuseEnvPassphrase(ctx))
|
|
102038
102708
|
return 1;
|
|
102039
|
-
|
|
102040
|
-
return 1;
|
|
102709
|
+
const promptCtx = refusePromptsWithoutTerminal(ctx, "candle <plugin> --wallet");
|
|
102041
102710
|
const resolvedVault = vaultPathFor(ctx, { values: {}, booleans: new Set, positionals: [] });
|
|
102042
102711
|
if ("error" in resolvedVault)
|
|
102043
102712
|
return usage(ctx, resolvedVault.error);
|
|
102044
102713
|
const vaultPath = resolvedVault.path;
|
|
102045
102714
|
const resolved = await runVaultCommand(ctx, async ({ hold }) => {
|
|
102046
102715
|
const raw = await requireVaultRaw(ctx, resolvedVault);
|
|
102047
|
-
const vault = hold((await unlockInteractively(
|
|
102716
|
+
const vault = hold((await unlockInteractively(promptCtx, vaultPath, raw)).vault);
|
|
102048
102717
|
for (const requested of split2.wallets) {
|
|
102049
102718
|
const entry = findExternalEntry(vault.index, requested);
|
|
102050
102719
|
if (entry === undefined) {
|
|
@@ -102734,14 +103403,20 @@ var NO_API_KEY6 = {
|
|
|
102734
103403
|
message: "No API key for this profile.",
|
|
102735
103404
|
suggestion: "Set CANDLE_API_KEY, or run `candle auth login` to store one."
|
|
102736
103405
|
};
|
|
103406
|
+
var NO_TERMINAL_REASON = "not read: no terminal for its unlock";
|
|
103407
|
+
var NO_TERMINAL_NOTE = "The vault was not read: its unlock needs a prompt, and standard input and standard error are not both a terminal. Run it at a terminal, pass --factor touch-id or --factor passkey to unlock without a prompt, or pass --no-vault to leave it out.";
|
|
102737
103408
|
async function portfolio(args, ctx) {
|
|
102738
103409
|
const parsed = parseArgs(args, {
|
|
102739
103410
|
valueFlags: ["--rpc-url", "--evm-rpc-url", "--keystore", "--chain"],
|
|
102740
|
-
booleanFlags: ["--no-perps"],
|
|
103411
|
+
booleanFlags: ["--no-perps", "--no-vault"],
|
|
102741
103412
|
pathFlags: ["--keystore"]
|
|
102742
103413
|
});
|
|
102743
103414
|
if ("error" in parsed)
|
|
102744
103415
|
return usage(ctx, parsed.error);
|
|
103416
|
+
const noVault = parsed.booleans.has("--no-vault");
|
|
103417
|
+
if (noVault && parsed.values["--keystore"] !== undefined) {
|
|
103418
|
+
return usage(ctx, "--keystore names the vault to read and has no effect with --no-vault.");
|
|
103419
|
+
}
|
|
102745
103420
|
if (parsed.positionals.length > 1)
|
|
102746
103421
|
return usage(ctx, `Unexpected argument: ${parsed.positionals[1]}`);
|
|
102747
103422
|
const filter = parsed.positionals[0];
|
|
@@ -102788,16 +103463,30 @@ async function portfolio(args, ctx) {
|
|
|
102788
103463
|
let vaultEntries;
|
|
102789
103464
|
let evmEntries;
|
|
102790
103465
|
let vaultReason;
|
|
103466
|
+
let vaultSkipped = false;
|
|
102791
103467
|
let known = 0;
|
|
102792
|
-
const raw = await readVaultRaw(resolvedVault.path);
|
|
102793
|
-
|
|
102794
|
-
|
|
102795
|
-
} else {
|
|
103468
|
+
const raw = noVault ? null : await readVaultRaw(resolvedVault.path);
|
|
103469
|
+
let vault;
|
|
103470
|
+
if (raw !== null) {
|
|
102796
103471
|
if (!refuseEnvPassphrase(ctx))
|
|
102797
103472
|
return 1;
|
|
102798
|
-
|
|
102799
|
-
|
|
102800
|
-
|
|
103473
|
+
try {
|
|
103474
|
+
const unlockCtx = refusePromptsWithoutTerminal(ctx, "portfolio");
|
|
103475
|
+
vault = hold((await unlockInteractively(unlockCtx, resolvedVault.path, raw)).vault);
|
|
103476
|
+
} catch (error) {
|
|
103477
|
+
if (!isNoTerminalRefusal(error))
|
|
103478
|
+
throw error;
|
|
103479
|
+
}
|
|
103480
|
+
}
|
|
103481
|
+
if (noVault) {
|
|
103482
|
+
vaultReason = "not read (--no-vault)";
|
|
103483
|
+
} else if (raw === null) {
|
|
103484
|
+
vaultReason = `no vault at ${resolvedVault.path}`;
|
|
103485
|
+
} else if (vault === undefined) {
|
|
103486
|
+
vaultReason = NO_TERMINAL_REASON;
|
|
103487
|
+
vaultSkipped = true;
|
|
103488
|
+
notice(deps, "stderr", "note", NO_TERMINAL_NOTE);
|
|
103489
|
+
} else {
|
|
102801
103490
|
vaultEntries = showSolana ? vault.index.entries.filter((entry) => entry.chain === "solana" && (entry.role === "vault" || entry.role === "external")).map((entry) => ({ address: entry.address, label: entry.label, role: entry.role })) : [];
|
|
102802
103491
|
evmEntries = showHood ? vault.index.entries.filter((entry) => entry.chain === "evm" && entry.role === "vault").map((entry) => ({ address: entry.address, label: entry.label })) : [];
|
|
102803
103492
|
known += vaultEntries.length + evmEntries.length;
|
|
@@ -103012,7 +103701,7 @@ async function portfolio(args, ctx) {
|
|
|
103012
103701
|
const listStatus = walletListStatus(answered);
|
|
103013
103702
|
const shownUnread = unavailable6.length > 0 || lpUnread > 0;
|
|
103014
103703
|
const listNote = listStatus === "cut-off" ? "cut-off" : listStatus === "unknown" && !shownUnread ? "unknown" : null;
|
|
103015
|
-
const complete = listNote === null && !shownUnread && !perpsPartial;
|
|
103704
|
+
const complete = listNote === null && !shownUnread && !perpsPartial && !vaultSkipped;
|
|
103016
103705
|
const totalUsd = groups.reduce((sum, g) => sum + g.valueUsd, 0);
|
|
103017
103706
|
const unpriced = groups.reduce((sum, g) => sum + g.unpriced, 0);
|
|
103018
103707
|
const lp = lpSection ? {
|
|
@@ -103089,6 +103778,7 @@ async function portfolio(args, ctx) {
|
|
|
103089
103778
|
byChain,
|
|
103090
103779
|
unavailableByChain,
|
|
103091
103780
|
listNote,
|
|
103781
|
+
vaultSkipped,
|
|
103092
103782
|
lp
|
|
103093
103783
|
});
|
|
103094
103784
|
if (perps)
|
|
@@ -103175,12 +103865,12 @@ async function startEvmRead(addresses, rpcUrl, fetchFn) {
|
|
|
103175
103865
|
try {
|
|
103176
103866
|
chainId = await rpc.chainId();
|
|
103177
103867
|
} catch (error) {
|
|
103178
|
-
const
|
|
103868
|
+
const failure4 = message(error);
|
|
103179
103869
|
return {
|
|
103180
103870
|
balances: async () => ({
|
|
103181
103871
|
byAddress: new Map(unique.map((address) => [address, { wei: null, usdg: null }])),
|
|
103182
103872
|
unavailable: unique,
|
|
103183
|
-
failure:
|
|
103873
|
+
failure: failure4
|
|
103184
103874
|
})
|
|
103185
103875
|
};
|
|
103186
103876
|
}
|
|
@@ -103192,7 +103882,7 @@ async function startEvmRead(addresses, rpcUrl, fetchFn) {
|
|
|
103192
103882
|
return {
|
|
103193
103883
|
balances: async () => {
|
|
103194
103884
|
const byAddress = new Map;
|
|
103195
|
-
let
|
|
103885
|
+
let failure4;
|
|
103196
103886
|
let next = 0;
|
|
103197
103887
|
await Promise.all(Array.from({ length: Math.min(EVM_READS_IN_FLIGHT, unique.length) }, async () => {
|
|
103198
103888
|
while (next < unique.length) {
|
|
@@ -103201,7 +103891,7 @@ async function startEvmRead(addresses, rpcUrl, fetchFn) {
|
|
|
103201
103891
|
try {
|
|
103202
103892
|
return await call2();
|
|
103203
103893
|
} catch (error) {
|
|
103204
|
-
|
|
103894
|
+
failure4 ??= message(error);
|
|
103205
103895
|
return null;
|
|
103206
103896
|
}
|
|
103207
103897
|
};
|
|
@@ -103214,7 +103904,7 @@ async function startEvmRead(addresses, rpcUrl, fetchFn) {
|
|
|
103214
103904
|
const read3 = byAddress.get(address);
|
|
103215
103905
|
return read3 === undefined || read3.wei === null || read3.usdg === null;
|
|
103216
103906
|
});
|
|
103217
|
-
return { byAddress, unavailable: unavailable6, ...
|
|
103907
|
+
return { byAddress, unavailable: unavailable6, ...failure4 === undefined ? {} : { failure: failure4 } };
|
|
103218
103908
|
}
|
|
103219
103909
|
};
|
|
103220
103910
|
}
|
|
@@ -103223,13 +103913,13 @@ async function readOwnRpc(addresses, solana, ctx) {
|
|
|
103223
103913
|
const unique = [...new Set(addresses)];
|
|
103224
103914
|
const lamports = new Map;
|
|
103225
103915
|
let rateLimited = false;
|
|
103226
|
-
let
|
|
103916
|
+
let failure4;
|
|
103227
103917
|
const noteFailure = (error) => {
|
|
103228
103918
|
if (isRateLimited(error)) {
|
|
103229
103919
|
rateLimited = true;
|
|
103230
|
-
|
|
103920
|
+
failure4 ??= rateLimitedReadFailure(ctx, error);
|
|
103231
103921
|
} else
|
|
103232
|
-
|
|
103922
|
+
failure4 ??= describeRpcFailure(error);
|
|
103233
103923
|
};
|
|
103234
103924
|
for (let at3 = 0;at3 < unique.length; at3 += CHUNK) {
|
|
103235
103925
|
const chunk = unique.slice(at3, at3 + CHUNK);
|
|
@@ -103291,7 +103981,7 @@ async function readOwnRpc(addresses, solana, ctx) {
|
|
|
103291
103981
|
if (sol === null || held === null)
|
|
103292
103982
|
unavailable6.push(address);
|
|
103293
103983
|
}
|
|
103294
|
-
return { byAddress, unavailable: unavailable6, ...
|
|
103984
|
+
return { byAddress, unavailable: unavailable6, ...failure4 === undefined ? {} : { failure: failure4 } };
|
|
103295
103985
|
}
|
|
103296
103986
|
function solanaHoldings(read3, prices) {
|
|
103297
103987
|
if (read3 === undefined || read3.lamports === null && read3.tokens === null)
|
|
@@ -103464,7 +104154,7 @@ ${tableBlock(ctx, "LP positions", ["GROUP", "WALLET", "POSITION", "POOL", "HOLDI
|
|
|
103464
104154
|
notes.push([
|
|
103465
104155
|
"total",
|
|
103466
104156
|
formatUsd(totals.totalUsd),
|
|
103467
|
-
totals.unpriced > 0 ? `${totals.unpriced} unpriced ${totals.unpriced === 1 ? "holding" : "holdings"} not counted` : "every holding priced"
|
|
104157
|
+
(totals.vaultSkipped ? "partial: the vault was not read (no terminal for its unlock). " : "") + (totals.unpriced > 0 ? `${totals.unpriced} unpriced ${totals.unpriced === 1 ? "holding" : "holdings"} not counted` : totals.vaultSkipped ? "other holdings priced" : "every holding priced")
|
|
103468
104158
|
]);
|
|
103469
104159
|
const style = terminalStyle(deps, "stdout");
|
|
103470
104160
|
const width = Math.max(...notes.map(([label]) => displayWidth(label)));
|
|
@@ -103486,6 +104176,8 @@ ${tableBlock(ctx, "LP positions", ["GROUP", "WALLET", "POSITION", "POOL", "HOLDI
|
|
|
103486
104176
|
if (unavailable6 > 0) {
|
|
103487
104177
|
warnings.push(`${unavailable6} ${unavailable6 === 1 ? "wallet" : "wallets"} could not be read in full (${unreadByChain.map(([chain2, n]) => `${n} on ${CHAIN_NAMES[chain2]}`).join(", ")}). What was not read is marked "not read" and is not in the total.`);
|
|
103488
104178
|
}
|
|
104179
|
+
if (totals.vaultSkipped)
|
|
104180
|
+
warnings.push("The vault was not read: no terminal for its unlock. Its holdings are not in the total, so the total is partial (exit 3). Run it in a terminal, or pass --no-vault to leave the vault out on purpose.");
|
|
103489
104181
|
writeNotes2(ctx, warnings);
|
|
103490
104182
|
writeListNote(ctx, totals.listNote);
|
|
103491
104183
|
const later = [];
|
|
@@ -103539,6 +104231,7 @@ ${financialHeading("Hyperliquid (live from Hyperliquid, not in the ledger; not i
|
|
|
103539
104231
|
}
|
|
103540
104232
|
|
|
103541
104233
|
// src/commands/profile.ts
|
|
104234
|
+
init_account();
|
|
103542
104235
|
init_args();
|
|
103543
104236
|
init_client();
|
|
103544
104237
|
init_human_output();
|
|
@@ -103718,17 +104411,17 @@ async function profileUse(args, ctx) {
|
|
|
103718
104411
|
let account = profile.account;
|
|
103719
104412
|
let username = profile.username;
|
|
103720
104413
|
if (apiKey) {
|
|
103721
|
-
const { account: live, username: liveUsername, failure:
|
|
104414
|
+
const { account: live, username: liveUsername, failure: failure4 } = await fetchAccount(deps, apiUrl, apiKey);
|
|
103722
104415
|
if (live) {
|
|
103723
104416
|
account = live;
|
|
103724
104417
|
username = liveUsername;
|
|
103725
104418
|
await deps.updateProfile(name2, { account: live, username: liveUsername, accountCachedAt: deps.now() });
|
|
103726
104419
|
} else {
|
|
103727
104420
|
if (json)
|
|
103728
|
-
notice(deps, "stderr", "warning", `Could not refresh the account for ${name2} (${
|
|
104421
|
+
notice(deps, "stderr", "warning", `Could not refresh the account for ${name2} (${failure4}); keeping the cached value.
|
|
103729
104422
|
`.trim());
|
|
103730
104423
|
else
|
|
103731
|
-
notice(deps, "stderr", "warning", `Could not refresh the account for ${name2} (${
|
|
104424
|
+
notice(deps, "stderr", "warning", `Could not refresh the account for ${name2} (${failure4}); keeping the cached value.`);
|
|
103732
104425
|
}
|
|
103733
104426
|
} else {
|
|
103734
104427
|
if (json)
|
|
@@ -104062,8 +104755,8 @@ async function quantAsk(args, ctx) {
|
|
|
104062
104755
|
if (turnId === undefined) {
|
|
104063
104756
|
if (reason === "interrupted")
|
|
104064
104757
|
return await stopped(ctx, client2, undefined, reason, timeoutS);
|
|
104065
|
-
const
|
|
104066
|
-
writeScreenLocalFailure(ctx.deps,
|
|
104758
|
+
const failure4 = refusal3?.status === 402 && offer ? offerFailure(offer.status, offer.headers, offer.body) : turnRefusal(refusal3 ?? { code: "BAD_RESPONSE", status: 0, shape: "transport" });
|
|
104759
|
+
writeScreenLocalFailure(ctx.deps, failure4, ctx.json);
|
|
104067
104760
|
return 1;
|
|
104068
104761
|
}
|
|
104069
104762
|
if (stop.signal.aborted) {
|
|
@@ -104669,8 +105362,7 @@ async function sign5(args, ctx) {
|
|
|
104669
105362
|
return usage(ctx, solana.error);
|
|
104670
105363
|
if (!refuseEnvPassphrase(ctx))
|
|
104671
105364
|
return 1;
|
|
104672
|
-
|
|
104673
|
-
return 1;
|
|
105365
|
+
const promptCtx = refusePromptsWithoutTerminal(ctx, "candle sign");
|
|
104674
105366
|
const { deps } = ctx;
|
|
104675
105367
|
const resolvedVault = vaultPathFor(ctx, parsed);
|
|
104676
105368
|
if ("error" in resolvedVault)
|
|
@@ -104700,7 +105392,7 @@ async function sign5(args, ctx) {
|
|
|
104700
105392
|
throw error;
|
|
104701
105393
|
}
|
|
104702
105394
|
const raw = await requireVaultRaw(ctx, resolvedVault);
|
|
104703
|
-
const opened = await unlockInteractively(
|
|
105395
|
+
const opened = await unlockInteractively(promptCtx, path, raw, {
|
|
104704
105396
|
acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
|
|
104705
105397
|
});
|
|
104706
105398
|
const vault = hold(opened.vault);
|
|
@@ -104859,8 +105551,7 @@ async function signMessage2(args, ctx) {
|
|
|
104859
105551
|
const requested = lifted.values[0];
|
|
104860
105552
|
if (!refuseEnvPassphrase(ctx))
|
|
104861
105553
|
return 1;
|
|
104862
|
-
|
|
104863
|
-
return 1;
|
|
105554
|
+
const promptCtx = refusePromptsWithoutTerminal(ctx, "candle sign message");
|
|
104864
105555
|
const { deps } = ctx;
|
|
104865
105556
|
const resolvedVault = vaultPathFor(ctx, parsed);
|
|
104866
105557
|
if ("error" in resolvedVault)
|
|
@@ -104874,7 +105565,7 @@ async function signMessage2(args, ctx) {
|
|
|
104874
105565
|
throw new VaultError("VAULT_UNREADABLE", `The message could not be read: ${error instanceof Error ? error.message : error}.`);
|
|
104875
105566
|
}
|
|
104876
105567
|
const raw = await requireVaultRaw(ctx, resolvedVault);
|
|
104877
|
-
const opened = await unlockInteractively(
|
|
105568
|
+
const opened = await unlockInteractively(promptCtx, path, raw, {
|
|
104878
105569
|
acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
|
|
104879
105570
|
});
|
|
104880
105571
|
const vault = hold(opened.vault);
|
|
@@ -105197,7 +105888,7 @@ init_presentation();
|
|
|
105197
105888
|
init_render();
|
|
105198
105889
|
init_trading();
|
|
105199
105890
|
init_swap();
|
|
105200
|
-
import { randomUUID as
|
|
105891
|
+
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
105201
105892
|
var withdrawal = exports_external.object({ withdrawal: exports_external.string(), amountRaw: exports_external.string(), endTime: exports_external.string(), claimable: exports_external.boolean() }).passthrough();
|
|
105202
105893
|
var positionsSchema = exports_external.object({
|
|
105203
105894
|
success: exports_external.literal(true),
|
|
@@ -105252,7 +105943,7 @@ async function runStake(action, args, ctx) {
|
|
|
105252
105943
|
return 2;
|
|
105253
105944
|
}
|
|
105254
105945
|
const { values, positionals, booleans } = parsed;
|
|
105255
|
-
const id = values["--client-stake-id"] ?? `stake-${
|
|
105946
|
+
const id = values["--client-stake-id"] ?? `stake-${randomUUID15()}`;
|
|
105256
105947
|
const all = booleans.has("--all");
|
|
105257
105948
|
if (!validClientId(id) || id.length > 128 || (action === "positions" ? positionals.length !== 0 : action === "finish" ? positionals.length > 1 : action === "claim" ? all ? positionals.length !== 0 : positionals.length !== 1 : positionals.length !== 1)) {
|
|
105258
105949
|
writeUsageFailure(ctx.deps, usage12, ctx.json);
|
|
@@ -105642,8 +106333,7 @@ async function vaultRename(args, ctx) {
|
|
|
105642
106333
|
const id = parsed.values["--id"];
|
|
105643
106334
|
if (!refuseEnvPassphrase(ctx))
|
|
105644
106335
|
return 1;
|
|
105645
|
-
|
|
105646
|
-
return 1;
|
|
106336
|
+
const promptCtx = refusePromptsWithoutTerminal(ctx, "vault rename");
|
|
105647
106337
|
const { deps } = ctx;
|
|
105648
106338
|
const resolvedVault = vaultPathFor(ctx, parsed);
|
|
105649
106339
|
if ("error" in resolvedVault)
|
|
@@ -105664,7 +106354,7 @@ async function vaultRename(args, ctx) {
|
|
|
105664
106354
|
}
|
|
105665
106355
|
return runVaultCommand(ctx, async ({ hold }) => {
|
|
105666
106356
|
const raw = await requireVaultRaw(ctx, resolvedVault);
|
|
105667
|
-
const opened = await unlockInteractively(
|
|
106357
|
+
const opened = await unlockInteractively(promptCtx, path, raw, {
|
|
105668
106358
|
acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
|
|
105669
106359
|
});
|
|
105670
106360
|
const vault = hold(opened.vault);
|
|
@@ -105935,10 +106625,9 @@ async function teeRename(args, ctx) {
|
|
|
105935
106625
|
if ("error" in vaultPath)
|
|
105936
106626
|
return usage(ctx, vaultPath.error);
|
|
105937
106627
|
const raw = rows.some((row2) => row2.tee) ? await readVaultRaw(vaultPath.path) : null;
|
|
105938
|
-
|
|
105939
|
-
return 1;
|
|
106628
|
+
const promptCtx = refusePromptsWithoutTerminal(ctx, "tee rename");
|
|
105940
106629
|
return runVaultCommand(ctx, async ({ hold }) => {
|
|
105941
|
-
const vault = raw === null ? null : hold((await unlockInteractively(
|
|
106630
|
+
const vault = raw === null ? null : hold((await unlockInteractively(promptCtx, vaultPath.path, raw, {
|
|
105942
106631
|
acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
|
|
105943
106632
|
})).vault);
|
|
105944
106633
|
const local = [];
|
|
@@ -106344,7 +107033,7 @@ init_args();
|
|
|
106344
107033
|
init_client();
|
|
106345
107034
|
init_deps();
|
|
106346
107035
|
init_human_output();
|
|
106347
|
-
import { randomUUID as
|
|
107036
|
+
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
106348
107037
|
init_render();
|
|
106349
107038
|
var EXIT_OUTCOME_UNKNOWN2 = 3;
|
|
106350
107039
|
var EDIT_FIELDS = launchFlagFields.filter((field) => field.key === "description" || field.key.startsWith("socials."));
|
|
@@ -106470,7 +107159,7 @@ async function tokenEdit(args, ctx) {
|
|
|
106470
107159
|
if (given2 !== undefined && (given2.length < 1 || given2.length > 128)) {
|
|
106471
107160
|
return usage12("--idempotency-key must be 1 to 128 characters.");
|
|
106472
107161
|
}
|
|
106473
|
-
const idempotencyKey = given2 ??
|
|
107162
|
+
const idempotencyKey = given2 ?? randomUUID16();
|
|
106474
107163
|
const apiKey = await resolveApiKey(ctx.deps, ctx.profile);
|
|
106475
107164
|
if (!apiKey) {
|
|
106476
107165
|
writeLocalFailure(ctx.deps, {
|
|
@@ -106495,17 +107184,17 @@ async function tokenEdit(args, ctx) {
|
|
|
106495
107184
|
return EXIT_OUTCOME_UNKNOWN2;
|
|
106496
107185
|
}
|
|
106497
107186
|
if (!result.ok) {
|
|
106498
|
-
const
|
|
107187
|
+
const failure4 = { apiUrl: ctx.apiUrl, authType: "key" };
|
|
106499
107188
|
if (!unsettledCommit && !mayHaveCommitted(result)) {
|
|
106500
|
-
writeFailure(ctx.deps, result,
|
|
107189
|
+
writeFailure(ctx.deps, result, failure4, ctx.json);
|
|
106501
107190
|
return 1;
|
|
106502
107191
|
}
|
|
106503
107192
|
const retry = `Rerun the same command with --idempotency-key ${idempotencyKey}: Candle returns the first result instead of editing twice.`;
|
|
106504
107193
|
if (ctx.json) {
|
|
106505
|
-
ctx.deps.stdout.write(`${JSON.stringify({ ...errorEnvelope(result,
|
|
107194
|
+
ctx.deps.stdout.write(`${JSON.stringify({ ...errorEnvelope(result, failure4), idempotencyKey, details: { idempotencyKey }, retryCommand: retry })}
|
|
106506
107195
|
`);
|
|
106507
107196
|
} else {
|
|
106508
|
-
writeFailure(ctx.deps, result,
|
|
107197
|
+
writeFailure(ctx.deps, result, failure4, false);
|
|
106509
107198
|
ctx.deps.stderr.write(`Idempotency key: ${idempotencyKey}
|
|
106510
107199
|
${retry}
|
|
106511
107200
|
`);
|
|
@@ -106605,7 +107294,9 @@ init_render();
|
|
|
106605
107294
|
init_solana_verify();
|
|
106606
107295
|
init_trading();
|
|
106607
107296
|
init_swap();
|
|
106608
|
-
var USAGE18 =
|
|
107297
|
+
var USAGE18 = `Usage: candle transfer --to <address|wallet name|allowlist label|vault> --asset <SOL|USDC|CNDL|ETH|USDG>|--mint <mint>|--token <0x...> --amount <decimal|max> [--wallet <name>] [--client-id <id>] [--rpc-url <url>] [--yes] [--json]
|
|
107298
|
+
candle transfer status <transfer id|client id> [--json]`;
|
|
107299
|
+
var CLIENT_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
106609
107300
|
var TRANSFER_ASSETS = [...Object.keys(BASES), ...Object.keys(HOOD_BASES)];
|
|
106610
107301
|
var BASE58_ADDRESS = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
106611
107302
|
async function readLinkedWallets(ctx, key) {
|
|
@@ -106738,9 +107429,26 @@ async function confirmTransfer(ctx, lines, yes) {
|
|
|
106738
107429
|
throw new TradingError("CONFIRMATION_REQUIRED", "Run interactively to confirm, or use --yes for an ordinary transfer prompt.");
|
|
106739
107430
|
return (await ctx.deps.promptLine("Proceed? [y/N] ")).trim().toLowerCase() === "y";
|
|
106740
107431
|
}
|
|
107432
|
+
async function transferStatus(args, ctx) {
|
|
107433
|
+
const parsed = parseArgs(args, { valueFlags: [], booleanFlags: [] });
|
|
107434
|
+
if ("error" in parsed || parsed.positionals.length !== 1 || !CLIENT_ID.test(parsed.positionals[0] ?? "")) {
|
|
107435
|
+
writeUsageFailure(ctx.deps, "Usage: candle transfer status <transfer id|client id> [--json]", ctx.json);
|
|
107436
|
+
return 2;
|
|
107437
|
+
}
|
|
107438
|
+
try {
|
|
107439
|
+
const key = await tradingKey(ctx);
|
|
107440
|
+
const id = parsed.positionals[0];
|
|
107441
|
+
const found = await request(ctx, key, `/api/v1/agent/transfer/${encodeURIComponent(id)}`);
|
|
107442
|
+
return printTradingResult(ctx, found);
|
|
107443
|
+
} catch (error) {
|
|
107444
|
+
return tradingFailure(ctx, error);
|
|
107445
|
+
}
|
|
107446
|
+
}
|
|
106741
107447
|
async function transfer(args, ctx) {
|
|
107448
|
+
if (args[0] === "status")
|
|
107449
|
+
return await transferStatus(args.slice(1), ctx);
|
|
106742
107450
|
const parsed = parseArgs(args, {
|
|
106743
|
-
valueFlags: ["--wallet", "--to", "--asset", "--mint", "--token", "--amount", "--rpc-url"],
|
|
107451
|
+
valueFlags: ["--wallet", "--to", "--asset", "--mint", "--token", "--amount", "--rpc-url", "--client-id"],
|
|
106744
107452
|
booleanFlags: ["--yes"]
|
|
106745
107453
|
});
|
|
106746
107454
|
if ("error" in parsed) {
|
|
@@ -106749,7 +107457,7 @@ async function transfer(args, ctx) {
|
|
|
106749
107457
|
}
|
|
106750
107458
|
const flags2 = parsed.values;
|
|
106751
107459
|
const asset = flags2["--asset"]?.toUpperCase();
|
|
106752
|
-
if (parsed.positionals.length !== 0 || !flags2["--to"] || !flags2["--amount"] || [flags2["--asset"], flags2["--mint"], flags2["--token"]].filter(Boolean).length !== 1 || asset !== undefined && !TRANSFER_ASSETS.includes(asset) || flags2["--mint"] !== undefined && !BASE58_ADDRESS.test(flags2["--mint"]) || flags2["--token"] !== undefined && !checkEvmAddress(flags2["--token"]).ok) {
|
|
107460
|
+
if (parsed.positionals.length !== 0 || !flags2["--to"] || !flags2["--amount"] || [flags2["--asset"], flags2["--mint"], flags2["--token"]].filter(Boolean).length !== 1 || asset !== undefined && !TRANSFER_ASSETS.includes(asset) || flags2["--mint"] !== undefined && !BASE58_ADDRESS.test(flags2["--mint"]) || flags2["--token"] !== undefined && !checkEvmAddress(flags2["--token"]).ok || flags2["--client-id"] !== undefined && !CLIENT_ID.test(flags2["--client-id"])) {
|
|
106753
107461
|
writeUsageFailure(ctx.deps, USAGE18, ctx.json);
|
|
106754
107462
|
return 2;
|
|
106755
107463
|
}
|
|
@@ -106796,7 +107504,8 @@ async function transfer(args, ctx) {
|
|
|
106796
107504
|
chain: "solana",
|
|
106797
107505
|
...asset ? { asset } : { mint: flags2["--mint"] },
|
|
106798
107506
|
amountRaw,
|
|
106799
|
-
to: destination.address
|
|
107507
|
+
to: destination.address,
|
|
107508
|
+
...flags2["--client-id"] !== undefined ? { clientTransferId: flags2["--client-id"] } : {}
|
|
106800
107509
|
});
|
|
106801
107510
|
const transferId = built.transferId;
|
|
106802
107511
|
const unsigned = built.unsignedTransactionsBase64;
|
|
@@ -106832,6 +107541,7 @@ async function transfer(args, ctx) {
|
|
|
106832
107541
|
const receipt = {
|
|
106833
107542
|
...result,
|
|
106834
107543
|
transferId,
|
|
107544
|
+
...flags2["--client-id"] !== undefined ? { clientTransferId: flags2["--client-id"] } : {},
|
|
106835
107545
|
walletId: wallet3.id,
|
|
106836
107546
|
wallet: safeText(wallet3.address),
|
|
106837
107547
|
destination,
|
|
@@ -106888,7 +107598,8 @@ async function hoodTransfer(ctx, args) {
|
|
|
106888
107598
|
chain: "hood",
|
|
106889
107599
|
...asset ? { asset } : { mint: tokenAddress },
|
|
106890
107600
|
amountRaw: requested,
|
|
106891
|
-
to: destination.address
|
|
107601
|
+
to: destination.address,
|
|
107602
|
+
...flags2["--client-id"] !== undefined ? { clientTransferId: flags2["--client-id"] } : {}
|
|
106892
107603
|
});
|
|
106893
107604
|
const parsed = sequencedSchema.safeParse(built);
|
|
106894
107605
|
if (!parsed.success)
|
|
@@ -106925,6 +107636,7 @@ async function hoodTransfer(ctx, args) {
|
|
|
106925
107636
|
const receipt = {
|
|
106926
107637
|
...run2.final,
|
|
106927
107638
|
transferId,
|
|
107639
|
+
...flags2["--client-id"] !== undefined ? { clientTransferId: flags2["--client-id"] } : {},
|
|
106928
107640
|
chain: "hood",
|
|
106929
107641
|
walletId: wallet3.id,
|
|
106930
107642
|
wallet: safeText(wallet3.address),
|
|
@@ -107597,10 +108309,10 @@ candle update`);
|
|
|
107597
108309
|
} catch (error) {
|
|
107598
108310
|
steps.fail("installing");
|
|
107599
108311
|
await discard(deps, helper.tmpPath);
|
|
107600
|
-
const
|
|
108312
|
+
const failure4 = notWritable(dir, error);
|
|
107601
108313
|
writeScreenLocalFailure(deps, {
|
|
107602
|
-
...
|
|
107603
|
-
message: `${
|
|
108314
|
+
...failure4,
|
|
108315
|
+
message: `${failure4.message} candle ${target.version} was installed to ${realExec}, but its security key helper could not be renamed to ${helper.target}.`
|
|
107604
108316
|
}, json);
|
|
107605
108317
|
return 1;
|
|
107606
108318
|
}
|
|
@@ -108531,8 +109243,8 @@ async function vaultDemoteBatch(args, ctx) {
|
|
|
108531
109243
|
row2.label,
|
|
108532
109244
|
row2.destination ?? `none (${row2.destinationNote})`
|
|
108533
109245
|
]), []);
|
|
108534
|
-
for (const
|
|
108535
|
-
ctx.deps[display].write(`Not demoted: ${
|
|
109246
|
+
for (const skipped2 of outcomes) {
|
|
109247
|
+
ctx.deps[display].write(`Not demoted: ${skipped2.address}: ${skipped2.reason}
|
|
108536
109248
|
`);
|
|
108537
109249
|
}
|
|
108538
109250
|
if (!yes) {
|
|
@@ -108640,12 +109352,12 @@ async function demoteOne(ctx, session, row2, values, booleans) {
|
|
|
108640
109352
|
}
|
|
108641
109353
|
}
|
|
108642
109354
|
outcome.results = results;
|
|
108643
|
-
const
|
|
108644
|
-
if (
|
|
108645
|
-
if (typeof
|
|
108646
|
-
outcome.code =
|
|
108647
|
-
if (typeof
|
|
108648
|
-
outcome.reason =
|
|
109355
|
+
const failure4 = [...results].reverse().find((doc) => typeof doc === "object" && doc !== null && doc.ok === false);
|
|
109356
|
+
if (failure4 !== undefined && exitCode !== 0) {
|
|
109357
|
+
if (typeof failure4.code === "string")
|
|
109358
|
+
outcome.code = failure4.code;
|
|
109359
|
+
if (typeof failure4.message === "string")
|
|
109360
|
+
outcome.reason = failure4.message;
|
|
108649
109361
|
}
|
|
108650
109362
|
}
|
|
108651
109363
|
if (thrown !== undefined)
|
|
@@ -110303,6 +111015,7 @@ init_solana_endpoint();
|
|
|
110303
111015
|
init_errors2();
|
|
110304
111016
|
init_evm_tee();
|
|
110305
111017
|
init_evm_transfer();
|
|
111018
|
+
init_format();
|
|
110306
111019
|
|
|
110307
111020
|
// src/vault/funding-receipts.ts
|
|
110308
111021
|
init_errors2();
|
|
@@ -110487,6 +111200,8 @@ async function vaultFund(args, ctx) {
|
|
|
110487
111200
|
if (parsed.values["--from"] !== undefined) {
|
|
110488
111201
|
return usage(ctx, "--from applies to an external wallet only; a TEE wallet is funded from its pinned vault key.");
|
|
110489
111202
|
}
|
|
111203
|
+
if (isRetiredTee(teeEntry))
|
|
111204
|
+
return refuseRetiredFund(ctx, teeAddress);
|
|
110490
111205
|
const reconciled = await reconcileFundingReceipts(vault, [teeAddress, ...teeEntry.tee?.vaultDestination ? [teeEntry.tee.vaultDestination] : []], solana.rpc, ctx);
|
|
110491
111206
|
if (reconciled !== null)
|
|
110492
111207
|
return reconciled;
|
|
@@ -110608,6 +111323,8 @@ async function vaultFundEvm(ctx, parsed, teeAddress) {
|
|
|
110608
111323
|
}, ctx.json);
|
|
110609
111324
|
return 1;
|
|
110610
111325
|
}
|
|
111326
|
+
if (isRetiredTee(teeEntry))
|
|
111327
|
+
return refuseRetiredFund(ctx, teeEntry.address);
|
|
110611
111328
|
if (teeEntry.tee?.remoteAuthority !== "verified-active" || teeEntry.tee.stopRequestedAt !== undefined) {
|
|
110612
111329
|
writeLocalFailure(ctx.deps, {
|
|
110613
111330
|
code: "TEE_WALLET_NOT_VERIFIED",
|
|
@@ -110642,6 +111359,14 @@ async function vaultFundEvm(ctx, parsed, teeAddress) {
|
|
|
110642
111359
|
}, client2.rpc);
|
|
110643
111360
|
});
|
|
110644
111361
|
}
|
|
111362
|
+
function refuseRetiredFund(ctx, address) {
|
|
111363
|
+
writeLocalFailure(ctx.deps, {
|
|
111364
|
+
code: "TEE_WALLET_STOPPED",
|
|
111365
|
+
message: `${address} is a retired TEE wallet; a retired address is never refunded.`,
|
|
111366
|
+
suggestion: "Nothing was signed. Create a new wallet to fund: candle tee new"
|
|
111367
|
+
}, ctx.json);
|
|
111368
|
+
return 1;
|
|
111369
|
+
}
|
|
110645
111370
|
|
|
110646
111371
|
// src/commands/vault-import-legacy.ts
|
|
110647
111372
|
init_args();
|
|
@@ -110853,14 +111578,14 @@ async function vaultImportLegacy(args, ctx) {
|
|
|
110853
111578
|
const digest = sourceDigest(vault.file.vaultId, new TextEncoder().encode(legacyRaw));
|
|
110854
111579
|
const already = new Set(vault.index.entries.map((entry) => entry.address));
|
|
110855
111580
|
const toMigrate = legacyEntries.filter((entry) => !already.has(entry.address));
|
|
110856
|
-
const
|
|
111581
|
+
const skipped2 = legacyEntries.length - toMigrate.length;
|
|
110857
111582
|
if (toMigrate.length === 0) {
|
|
110858
111583
|
await recordMigrationSidecar(vaultPath, vault, fromPath, digest, deps.now());
|
|
110859
111584
|
return reportDone(ctx, {
|
|
110860
111585
|
path: fromPath,
|
|
110861
111586
|
vaultPath,
|
|
110862
111587
|
migrated: 0,
|
|
110863
|
-
skipped,
|
|
111588
|
+
skipped: skipped2,
|
|
110864
111589
|
addresses: [],
|
|
110865
111590
|
digest,
|
|
110866
111591
|
idempotent: true
|
|
@@ -110900,7 +111625,7 @@ async function vaultImportLegacy(args, ctx) {
|
|
|
110900
111625
|
path: fromPath,
|
|
110901
111626
|
vaultPath,
|
|
110902
111627
|
migrated: newEntries.length,
|
|
110903
|
-
skipped,
|
|
111628
|
+
skipped: skipped2,
|
|
110904
111629
|
addresses: newEntries.map((entry) => ({
|
|
110905
111630
|
address: entry.address,
|
|
110906
111631
|
lifecycle: entry.tee?.lifecycle ?? "local-candidate",
|
|
@@ -111032,7 +111757,7 @@ async function readLamports(addresses, solana, ctx) {
|
|
|
111032
111757
|
const { rpc } = solana;
|
|
111033
111758
|
const lamports = new Map;
|
|
111034
111759
|
const unavailable6 = [];
|
|
111035
|
-
let
|
|
111760
|
+
let failure4;
|
|
111036
111761
|
let rateLimited = false;
|
|
111037
111762
|
for (let at3 = 0;at3 < addresses.length; at3 += CHUNK2) {
|
|
111038
111763
|
const chunk = addresses.slice(at3, at3 + CHUNK2);
|
|
@@ -111048,12 +111773,12 @@ async function readLamports(addresses, solana, ctx) {
|
|
|
111048
111773
|
unavailable6.push(...chunk);
|
|
111049
111774
|
if (isRateLimited(error)) {
|
|
111050
111775
|
rateLimited = true;
|
|
111051
|
-
|
|
111776
|
+
failure4 ??= rateLimitedReadFailure(ctx, error);
|
|
111052
111777
|
} else
|
|
111053
|
-
|
|
111778
|
+
failure4 ??= describeRpcFailure(error);
|
|
111054
111779
|
}
|
|
111055
111780
|
}
|
|
111056
|
-
return { lamports, unavailable: unavailable6, ...
|
|
111781
|
+
return { lamports, unavailable: unavailable6, ...failure4 === undefined ? {} : { failure: failure4 } };
|
|
111057
111782
|
}
|
|
111058
111783
|
async function readEvmBalances(addresses, rpcUrl, fetchFn) {
|
|
111059
111784
|
const rpc = createEvmRpc(rpcUrl, fetchFn);
|
|
@@ -111130,13 +111855,12 @@ async function vaultList(args, ctx) {
|
|
|
111130
111855
|
if (balances && "error" in evmRpc)
|
|
111131
111856
|
return usage(ctx, evmRpc.error);
|
|
111132
111857
|
const evmRpcUrl = "url" in evmRpc ? evmRpc.url : DEFAULT_HOOD_RPC_URL;
|
|
111133
|
-
|
|
111134
|
-
return 1;
|
|
111858
|
+
const promptCtx = refusePromptsWithoutTerminal(ctx, "vault list");
|
|
111135
111859
|
return runVaultCommand(ctx, async ({ hold }) => {
|
|
111136
111860
|
const raw = await readVaultRaw(path);
|
|
111137
111861
|
if (raw === null)
|
|
111138
111862
|
throw missingVault(ctx, resolvedVault);
|
|
111139
|
-
const vault = hold((await unlockInteractively(
|
|
111863
|
+
const vault = hold((await unlockInteractively(promptCtx, path, raw, { acceptOlderCopy: parsed.booleans.has("--accept-older-copy") })).vault);
|
|
111140
111864
|
const all = vault.index.entries;
|
|
111141
111865
|
const filtered = filter === undefined ? all : all.filter((entry) => matches2(entry, filter));
|
|
111142
111866
|
const showRetired = parsed.booleans.has("--all");
|
|
@@ -111153,17 +111877,17 @@ async function vaultList(args, ctx) {
|
|
|
111153
111877
|
}
|
|
111154
111878
|
let lamports = new Map;
|
|
111155
111879
|
let unavailable6 = [];
|
|
111156
|
-
let
|
|
111880
|
+
let failure4;
|
|
111157
111881
|
if (balances && solana.length > 0 && solanaClient !== undefined) {
|
|
111158
111882
|
const outcome = await readLamports(solana.map((entry) => entry.address), solanaClient, ctx);
|
|
111159
111883
|
lamports = outcome.lamports;
|
|
111160
111884
|
unavailable6 = outcome.unavailable;
|
|
111161
|
-
|
|
111885
|
+
failure4 = outcome.failure;
|
|
111162
111886
|
}
|
|
111163
111887
|
const solanaComplete = unavailable6.length === 0;
|
|
111164
111888
|
const totalLamports = [...lamports.values()].reduce((sum, value) => sum + value, 0n);
|
|
111165
111889
|
if (!solanaComplete) {
|
|
111166
|
-
notice(deps, "stderr", "warning", `${unavailable6.length} ${unavailable6.length === 1 ? "address" : "addresses"} could not be read: ${
|
|
111890
|
+
notice(deps, "stderr", "warning", `${unavailable6.length} ${unavailable6.length === 1 ? "address" : "addresses"} could not be read: ${failure4 ?? "the RPC did not answer"}. Narrow with a filter, or use your own endpoint with --rpc-url.
|
|
111167
111891
|
`.trim());
|
|
111168
111892
|
}
|
|
111169
111893
|
const evm = balances ? matched.filter((entry) => entry.chain === "evm") : [];
|
|
@@ -111395,8 +112119,8 @@ function unreadableRoomLine(reason) {
|
|
|
111395
112119
|
`;
|
|
111396
112120
|
}
|
|
111397
112121
|
var RESTORED_SENTENCE = "The vault entry is back as it was: nothing left this machine.";
|
|
111398
|
-
function restoresPreImportEntry(
|
|
111399
|
-
return
|
|
112122
|
+
function restoresPreImportEntry(failure4) {
|
|
112123
|
+
return failure4.stage === "init" && failure4.status !== undefined && failure4.status !== 0;
|
|
111400
112124
|
}
|
|
111401
112125
|
function restoreRefusedFailure(init, error) {
|
|
111402
112126
|
if (error instanceof VaultError && error.code === "VAULT_CHANGED") {
|
|
@@ -111674,7 +112398,7 @@ async function promoteEvmInPlace(input, subjectLabel, sweepTo) {
|
|
|
111674
112398
|
});
|
|
111675
112399
|
const code = imported.exit;
|
|
111676
112400
|
if (imported.failure !== undefined) {
|
|
111677
|
-
let
|
|
112401
|
+
let failure4 = {
|
|
111678
112402
|
code: imported.failure.code,
|
|
111679
112403
|
message: imported.failure.message,
|
|
111680
112404
|
...imported.failure.suggestion !== undefined ? { suggestion: imported.failure.suggestion } : {}
|
|
@@ -111682,12 +112406,12 @@ async function promoteEvmInPlace(input, subjectLabel, sweepTo) {
|
|
|
111682
112406
|
if (restoresPreImportEntry(imported.failure)) {
|
|
111683
112407
|
try {
|
|
111684
112408
|
vault = hold(await commitVault(vault, { index: preImportIndex }, ctx.deps));
|
|
111685
|
-
|
|
112409
|
+
failure4 = { ...failure4, message: `${failure4.message} ${RESTORED_SENTENCE}` };
|
|
111686
112410
|
} catch (error) {
|
|
111687
|
-
|
|
112411
|
+
failure4 = restoreRefusedFailure(failure4, error);
|
|
111688
112412
|
}
|
|
111689
112413
|
}
|
|
111690
|
-
writeLocalFailure(ctx.deps,
|
|
112414
|
+
writeLocalFailure(ctx.deps, failure4, ctx.json);
|
|
111691
112415
|
return code;
|
|
111692
112416
|
}
|
|
111693
112417
|
let rebound;
|
|
@@ -112133,7 +112857,7 @@ async function promoteInPlace(ctx, parsed, subjectLabel, toKey) {
|
|
|
112133
112857
|
});
|
|
112134
112858
|
const code = imported.exit;
|
|
112135
112859
|
if (imported.failure !== undefined) {
|
|
112136
|
-
let
|
|
112860
|
+
let failure4 = {
|
|
112137
112861
|
code: imported.failure.code,
|
|
112138
112862
|
message: imported.failure.message,
|
|
112139
112863
|
...imported.failure.suggestion !== undefined ? { suggestion: imported.failure.suggestion } : {}
|
|
@@ -112141,12 +112865,12 @@ async function promoteInPlace(ctx, parsed, subjectLabel, toKey) {
|
|
|
112141
112865
|
if (restoresPreImportEntry(imported.failure)) {
|
|
112142
112866
|
try {
|
|
112143
112867
|
vault = hold(await commitVault(vault, { index: preImportIndex }, ctx.deps));
|
|
112144
|
-
|
|
112868
|
+
failure4 = { ...failure4, message: `${failure4.message} ${RESTORED_SENTENCE}` };
|
|
112145
112869
|
} catch (error) {
|
|
112146
|
-
|
|
112870
|
+
failure4 = restoreRefusedFailure(failure4, error);
|
|
112147
112871
|
}
|
|
112148
112872
|
}
|
|
112149
|
-
writeLocalFailure(ctx.deps,
|
|
112873
|
+
writeLocalFailure(ctx.deps, failure4, ctx.json);
|
|
112150
112874
|
return code;
|
|
112151
112875
|
}
|
|
112152
112876
|
let rebound;
|
|
@@ -112188,10 +112912,10 @@ async function promoteInPlace(ctx, parsed, subjectLabel, toKey) {
|
|
|
112188
112912
|
async function resumePromote(ctx, vault, entry, reopen, path, hold, confirmations) {
|
|
112189
112913
|
const announceGrant = confirmations.announceGrant ?? true;
|
|
112190
112914
|
const report2 = confirmations.report ?? "write";
|
|
112191
|
-
const fail4 = (
|
|
112915
|
+
const fail4 = (failure4, exit) => {
|
|
112192
112916
|
if (report2 === "write")
|
|
112193
|
-
writeLocalFailure(ctx.deps,
|
|
112194
|
-
return { exit, ...report2 === "return" ? { failure:
|
|
112917
|
+
writeLocalFailure(ctx.deps, failure4, ctx.json);
|
|
112918
|
+
return { exit, ...report2 === "return" ? { failure: failure4 } : {} };
|
|
112195
112919
|
};
|
|
112196
112920
|
const apiKey = await resolveApiKey(ctx.deps, ctx.profile);
|
|
112197
112921
|
if (!apiKey) {
|
|
@@ -112337,10 +113061,10 @@ async function runTeeImport(ctx, opts) {
|
|
|
112337
113061
|
const closeReopened = opts.closeReopened ?? true;
|
|
112338
113062
|
const apiKey = await resolveApiKey(ctx.deps, ctx.profile);
|
|
112339
113063
|
if (!apiKey) {
|
|
112340
|
-
const
|
|
113064
|
+
const failure4 = { code: "NO_API_KEY", message: "No API key available.", suggestion: "Run: candle keys create" };
|
|
112341
113065
|
if (report2 === "write")
|
|
112342
|
-
writeLocalFailure(ctx.deps,
|
|
112343
|
-
return { exit: 1, ...report2 === "return" ? { failure:
|
|
113066
|
+
writeLocalFailure(ctx.deps, failure4, ctx.json);
|
|
113067
|
+
return { exit: 1, ...report2 === "return" ? { failure: failure4 } : {} };
|
|
112344
113068
|
}
|
|
112345
113069
|
opts.onImport?.();
|
|
112346
113070
|
const flow = await runImportFlow({
|
|
@@ -112356,28 +113080,28 @@ async function runTeeImport(ctx, opts) {
|
|
|
112356
113080
|
...opts.keySigner !== undefined ? { keySigner: opts.keySigner } : {}
|
|
112357
113081
|
});
|
|
112358
113082
|
if (!flow.ok) {
|
|
112359
|
-
const
|
|
112360
|
-
if (
|
|
113083
|
+
const failure4 = flow.failure;
|
|
113084
|
+
if (failure4.kind === "api") {
|
|
112361
113085
|
const render2 = { apiUrl: ctx.apiUrl, authType: "key" };
|
|
112362
113086
|
if (report2 === "write") {
|
|
112363
|
-
writeFailure(ctx.deps,
|
|
113087
|
+
writeFailure(ctx.deps, failure4.response, render2, ctx.json);
|
|
112364
113088
|
return { exit: 1 };
|
|
112365
113089
|
}
|
|
112366
|
-
const suggestion = suggestionFor(
|
|
113090
|
+
const suggestion = suggestionFor(failure4.response, render2);
|
|
112367
113091
|
return {
|
|
112368
113092
|
exit: 1,
|
|
112369
113093
|
failure: {
|
|
112370
|
-
code: errorEnvelope(
|
|
112371
|
-
message: renderError(
|
|
113094
|
+
code: errorEnvelope(failure4.response, render2).code,
|
|
113095
|
+
message: renderError(failure4.response, render2),
|
|
112372
113096
|
...suggestion ? { suggestion } : {},
|
|
112373
|
-
stage:
|
|
112374
|
-
status:
|
|
113097
|
+
stage: failure4.stage,
|
|
113098
|
+
status: failure4.response.status
|
|
112375
113099
|
}
|
|
112376
113100
|
};
|
|
112377
113101
|
}
|
|
112378
113102
|
const local = {
|
|
112379
|
-
code:
|
|
112380
|
-
message: `${opts.address}: ${
|
|
113103
|
+
code: failure4.kind === "signer-store" ? "SIGNER_STORE_FAILED" : "SIGNER_COMMIT_FAILED",
|
|
113104
|
+
message: `${opts.address}: ${failure4.error instanceof Error ? failure4.error.message : String(failure4.error)}`,
|
|
112381
113105
|
suggestion: "The vault entry is import-pending. Fix the error and re-run promote to resume."
|
|
112382
113106
|
};
|
|
112383
113107
|
if (report2 === "write")
|
|
@@ -112641,15 +113365,15 @@ async function preflightBatch(index, rows, opts) {
|
|
|
112641
113365
|
const failures = [];
|
|
112642
113366
|
const pinnedBy = new Map;
|
|
112643
113367
|
const promotedBy = new Map;
|
|
112644
|
-
const refuse3 = (row2,
|
|
113368
|
+
const refuse3 = (row2, failure4) => {
|
|
112645
113369
|
failures.push({
|
|
112646
113370
|
line: row2.line,
|
|
112647
113371
|
label: row2.label,
|
|
112648
113372
|
destination: row2.destination,
|
|
112649
|
-
code:
|
|
112650
|
-
message:
|
|
112651
|
-
...
|
|
112652
|
-
...
|
|
113373
|
+
code: failure4.code,
|
|
113374
|
+
message: failure4.message,
|
|
113375
|
+
...failure4.suggestion !== undefined ? { suggestion: failure4.suggestion } : {},
|
|
113376
|
+
...failure4.why !== undefined ? { why: failure4.why } : {}
|
|
112653
113377
|
});
|
|
112654
113378
|
};
|
|
112655
113379
|
const refuseWith = (row2, error, why) => {
|
|
@@ -113092,8 +113816,8 @@ ${table}
|
|
|
113092
113816
|
rateLimited: 0,
|
|
113093
113817
|
elapsedMs: 0
|
|
113094
113818
|
};
|
|
113095
|
-
const
|
|
113096
|
-
const rebindable = rebindPhase === undefined ? undefined : splitRebindable(
|
|
113819
|
+
const skipped2 = planned.map((item) => skippedResult(item));
|
|
113820
|
+
const rebindable = rebindPhase === undefined ? undefined : splitRebindable(skipped2);
|
|
113097
113821
|
if (rebindPhase === undefined || rebindable === undefined || rebindable.wallets.length === 0) {
|
|
113098
113822
|
deps.stderr.write(`
|
|
113099
113823
|
${table}
|
|
@@ -113103,7 +113827,7 @@ ${table}
|
|
|
113103
113827
|
return finish(ctx, {
|
|
113104
113828
|
file: pairsFile,
|
|
113105
113829
|
rows: rows.length,
|
|
113106
|
-
keys:
|
|
113830
|
+
keys: skipped2,
|
|
113107
113831
|
destinations: destinations2,
|
|
113108
113832
|
exit: 0,
|
|
113109
113833
|
controlledBy,
|
|
@@ -113124,11 +113848,11 @@ ${renderControlledBy(controlledBy, n)}
|
|
|
113124
113848
|
if (typed.trim().toLowerCase() !== CONFIRM_WORD) {
|
|
113125
113849
|
throw new VaultError("PROMOTE_NOT_ACKNOWLEDGED", `The acknowledgement is the word ${CONFIRM_WORD}; nothing was moved, and nothing was written.`, { suggestion: `Run the command again and type ${CONFIRM_WORD} at the prompt.` });
|
|
113126
113850
|
}
|
|
113127
|
-
const rebound2 = await runRebindPhase(ctx, rebindPhase,
|
|
113851
|
+
const rebound2 = await runRebindPhase(ctx, rebindPhase, skipped2);
|
|
113128
113852
|
return finish(ctx, {
|
|
113129
113853
|
file: pairsFile,
|
|
113130
113854
|
rows: rows.length,
|
|
113131
|
-
keys:
|
|
113855
|
+
keys: skipped2,
|
|
113132
113856
|
destinations: destinations2,
|
|
113133
113857
|
exit: 0,
|
|
113134
113858
|
controlledBy,
|
|
@@ -113201,24 +113925,24 @@ ${renderControlledBy(controlledBy, acting.length)}
|
|
|
113201
113925
|
keySigner: keySignerImport(rebindPhase?.toKey)
|
|
113202
113926
|
});
|
|
113203
113927
|
if (imported.failure !== undefined || imported.submitted === undefined) {
|
|
113204
|
-
let
|
|
113928
|
+
let failure4 = imported.failure ?? {
|
|
113205
113929
|
code: "VAULT_WRITE_FAILED",
|
|
113206
113930
|
message: "The import did not complete."
|
|
113207
113931
|
};
|
|
113208
|
-
if (restoresPreImportEntry(
|
|
113932
|
+
if (restoresPreImportEntry(failure4)) {
|
|
113209
113933
|
try {
|
|
113210
113934
|
current = hold(await commitVault(current, { index: preImportIndex }, deps));
|
|
113211
|
-
|
|
113935
|
+
failure4 = { ...failure4, message: `${failure4.message} ${RESTORED_SENTENCE}` };
|
|
113212
113936
|
} catch (error) {
|
|
113213
|
-
|
|
113937
|
+
failure4 = restoreRefusedFailure(failure4, error);
|
|
113214
113938
|
}
|
|
113215
113939
|
}
|
|
113216
113940
|
stopped2 = {
|
|
113217
113941
|
line: row3.line,
|
|
113218
113942
|
exit: imported.exit,
|
|
113219
|
-
code:
|
|
113220
|
-
message:
|
|
113221
|
-
...
|
|
113943
|
+
code: failure4.code,
|
|
113944
|
+
message: failure4.message,
|
|
113945
|
+
...failure4.suggestion !== undefined ? { suggestion: failure4.suggestion } : {}
|
|
113222
113946
|
};
|
|
113223
113947
|
break;
|
|
113224
113948
|
}
|
|
@@ -113387,7 +114111,7 @@ async function verifySubjectSecret(vault, subject) {
|
|
|
113387
114111
|
}
|
|
113388
114112
|
async function reconcileForPreflight(ctx, row2, subject, resolved) {
|
|
113389
114113
|
const { onDisk } = resolved;
|
|
113390
|
-
const refuse3 = (
|
|
114114
|
+
const refuse3 = (failure4) => ({ ok: false, failure: failure4 });
|
|
113391
114115
|
const apiKey = await resolveApiKey(ctx.deps, ctx.profile);
|
|
113392
114116
|
if (!apiKey) {
|
|
113393
114117
|
return refuse3({
|
|
@@ -113513,11 +114237,11 @@ function renderBatchTable(planned, opts) {
|
|
|
113513
114237
|
return renderHumanTable(headers2, rows, opts.style ?? terminalStyle({ env: {}, isTTY: { stdin: false, stdout: false, stderr: false } }, "stderr"), headers2.map((_, i) => i === 0 || i === 1 || i === 3 || i === 4 || i === 5 ? { id: true } : i === 2 ? { priority: 2 } : { align: "right", priority: 1 }));
|
|
113514
114238
|
}
|
|
113515
114239
|
function renderFooter(opts) {
|
|
113516
|
-
const
|
|
114240
|
+
const skipped2 = opts.planned.filter((item) => item.kind === "skip").length;
|
|
113517
114241
|
const resumes = opts.planned.filter((item) => item.kind === "resume").length;
|
|
113518
114242
|
const promotes = opts.planned.filter((item) => item.kind === "promote").length;
|
|
113519
114243
|
const lines = [
|
|
113520
|
-
`${opts.planned.length} rows from ${opts.file}: ${
|
|
114244
|
+
`${opts.planned.length} rows from ${opts.file}: ${skipped2} already promoted (skipped), ${resumes} to resume, ${promotes} to promote.`
|
|
113521
114245
|
];
|
|
113522
114246
|
if (opts.destinations.length > 0) {
|
|
113523
114247
|
lines.push(`${opts.destinations.length} destination${opts.destinations.length === 1 ? "" : "s"}, in this order: ${opts.destinations.map((d) => `${destinationCell(d.label, d.address)} ${d.keys} key${d.keys === 1 ? "" : "s"}`).join(" · ")}`);
|
|
@@ -113567,7 +114291,7 @@ ${opts.landed} of ${opts.acting} rows landed and ARE in the vault; the vault is
|
|
|
113567
114291
|
function finish(ctx, opts) {
|
|
113568
114292
|
const promoted = opts.keys.filter((r) => r.state === "promoted").length;
|
|
113569
114293
|
const resumed = opts.keys.filter((r) => r.state === "resumed").length;
|
|
113570
|
-
const
|
|
114294
|
+
const skipped2 = opts.keys.filter((r) => r.state === "skipped").length;
|
|
113571
114295
|
const unverified = opts.keys.filter((r) => r.state !== "skipped" && r.remoteAuthority !== "verified-active");
|
|
113572
114296
|
const rebindFailed = opts.rebound?.run !== undefined && !opts.rebound.run.ok;
|
|
113573
114297
|
const exit = rebindFailed ? Math.max(opts.exit, 1) : opts.exit;
|
|
@@ -113580,7 +114304,7 @@ function finish(ctx, opts) {
|
|
|
113580
114304
|
rows: opts.rows,
|
|
113581
114305
|
promoted,
|
|
113582
114306
|
resumed,
|
|
113583
|
-
skipped,
|
|
114307
|
+
skipped: skipped2,
|
|
113584
114308
|
destinations: opts.destinations.map(({ label, address, keys }) => ({ label, address, keys })),
|
|
113585
114309
|
keys: opts.rebound === undefined ? opts.keys : keysWithRebind(opts.keys, opts.rebound.phase, opts.rebound.run),
|
|
113586
114310
|
controlledBy: controlledByJson(opts.controlledBy),
|
|
@@ -113592,7 +114316,7 @@ function finish(ctx, opts) {
|
|
|
113592
114316
|
writeFields(ctx.deps, "stdout", [
|
|
113593
114317
|
["Promoted", promoted],
|
|
113594
114318
|
["Resumed", resumed],
|
|
113595
|
-
["Already promoted (skipped)",
|
|
114319
|
+
["Already promoted (skipped)", skipped2],
|
|
113596
114320
|
["Commit", "one unlock, each committed on its own"]
|
|
113597
114321
|
]);
|
|
113598
114322
|
if (unverified.length > 0) {
|
|
@@ -114672,10 +115396,12 @@ function describeEnvelope(envelope, facts) {
|
|
|
114672
115396
|
init_args();
|
|
114673
115397
|
init_deps();
|
|
114674
115398
|
init_evm_lite();
|
|
115399
|
+
init_render();
|
|
114675
115400
|
init_solana_endpoint();
|
|
114676
115401
|
init_errors2();
|
|
114677
115402
|
init_evm_tee();
|
|
114678
115403
|
init_evm_transfer();
|
|
115404
|
+
init_format();
|
|
114679
115405
|
init_promote_support();
|
|
114680
115406
|
init_store();
|
|
114681
115407
|
init_vault_support();
|
|
@@ -114731,6 +115457,15 @@ async function vaultTransfer(args, ctx) {
|
|
|
114731
115457
|
return usage(ctx, `No vault key or promoted wallet matches --from ${fromLabel}.`);
|
|
114732
115458
|
}
|
|
114733
115459
|
assertTransferSigner(fromEntry);
|
|
115460
|
+
const retiredDestination = vault.index.entries.find((entry) => isRetiredTee(entry) && entry.address.toLowerCase() === to.toLowerCase());
|
|
115461
|
+
if (retiredDestination !== undefined) {
|
|
115462
|
+
writeLocalFailure(ctx.deps, {
|
|
115463
|
+
code: "TEE_WALLET_STOPPED",
|
|
115464
|
+
message: `${to} is a retired TEE wallet (${retiredDestination.label}); a retired address is never refunded.`,
|
|
115465
|
+
suggestion: "Nothing was signed. Send to a live wallet, or create one: candle tee new"
|
|
115466
|
+
}, ctx.json);
|
|
115467
|
+
return 1;
|
|
115468
|
+
}
|
|
114734
115469
|
if (fromEntry.chain === "evm") {
|
|
114735
115470
|
const evmRpc = resolveEvmRpcUrl(rpcUrlFlag, ctx.deps.env[EVM_RPC_URL_ENV], "--rpc-url");
|
|
114736
115471
|
if ("error" in evmRpc)
|
|
@@ -115163,8 +115898,8 @@ async function setAllowLaunch(args, ctx, enabled) {
|
|
|
115163
115898
|
for (const message of new Set(notEnforced.map((item) => item.message))) {
|
|
115164
115899
|
notice(deps, "stderr", "danger", `${message} (ALLOW_LAUNCH_NOT_ENFORCED)`);
|
|
115165
115900
|
}
|
|
115166
|
-
for (const
|
|
115167
|
-
notice(deps, "stderr", "danger", `Not changed: ${
|
|
115901
|
+
for (const failure4 of failed) {
|
|
115902
|
+
notice(deps, "stderr", "danger", `Not changed: ${failure4.id}: ${failure4.message}${failure4.code ? ` (${failure4.code})` : ""}.`);
|
|
115168
115903
|
}
|
|
115169
115904
|
return failed.length > 0 || notEnforced.length > 0 ? 1 : 0;
|
|
115170
115905
|
}
|
|
@@ -115175,6 +115910,101 @@ function walletsDisallowLaunch(args, ctx) {
|
|
|
115175
115910
|
return setAllowLaunch(args, ctx, false);
|
|
115176
115911
|
}
|
|
115177
115912
|
|
|
115913
|
+
// src/commands/wallets-allowlist.ts
|
|
115914
|
+
init_args();
|
|
115915
|
+
init_client();
|
|
115916
|
+
init_deps();
|
|
115917
|
+
init_human_output();
|
|
115918
|
+
init_presentation();
|
|
115919
|
+
init_profiles();
|
|
115920
|
+
init_render();
|
|
115921
|
+
init_key_output();
|
|
115922
|
+
var EMPTY_NOTE = "No approved withdrawal addresses. An agent can move funds only to the account's own wallets and its vault.";
|
|
115923
|
+
var MANAGED_HINT = "Approving an address needs the owner signed in to the agent console's Wallets tab; an API key cannot approve. An acting key can revoke one; a read-only key cannot.";
|
|
115924
|
+
async function walletsAllowlist(args, ctx) {
|
|
115925
|
+
const { deps, apiUrl, json } = ctx;
|
|
115926
|
+
const parsed = parseArgs(args, { booleanFlags: ["--all"] });
|
|
115927
|
+
if ("error" in parsed) {
|
|
115928
|
+
writeScreenUsageFailure(deps, parsed.error, json);
|
|
115929
|
+
return 2;
|
|
115930
|
+
}
|
|
115931
|
+
if (parsed.positionals.length > 0) {
|
|
115932
|
+
writeScreenUsageFailure(deps, `Unexpected argument: ${parsed.positionals[0]}`, json);
|
|
115933
|
+
return 2;
|
|
115934
|
+
}
|
|
115935
|
+
await printIdentity(ctx);
|
|
115936
|
+
const apiKey = await resolveApiKey(deps, ctx.profile);
|
|
115937
|
+
if (!apiKey) {
|
|
115938
|
+
writeScreenLocalFailure(deps, { code: "NO_API_KEY", message: "No API key available.", suggestion: "Run: candle keys create" }, json);
|
|
115939
|
+
return 1;
|
|
115940
|
+
}
|
|
115941
|
+
const query3 = parsed.booleans.has("--all") ? "?includeRevoked=true" : "";
|
|
115942
|
+
const result = await apiRequest(`/api/v1/agent/withdrawal-addresses${query3}`, {
|
|
115943
|
+
method: "GET",
|
|
115944
|
+
auth: "key",
|
|
115945
|
+
credentials: { apiKey },
|
|
115946
|
+
apiUrl,
|
|
115947
|
+
fetch: deps.fetch,
|
|
115948
|
+
env: deps.env
|
|
115949
|
+
});
|
|
115950
|
+
if (!result.ok) {
|
|
115951
|
+
writeScreenFailure(deps, result, { apiUrl, authType: "key" }, json);
|
|
115952
|
+
return 1;
|
|
115953
|
+
}
|
|
115954
|
+
const body = result.body;
|
|
115955
|
+
if (!Array.isArray(body.addresses)) {
|
|
115956
|
+
writeScreenLocalFailure(deps, { code: "INVALID_RESPONSE", message: "The withdrawal allowlist read did not return a list." }, json);
|
|
115957
|
+
return 1;
|
|
115958
|
+
}
|
|
115959
|
+
const rows = body.addresses;
|
|
115960
|
+
if (json) {
|
|
115961
|
+
deps.stdout.write(`${JSON.stringify({ success: true, addresses: rows, count: rows.length })}
|
|
115962
|
+
`);
|
|
115963
|
+
return 0;
|
|
115964
|
+
}
|
|
115965
|
+
const style = terminalStyle(deps, "stdout");
|
|
115966
|
+
deps.stdout.write(`${heading(`Withdrawal allowlist (${rows.length})`, style)}
|
|
115967
|
+
|
|
115968
|
+
`);
|
|
115969
|
+
if (rows.length === 0) {
|
|
115970
|
+
notice(deps, "stdout", "note", EMPTY_NOTE);
|
|
115971
|
+
notice(deps, "stdout", "note", MANAGED_HINT);
|
|
115972
|
+
return 0;
|
|
115973
|
+
}
|
|
115974
|
+
if (style.width < 100) {
|
|
115975
|
+
for (const row2 of rows) {
|
|
115976
|
+
writeFields(deps, "stdout", [
|
|
115977
|
+
["Id", row2.id],
|
|
115978
|
+
["Address", row2.address],
|
|
115979
|
+
["Label", row2.label],
|
|
115980
|
+
["Chain", row2.chain],
|
|
115981
|
+
["Revoked", row2.revokedAt ? "yes" : "no"]
|
|
115982
|
+
]);
|
|
115983
|
+
deps.stdout.write(`
|
|
115984
|
+
`);
|
|
115985
|
+
}
|
|
115986
|
+
} else {
|
|
115987
|
+
deps.stdout.write(`${renderTable(["Id", "Chain", "Address", "Label", "Revoked"], rows.map((row2) => [
|
|
115988
|
+
row2.id,
|
|
115989
|
+
row2.chain,
|
|
115990
|
+
row2.address,
|
|
115991
|
+
row2.label ?? symbols(style).missing,
|
|
115992
|
+
row2.revokedAt ? "yes" : "no"
|
|
115993
|
+
]), {
|
|
115994
|
+
style,
|
|
115995
|
+
width: style.width,
|
|
115996
|
+
indent: 2,
|
|
115997
|
+
rule: false,
|
|
115998
|
+
columns: [{ id: true }, { priority: 3 }, { id: true }, { priority: 2 }, { priority: 1 }]
|
|
115999
|
+
})}
|
|
116000
|
+
`);
|
|
116001
|
+
}
|
|
116002
|
+
deps.stdout.write(`
|
|
116003
|
+
`);
|
|
116004
|
+
notice(deps, "stdout", "note", MANAGED_HINT);
|
|
116005
|
+
return 0;
|
|
116006
|
+
}
|
|
116007
|
+
|
|
115178
116008
|
// src/commands/wallets-authorize.ts
|
|
115179
116009
|
init_args();
|
|
115180
116010
|
init_client();
|
|
@@ -115327,7 +116157,7 @@ init_solana_alt();
|
|
|
115327
116157
|
init_solana_lite();
|
|
115328
116158
|
init_trading();
|
|
115329
116159
|
init_swap();
|
|
115330
|
-
import { randomUUID as
|
|
116160
|
+
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
115331
116161
|
var USAGE21 = "Usage: candle wallet close-empty [--wallet embedded|<tee address|label>] [--keep <mint>]... [--client-trade-id <id>] [--preview] [--yes] [--json]";
|
|
115332
116162
|
var BASE58_ADDRESS2 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
115333
116163
|
var LISTED_ROWS = 50;
|
|
@@ -115615,7 +116445,7 @@ async function walletsCloseEmpty(args, ctx) {
|
|
|
115615
116445
|
return 2;
|
|
115616
116446
|
}
|
|
115617
116447
|
const named = parsed.values["--client-trade-id"];
|
|
115618
|
-
const id = named ?? `close-${
|
|
116448
|
+
const id = named ?? `close-${randomUUID17()}`;
|
|
115619
116449
|
if (parsed.positionals.length !== 0 || !validClientId(id) || !lifted.keep.every((m) => BASE58_ADDRESS2.test(m))) {
|
|
115620
116450
|
writeUsageFailure(ctx.deps, USAGE21, ctx.json);
|
|
115621
116451
|
return 2;
|
|
@@ -115877,6 +116707,7 @@ init_ownership();
|
|
|
115877
116707
|
init_file_lock();
|
|
115878
116708
|
|
|
115879
116709
|
// src/guard.ts
|
|
116710
|
+
init_account();
|
|
115880
116711
|
init_profiles();
|
|
115881
116712
|
async function verifyProfileAccount(ctx, config) {
|
|
115882
116713
|
const { deps, profile } = ctx;
|
|
@@ -115892,11 +116723,11 @@ async function verifyProfileAccount(ctx, config) {
|
|
|
115892
116723
|
const apiKey = await deps.store.get(profileSecretRef(profile, "apiKey"));
|
|
115893
116724
|
if (!apiKey)
|
|
115894
116725
|
return { ok: true, skipped: "no stored key" };
|
|
115895
|
-
const { account: live, failure:
|
|
116726
|
+
const { account: live, failure: failure4 } = await fetchAccount(deps, ctx.apiUrl, apiKey);
|
|
115896
116727
|
if (!live) {
|
|
115897
116728
|
return {
|
|
115898
116729
|
ok: true,
|
|
115899
|
-
warning: `Could not verify the account for ${profile} (${
|
|
116730
|
+
warning: `Could not verify the account for ${profile} (${failure4}); proceeding on the cached value ${cached}.`
|
|
115900
116731
|
};
|
|
115901
116732
|
}
|
|
115902
116733
|
if (live !== cached) {
|
|
@@ -115929,6 +116760,7 @@ init_profiles();
|
|
|
115929
116760
|
init_release();
|
|
115930
116761
|
init_render();
|
|
115931
116762
|
init_secret_store();
|
|
116763
|
+
init_trading_checks();
|
|
115932
116764
|
|
|
115933
116765
|
// src/update-notice.ts
|
|
115934
116766
|
init_client();
|
|
@@ -116144,7 +116976,8 @@ var COMMANDS = {
|
|
|
116144
116976
|
"allow-launch": walletsAllowLaunch,
|
|
116145
116977
|
"disallow-launch": walletsDisallowLaunch,
|
|
116146
116978
|
"close-empty": walletsCloseEmpty,
|
|
116147
|
-
authorize: walletsAuthorize
|
|
116979
|
+
authorize: walletsAuthorize,
|
|
116980
|
+
allowlist: walletsAllowlist
|
|
116148
116981
|
},
|
|
116149
116982
|
bare: wallets
|
|
116150
116983
|
},
|
|
@@ -116175,6 +117008,7 @@ var COMMANDS = {
|
|
|
116175
117008
|
tee: {
|
|
116176
117009
|
subcommands: {
|
|
116177
117010
|
new: teeNew,
|
|
117011
|
+
check: teeCheck,
|
|
116178
117012
|
enable: teeEnable,
|
|
116179
117013
|
fund: teeFund,
|
|
116180
117014
|
status: teeStatus,
|