@candledottv/cli 0.11.16 → 0.11.18
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 +24 -15
- package/dist/index.js +592 -232
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -343,14 +343,14 @@ var init_agent_key_access = __esm(() => {
|
|
|
343
343
|
];
|
|
344
344
|
AGENT_KEY_PRESET_LABELS = {
|
|
345
345
|
read: "Read",
|
|
346
|
-
readwrite: "
|
|
347
|
-
readwritetransfer: "
|
|
346
|
+
readwrite: "Write",
|
|
347
|
+
readwritetransfer: "Withdraw"
|
|
348
348
|
};
|
|
349
349
|
AGENT_KEY_CAPABILITY_CHIPS = [
|
|
350
350
|
["Launch", "launch"],
|
|
351
351
|
["Trade", "trade"],
|
|
352
352
|
["Transfer", "transfer"],
|
|
353
|
-
["
|
|
353
|
+
["Withdraw", "linkedTransfer"],
|
|
354
354
|
["Report", "report"]
|
|
355
355
|
];
|
|
356
356
|
});
|
|
@@ -1028,7 +1028,7 @@ async function keysCreate(args, ctx) {
|
|
|
1028
1028
|
if (parsed.values["--access"] !== undefined) {
|
|
1029
1029
|
const preset = ACCESS_LEVELS[parsed.values["--access"]];
|
|
1030
1030
|
if (preset === undefined) {
|
|
1031
|
-
writeUsageFailure(deps,
|
|
1031
|
+
writeUsageFailure(deps, "--access must be one of: read, write, withdraw.", json);
|
|
1032
1032
|
return 2;
|
|
1033
1033
|
}
|
|
1034
1034
|
accessScopes = scopesForPreset(preset);
|
|
@@ -1226,6 +1226,9 @@ var init_keys = __esm(() => {
|
|
|
1226
1226
|
init_keys_embedded_wallet();
|
|
1227
1227
|
ACCESS_LEVELS = {
|
|
1228
1228
|
read: "read",
|
|
1229
|
+
write: "readwrite",
|
|
1230
|
+
withdraw: "readwritetransfer",
|
|
1231
|
+
transfer: "readwritetransfer",
|
|
1229
1232
|
"read-write": "readwrite",
|
|
1230
1233
|
"read-write-transfer": "readwritetransfer"
|
|
1231
1234
|
};
|
|
@@ -14506,8 +14509,29 @@ async function request(ctx, key, path, body) {
|
|
|
14506
14509
|
fetch: ctx.deps.fetch,
|
|
14507
14510
|
env: ctx.deps.env
|
|
14508
14511
|
});
|
|
14509
|
-
if (!result.ok)
|
|
14510
|
-
|
|
14512
|
+
if (!result.ok) {
|
|
14513
|
+
const raw = result.raw;
|
|
14514
|
+
const error = raw?.error;
|
|
14515
|
+
const details = {};
|
|
14516
|
+
for (const field of [
|
|
14517
|
+
"field",
|
|
14518
|
+
"stage",
|
|
14519
|
+
"signature",
|
|
14520
|
+
"leg",
|
|
14521
|
+
"legs",
|
|
14522
|
+
"hashes",
|
|
14523
|
+
"retryable",
|
|
14524
|
+
"evidenceStored",
|
|
14525
|
+
"terminal"
|
|
14526
|
+
]) {
|
|
14527
|
+
if (error?.[field] !== undefined && (field === "field" || path === "/api/v1/agent/swap/submit"))
|
|
14528
|
+
details[field] = error[field];
|
|
14529
|
+
}
|
|
14530
|
+
throw new TradingError(result.code ?? "REQUEST_FAILED", result.message, {
|
|
14531
|
+
...Object.keys(details).length ? { details } : {},
|
|
14532
|
+
...path === "/api/v1/agent/swap/submit" && error?.stage === "unconfirmed" ? { exitCode: 3 } : {}
|
|
14533
|
+
});
|
|
14534
|
+
}
|
|
14511
14535
|
if (!result.body || typeof result.body !== "object")
|
|
14512
14536
|
throw new TradingError("INVALID_RESPONSE", "Candle returned an invalid response.");
|
|
14513
14537
|
return result.body;
|
|
@@ -14553,7 +14577,7 @@ async function completeTradingWallet(ctx, row, appId, scope, chain2 = "solana",
|
|
|
14553
14577
|
if (!row.active)
|
|
14554
14578
|
throw new TradingError("TEE_WALLET_INACTIVE", `The payer must be a verified-active ${chainName(chain2)} TEE wallet.`);
|
|
14555
14579
|
if (scope === "launch:write" && row.allowLaunch !== true)
|
|
14556
|
-
throw new TradingError("LAUNCH_NOT_ALLOWED", `The
|
|
14580
|
+
throw 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.`);
|
|
14557
14581
|
if (!appId || !row.privyWalletId)
|
|
14558
14582
|
throw new TradingError("SIGNER_UNAVAILABLE", "The server did not return its relay public identifiers.");
|
|
14559
14583
|
const signer = await localSignerFor(ctx.deps, row.id, row.signerQuorumId);
|
|
@@ -27434,6 +27458,20 @@ async function resolveTeeAddress(ctx, _parsed, address, openLegacy, access3 = "r
|
|
|
27434
27458
|
}
|
|
27435
27459
|
};
|
|
27436
27460
|
}
|
|
27461
|
+
async function grantSignAccess(resolved) {
|
|
27462
|
+
if (resolved.privateKeyBase58 === null) {
|
|
27463
|
+
const secret = await decryptKey(resolved.vault, resolved.entry.id);
|
|
27464
|
+
try {
|
|
27465
|
+
if (addressFromSecret64(secret) !== resolved.entry.address) {
|
|
27466
|
+
throw new VaultError("VAULT_VERIFY_FAILED", `${resolved.entry.address} in the vault does not re-derive from its stored secret.`);
|
|
27467
|
+
}
|
|
27468
|
+
resolved.privateKeyBase58 = base58.encode(secret);
|
|
27469
|
+
} finally {
|
|
27470
|
+
wipe(secret);
|
|
27471
|
+
}
|
|
27472
|
+
}
|
|
27473
|
+
resolved.legacyView = keyEntryAsKeystore(resolved.entry, resolved.privateKeyBase58);
|
|
27474
|
+
}
|
|
27437
27475
|
function releaseResolvedTee(resolved) {
|
|
27438
27476
|
if (resolved.source === "vault")
|
|
27439
27477
|
closeVault(resolved.vault);
|
|
@@ -30311,6 +30349,8 @@ function tradingFailure(ctx, error, id) {
|
|
|
30311
30349
|
writeUsageFailure(ctx.deps, error.message, ctx.json);
|
|
30312
30350
|
return 2;
|
|
30313
30351
|
}
|
|
30352
|
+
if (error instanceof TradingError && error.details)
|
|
30353
|
+
describeSwapEvidence(ctx, error.details, error.details.terminal === true);
|
|
30314
30354
|
writeLocalFailure(ctx.deps, {
|
|
30315
30355
|
code: error instanceof TradingError ? error.code : "TRADING_FAILED",
|
|
30316
30356
|
message: `${error instanceof Error ? describeRpcFailure(error) : "Trading failed."}${id ? ` Operation ${id}; use candle swap status ${id} before another attempt.` : ""}`,
|
|
@@ -30335,7 +30375,31 @@ async function tradingRead(ctx, client, read) {
|
|
|
30335
30375
|
throw error;
|
|
30336
30376
|
}
|
|
30337
30377
|
}
|
|
30378
|
+
async function requestSwapBuild(ctx, key, body) {
|
|
30379
|
+
try {
|
|
30380
|
+
return await request(ctx, key, "/api/v1/agent/swap/build", body);
|
|
30381
|
+
} catch (error) {
|
|
30382
|
+
if (body.payer?.type === "main" && error instanceof TradingError && error.code === "VALIDATION_FAILED" && error.details?.field === "payer") {
|
|
30383
|
+
throw new TradingError("EMBEDDED_PAYER_UNSUPPORTED", "This Candle deployment cannot hold an embedded-wallet swap back for confirmation. Nothing was built. Name a TEE wallet with --wallet, or point at a deployment that supports embedded swap builds.");
|
|
30384
|
+
}
|
|
30385
|
+
throw error;
|
|
30386
|
+
}
|
|
30387
|
+
}
|
|
30388
|
+
function describeSwapEvidence(ctx, details, terminal) {
|
|
30389
|
+
if (details.stage === "not_broadcast" && terminal)
|
|
30390
|
+
ctx.deps.stderr.write(`Nothing was sent. This swap job is terminal; retry with a new id.
|
|
30391
|
+
`);
|
|
30392
|
+
if (details.stage === "unconfirmed") {
|
|
30393
|
+
const hashes = Array.isArray(details.hashes) ? details.hashes : typeof details.signature === "string" ? [details.signature] : [];
|
|
30394
|
+
ctx.deps.stderr.write(`Swap outcome needs verification${details.leg !== undefined ? ` (leg ${safeText(String(details.leg))})` : ""}. Known hashes: ${hashes.length ? hashes.map((hash) => safeText(String(hash))).join(", ") : "unknown"}. Do not automatically retry or use a new id.
|
|
30395
|
+
`);
|
|
30396
|
+
}
|
|
30397
|
+
}
|
|
30338
30398
|
function printTradingResult(ctx, result) {
|
|
30399
|
+
if (result.kind === "swap" && result.job && typeof result.job === "object") {
|
|
30400
|
+
const job = result.job;
|
|
30401
|
+
describeSwapEvidence(ctx, job, job.status === "failed");
|
|
30402
|
+
}
|
|
30339
30403
|
ctx.deps.stdout.write(ctx.json ? `${JSON.stringify(result)}
|
|
30340
30404
|
` : `${JSON.stringify(result, null, 2)}
|
|
30341
30405
|
`);
|
|
@@ -30533,8 +30597,6 @@ async function swap(args, ctx) {
|
|
|
30533
30597
|
if (prior)
|
|
30534
30598
|
return printTradingResult(ctx, prior);
|
|
30535
30599
|
const payerWallet = await tradingPayer(ctx, key, flags["--wallet"]);
|
|
30536
|
-
if (payerWallet.kind === "embedded" && kind === "swap")
|
|
30537
|
-
throw new TradingError("PAIR_UNSUPPORTED", "The embedded wallet cannot swap between base assets from this command yet: that rail executes in one call, so there would be nothing to confirm. Trade a token with it, or name a TEE wallet for a base pair.");
|
|
30538
30600
|
const wallet = payerWallet.kind === "tee" ? payerWallet.wallet : { address: payerWallet.address };
|
|
30539
30601
|
const solana = lazySolanaClient(ctx, flags["--rpc-url"]);
|
|
30540
30602
|
const decimals = await decimalsFor(ctx, from, solana);
|
|
@@ -30560,14 +30622,14 @@ async function swap(args, ctx) {
|
|
|
30560
30622
|
amountRaw = rawAmount(flags["--amount"], decimals);
|
|
30561
30623
|
if (BigInt(amountRaw) > BigInt(Number.MAX_SAFE_INTEGER))
|
|
30562
30624
|
throw new TradingError("INVALID_AMOUNT", "Amount exceeds the venue's exact integer range.");
|
|
30563
|
-
if (payerWallet.kind === "embedded")
|
|
30625
|
+
if (payerWallet.kind === "embedded" && kind === "trade")
|
|
30564
30626
|
await assertDeferredExecuteSupported(ctx, key, id);
|
|
30565
30627
|
if (!await claimOperation(ctx, key, id, kind))
|
|
30566
30628
|
throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this id; no write was resent.");
|
|
30567
30629
|
ctx.deps.stderr.write(`Operation: ${id}
|
|
30568
30630
|
`);
|
|
30569
30631
|
const payer = payerWallet.kind === "tee" ? { type: "linked", linkedWalletId: payerWallet.wallet.id } : { type: "main" };
|
|
30570
|
-
const built = kind === "swap" ? await
|
|
30632
|
+
const built = kind === "swap" ? await requestSwapBuild(ctx, key, {
|
|
30571
30633
|
clientTradeId: id,
|
|
30572
30634
|
from,
|
|
30573
30635
|
to,
|
|
@@ -30589,7 +30651,7 @@ async function swap(args, ctx) {
|
|
|
30589
30651
|
return printTradingResult(ctx, { ...built, clientTradeId: id, kind });
|
|
30590
30652
|
const data = swapBuildSchema.parse(kind === "swap" ? built.payload : built);
|
|
30591
30653
|
if (kind === "swap" && (data.venue !== "jupiter" || data.recipient !== wallet.address))
|
|
30592
|
-
throw new TradingError("INVALID_RESPONSE", "A
|
|
30654
|
+
throw new TradingError("INVALID_RESPONSE", "A base swap must use Jupiter and return to its payer.");
|
|
30593
30655
|
if (kind === "trade" && (built.chain !== "solana" || built.walletAddress !== wallet.address))
|
|
30594
30656
|
throw new TradingError("INVALID_RESPONSE", "The token build does not name the requested Solana payer.");
|
|
30595
30657
|
const artifacts = kind === "swap" ? { ...data, transactionBase64: undefined, quoteSource: undefined, quoteAsset: undefined } : data.artifacts;
|
|
@@ -30616,9 +30678,11 @@ async function swap(args, ctx) {
|
|
|
30616
30678
|
if (!Number.isFinite(data.expiresAt) || data.expiresAt <= ctx.deps.now())
|
|
30617
30679
|
throw new TradingError("QUOTE_EXPIRED", "The quote expired before signing. Start a new intention with a new id.");
|
|
30618
30680
|
if (payerWallet.kind === "embedded") {
|
|
30619
|
-
const executed = await request(ctx, key, "/api/v1/trade/agent/execute", { clientTradeId: id });
|
|
30681
|
+
const executed = kind === "swap" ? await request(ctx, key, "/api/v1/agent/swap/submit", { clientTradeId: id, swapId: data.swapId }) : await request(ctx, key, "/api/v1/trade/agent/execute", { clientTradeId: id });
|
|
30682
|
+
const settled2 = kind === "swap" ? reportSettlement(ctx, executed.payload?.settlement, { asset: to, decimals: outDecimals }, id) : {};
|
|
30620
30683
|
return printTradingResult(ctx, {
|
|
30621
30684
|
...executed,
|
|
30685
|
+
...settled2,
|
|
30622
30686
|
clientTradeId: id,
|
|
30623
30687
|
kind,
|
|
30624
30688
|
quote,
|
|
@@ -30728,8 +30792,6 @@ async function hoodSwap(ctx, args) {
|
|
|
30728
30792
|
if (prior)
|
|
30729
30793
|
return printTradingResult(ctx, prior);
|
|
30730
30794
|
const payer = await tradingPayer(ctx, key, flags["--wallet"], "swap:write", "hood");
|
|
30731
|
-
if (payer.kind === "embedded" && kind === "swap")
|
|
30732
|
-
throw new TradingError("PAIR_UNSUPPORTED", "The embedded wallet cannot swap ETH and USDG from this command: that rail executes in one call, so there would be nothing to confirm. Trade a token with it, or name a Hood TEE wallet.");
|
|
30733
30795
|
const payerAddress = payer.kind === "tee" ? payer.wallet.address : payer.address;
|
|
30734
30796
|
const rpc = lazyEvmRpc(ctx, flags["--rpc-url"]);
|
|
30735
30797
|
const decimals = await hoodDecimals(from, rpc);
|
|
@@ -30742,7 +30804,7 @@ async function hoodSwap(ctx, args) {
|
|
|
30742
30804
|
throw new TradingError("INVALID_AMOUNT", "The selected percentage rounds to zero raw units.");
|
|
30743
30805
|
} else
|
|
30744
30806
|
amountRaw = rawAmount(flags["--amount"], decimals);
|
|
30745
|
-
if (payer.kind === "embedded")
|
|
30807
|
+
if (payer.kind === "embedded" && kind === "trade")
|
|
30746
30808
|
await assertDeferredExecuteSupported(ctx, key, id);
|
|
30747
30809
|
if (!await claimOperation(ctx, key, id, kind))
|
|
30748
30810
|
throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this id; no write was resent.");
|
|
@@ -30751,7 +30813,7 @@ async function hoodSwap(ctx, args) {
|
|
|
30751
30813
|
const payerBody = payer.kind === "tee" ? { type: "linked", linkedWalletId: payer.wallet.id } : { type: "main" };
|
|
30752
30814
|
const base = from.base ?? to.base;
|
|
30753
30815
|
const token = from.base ? to.asset : from.asset;
|
|
30754
|
-
const built = kind === "swap" ? await
|
|
30816
|
+
const built = kind === "swap" ? await requestSwapBuild(ctx, key, {
|
|
30755
30817
|
clientTradeId: id,
|
|
30756
30818
|
from: from.asset,
|
|
30757
30819
|
to: to.asset,
|
|
@@ -30825,8 +30887,16 @@ async function hoodSwap(ctx, args) {
|
|
|
30825
30887
|
if (!Number.isFinite(data.expiresAt) || data.expiresAt <= ctx.deps.now())
|
|
30826
30888
|
throw new TradingError("QUOTE_EXPIRED", "The quote expired before signing. Start a new intention with a new id.");
|
|
30827
30889
|
if (payer.kind === "embedded") {
|
|
30828
|
-
const executed = await request(ctx, key, "/api/v1/trade/agent/execute", { clientTradeId: id });
|
|
30829
|
-
|
|
30890
|
+
const executed = kind === "swap" ? await request(ctx, key, "/api/v1/agent/swap/submit", { clientTradeId: id, swapId: data.swapId }) : await request(ctx, key, "/api/v1/trade/agent/execute", { clientTradeId: id });
|
|
30891
|
+
const settled2 = kind === "swap" ? reportSettlement(ctx, executed.payload?.settlement, { asset: to.asset, decimals: outDecimals }, id) : {};
|
|
30892
|
+
return printTradingResult(ctx, {
|
|
30893
|
+
...executed,
|
|
30894
|
+
...settled2,
|
|
30895
|
+
clientTradeId: id,
|
|
30896
|
+
kind,
|
|
30897
|
+
quote,
|
|
30898
|
+
wallet: safeText(payerAddress)
|
|
30899
|
+
});
|
|
30830
30900
|
}
|
|
30831
30901
|
const wallet = payer.wallet;
|
|
30832
30902
|
if (wallet.chain !== "evm" || !sequenced)
|
|
@@ -30935,7 +31005,7 @@ async function bridgeSwap(ctx, args) {
|
|
|
30935
31005
|
throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this id; no write was resent.");
|
|
30936
31006
|
ctx.deps.stderr.write(`Operation: ${id}
|
|
30937
31007
|
`);
|
|
30938
|
-
const built = await
|
|
31008
|
+
const built = await requestSwapBuild(ctx, key, {
|
|
30939
31009
|
clientTradeId: id,
|
|
30940
31010
|
from,
|
|
30941
31011
|
to,
|
|
@@ -32319,23 +32389,10 @@ async function teeSweepEvm(args, ctx) {
|
|
|
32319
32389
|
if (!address || extra !== undefined) {
|
|
32320
32390
|
return usage(ctx, "Usage: candle tee sweep <0x address> [--rpc-url <url>] [--emergency] [--token <0x...>]... [--from-block <n>]");
|
|
32321
32391
|
}
|
|
32322
|
-
const
|
|
32323
|
-
|
|
32324
|
-
|
|
32325
|
-
|
|
32326
|
-
return usage(ctx, `--token must be an ERC-20 contract address: ${raw || "(empty)"} is ${checked.reason}.`);
|
|
32327
|
-
tokens.push(checked.address);
|
|
32328
|
-
}
|
|
32329
|
-
let fromBlock;
|
|
32330
|
-
const fromBlockRaw = parsed.values["--from-block"];
|
|
32331
|
-
if (fromBlockRaw !== undefined) {
|
|
32332
|
-
if (!/^\d+$/.test(fromBlockRaw))
|
|
32333
|
-
return usage(ctx, `--from-block must be a Hood block number: ${fromBlockRaw} is not.`);
|
|
32334
|
-
fromBlock = BigInt(fromBlockRaw);
|
|
32335
|
-
}
|
|
32336
|
-
const client = resolveHoodClient(ctx, parsed.values["--rpc-url"]);
|
|
32337
|
-
if ("error" in client)
|
|
32338
|
-
return usage(ctx, client.error);
|
|
32392
|
+
const options = sweepOptions(ctx, parsed, tokenFlags);
|
|
32393
|
+
if (typeof options === "number")
|
|
32394
|
+
return options;
|
|
32395
|
+
const { tokens, fromBlock, client } = options;
|
|
32339
32396
|
if (!refuseEnvPassphrase(ctx))
|
|
32340
32397
|
return 1;
|
|
32341
32398
|
if (!requireTty(ctx, "tee sweep"))
|
|
@@ -32354,6 +32411,26 @@ async function teeSweepEvm(args, ctx) {
|
|
|
32354
32411
|
return sweepEvmWallet({ ctx, vault, hold, entry, client, emergency, tokenFlags: tokens, fromBlock });
|
|
32355
32412
|
});
|
|
32356
32413
|
}
|
|
32414
|
+
function sweepOptions(ctx, parsed, tokenFlags) {
|
|
32415
|
+
const tokens = [];
|
|
32416
|
+
for (const raw of tokenFlags) {
|
|
32417
|
+
const checked = checkEvmAddress(raw);
|
|
32418
|
+
if (!checked.ok)
|
|
32419
|
+
return usage(ctx, `--token must be an ERC-20 contract address: ${raw || "(empty)"} is ${checked.reason}.`);
|
|
32420
|
+
tokens.push(checked.address);
|
|
32421
|
+
}
|
|
32422
|
+
let fromBlock;
|
|
32423
|
+
const fromBlockRaw = parsed.values["--from-block"];
|
|
32424
|
+
if (fromBlockRaw !== undefined) {
|
|
32425
|
+
if (!/^\d+$/.test(fromBlockRaw))
|
|
32426
|
+
return usage(ctx, `--from-block must be a Hood block number: ${fromBlockRaw} is not.`);
|
|
32427
|
+
fromBlock = BigInt(fromBlockRaw);
|
|
32428
|
+
}
|
|
32429
|
+
const client = resolveHoodClient(ctx, parsed.values["--rpc-url"]);
|
|
32430
|
+
if ("error" in client)
|
|
32431
|
+
return usage(ctx, client.error);
|
|
32432
|
+
return { tokens, fromBlock, client };
|
|
32433
|
+
}
|
|
32357
32434
|
async function sweepEvmWallet(input) {
|
|
32358
32435
|
const { ctx, client, emergency, hold } = input;
|
|
32359
32436
|
const { deps, json } = ctx;
|
|
@@ -32931,78 +33008,83 @@ async function teeDisableEvm(args, ctx) {
|
|
|
32931
33008
|
const resolvedVault = vaultPathFor(ctx, parsed);
|
|
32932
33009
|
if ("error" in resolvedVault)
|
|
32933
33010
|
return usage(ctx, resolvedVault.error);
|
|
32934
|
-
const { deps, json } = ctx;
|
|
32935
33011
|
await printIdentity(ctx);
|
|
32936
33012
|
return runVaultCommand(ctx, async ({ hold }) => {
|
|
32937
33013
|
const raw = await requireVaultRaw(ctx, resolvedVault);
|
|
32938
33014
|
const opened = await unlockInteractively(ctx, resolvedVault.path, raw, { acceptOlderCopy: true });
|
|
32939
|
-
|
|
33015
|
+
const vault = hold(opened.vault);
|
|
32940
33016
|
const entry = findEvmTee(vault, address);
|
|
32941
33017
|
if (entry === undefined)
|
|
32942
33018
|
return unknownWallet(ctx, address);
|
|
32943
|
-
|
|
32944
|
-
|
|
32945
|
-
|
|
33019
|
+
return (await disableEvmEntry(ctx, vault, hold, entry, false)).code;
|
|
33020
|
+
});
|
|
33021
|
+
}
|
|
33022
|
+
async function disableEvmEntry(ctx, opened, hold, entry, inDemote) {
|
|
33023
|
+
const { deps, json } = ctx;
|
|
33024
|
+
let vault = opened;
|
|
33025
|
+
if (entry.linkedWalletId === undefined) {
|
|
33026
|
+
writeLocalFailure(deps, { code: "TEE_WALLET_NOT_ENABLED", message: `${entry.address} was never enabled; there is nothing to stop.` }, json);
|
|
33027
|
+
return { code: 1, vault };
|
|
33028
|
+
}
|
|
33029
|
+
const linkedWalletId = entry.linkedWalletId;
|
|
33030
|
+
const stopRequestedAt = entry.tee?.stopRequestedAt ?? new Date(deps.now()).toISOString();
|
|
33031
|
+
vault = hold(await patchTee(ctx, vault, entry.id, (tee) => ({ ...tee, stopRequestedAt })));
|
|
33032
|
+
const unconfirmed = (detail, suggestion) => {
|
|
33033
|
+
if (json) {
|
|
33034
|
+
writeJson(deps, {
|
|
33035
|
+
ok: false,
|
|
33036
|
+
code: "STOP_UNCONFIRMED",
|
|
33037
|
+
message: detail,
|
|
33038
|
+
address: entry.address,
|
|
33039
|
+
linkedWalletId,
|
|
33040
|
+
stopRequestedAt,
|
|
33041
|
+
remoteEnforcement: "unconfirmed",
|
|
33042
|
+
suggestion
|
|
33043
|
+
});
|
|
33044
|
+
return { code: 1, vault };
|
|
32946
33045
|
}
|
|
32947
|
-
|
|
32948
|
-
const stopRequestedAt = entry.tee?.stopRequestedAt ?? new Date(deps.now()).toISOString();
|
|
32949
|
-
vault = hold(await patchTee(ctx, vault, entry.id, (tee) => ({ ...tee, stopRequestedAt })));
|
|
32950
|
-
const unconfirmed = (detail, suggestion) => {
|
|
32951
|
-
if (json) {
|
|
32952
|
-
writeJson(deps, {
|
|
32953
|
-
ok: false,
|
|
32954
|
-
code: "STOP_UNCONFIRMED",
|
|
32955
|
-
message: detail,
|
|
32956
|
-
address: entry.address,
|
|
32957
|
-
linkedWalletId,
|
|
32958
|
-
stopRequestedAt,
|
|
32959
|
-
remoteEnforcement: "unconfirmed",
|
|
32960
|
-
suggestion
|
|
32961
|
-
});
|
|
32962
|
-
return 1;
|
|
32963
|
-
}
|
|
32964
|
-
deps.stderr.write(`${detail}
|
|
33046
|
+
deps.stderr.write(`${detail}
|
|
32965
33047
|
Stop intent recorded locally at ${stopRequestedAt}: this CLI will not fund ${entry.address} again. Remote enforcement is UNCONFIRMED: the agent may still trade until the server acknowledges the stop.
|
|
32966
33048
|
${suggestion}
|
|
32967
33049
|
`);
|
|
32968
|
-
|
|
32969
|
-
|
|
32970
|
-
|
|
32971
|
-
|
|
32972
|
-
|
|
32973
|
-
|
|
32974
|
-
|
|
32975
|
-
|
|
32976
|
-
|
|
32977
|
-
|
|
32978
|
-
|
|
32979
|
-
|
|
32980
|
-
|
|
32981
|
-
|
|
32982
|
-
|
|
32983
|
-
|
|
32984
|
-
|
|
32985
|
-
|
|
32986
|
-
|
|
32987
|
-
|
|
33050
|
+
return { code: 1, vault };
|
|
33051
|
+
};
|
|
33052
|
+
const apiKey = await resolveApiKey(deps, ctx.profile);
|
|
33053
|
+
if (!apiKey) {
|
|
33054
|
+
return unconfirmed("No API key available, so the server was not asked to stop the agent.", `Stop it from your Candle session (revoke linked wallet ${linkedWalletId}), or restore the key and re-run: candle tee disable ${entry.address}`);
|
|
33055
|
+
}
|
|
33056
|
+
const result = await apiRequest(`/api/v1/agent/wallets/${encodeURIComponent(linkedWalletId)}`, {
|
|
33057
|
+
method: "DELETE",
|
|
33058
|
+
auth: "key",
|
|
33059
|
+
credentials: { apiKey },
|
|
33060
|
+
apiUrl: ctx.apiUrl,
|
|
33061
|
+
fetch: deps.fetch,
|
|
33062
|
+
env: deps.env
|
|
33063
|
+
});
|
|
33064
|
+
if (!result.ok) {
|
|
33065
|
+
return unconfirmed(`The stop request failed: ${result.message ?? `HTTP ${result.status}`}.`, `Re-run: candle tee disable ${entry.address}. If the key is lost or revoked, stop it from your Candle session (revoke linked wallet ${linkedWalletId}).`);
|
|
33066
|
+
}
|
|
33067
|
+
const outcome = readDisableOutcome(result.body);
|
|
33068
|
+
for (const warning of bridgeDisableWarnings(entry.address, result.body))
|
|
33069
|
+
deps.stderr.write(`${warning}
|
|
32988
33070
|
`);
|
|
32989
|
-
|
|
32990
|
-
|
|
32991
|
-
|
|
32992
|
-
|
|
32993
|
-
|
|
32994
|
-
|
|
33071
|
+
if (json) {
|
|
33072
|
+
writeJson(deps, { address: entry.address, linkedWalletId, ...result.body });
|
|
33073
|
+
return { code: outcome.complete ? 0 : 3, vault };
|
|
33074
|
+
}
|
|
33075
|
+
if (outcome.complete) {
|
|
33076
|
+
deps.stdout.write(`Stopped ${entry.address}. Remote signing denial verified; the wallet is quarantined.
|
|
32995
33077
|
`);
|
|
33078
|
+
if (!inDemote)
|
|
32996
33079
|
deps.stdout.write(`Recover the funds: candle tee sweep ${entry.address}
|
|
32997
33080
|
`);
|
|
32998
|
-
|
|
32999
|
-
|
|
33000
|
-
|
|
33081
|
+
return { code: 0, vault };
|
|
33082
|
+
}
|
|
33083
|
+
deps.stdout.write(`Agent trading stopped at Candle for ${entry.address}. Remote policy verification is pending${outcome.reasonCode ? ` (${outcome.reasonCode})` : ""}.
|
|
33001
33084
|
Funds remain in the TEE wallet and its TEE signing authority may still be active. Re-run: candle tee disable ${entry.address}
|
|
33002
|
-
If the provider is down or theft is suspected: candle tee sweep ${entry.address} --emergency
|
|
33003
|
-
`);
|
|
33004
|
-
|
|
33005
|
-
});
|
|
33085
|
+
${inDemote ? "" : `If the provider is down or theft is suspected: candle tee sweep ${entry.address} --emergency
|
|
33086
|
+
`}`);
|
|
33087
|
+
return { code: 3, vault };
|
|
33006
33088
|
}
|
|
33007
33089
|
async function vaultDemoteEvm(args, ctx) {
|
|
33008
33090
|
const { rest, values: tokenFlags } = extractRepeated(args, "--token");
|
|
@@ -33017,6 +33099,9 @@ async function vaultDemoteEvm(args, ctx) {
|
|
|
33017
33099
|
if (!address || extra !== undefined) {
|
|
33018
33100
|
return usage(ctx, "Usage: candle vault demote <tee-address> [--rpc-url <url>] [--emergency] [--sweep-to <label>]");
|
|
33019
33101
|
}
|
|
33102
|
+
const options = sweepOptions(ctx, parsed, tokenFlags);
|
|
33103
|
+
if (typeof options === "number")
|
|
33104
|
+
return options;
|
|
33020
33105
|
if (!refuseEnvPassphrase(ctx))
|
|
33021
33106
|
return 1;
|
|
33022
33107
|
if (!requireTty(ctx, "vault demote"))
|
|
@@ -33026,13 +33111,13 @@ async function vaultDemoteEvm(args, ctx) {
|
|
|
33026
33111
|
return usage(ctx, resolvedVault.error);
|
|
33027
33112
|
const sweepTo = parsed.values["--sweep-to"];
|
|
33028
33113
|
let emergency = parsed.booleans.has("--emergency");
|
|
33029
|
-
|
|
33114
|
+
return runVaultCommand(ctx, async ({ hold }) => {
|
|
33030
33115
|
const raw = await requireVaultRaw(ctx, resolvedVault);
|
|
33031
33116
|
const opened = await unlockInteractively(ctx, resolvedVault.path, raw, {
|
|
33032
33117
|
acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
|
|
33033
33118
|
});
|
|
33034
|
-
|
|
33035
|
-
|
|
33119
|
+
let vault = hold(opened.vault);
|
|
33120
|
+
let entry = findEvmTee(vault, address);
|
|
33036
33121
|
if (entry === undefined)
|
|
33037
33122
|
return unknownWallet(ctx, address);
|
|
33038
33123
|
if (entry.tee?.vaultDestination === undefined) {
|
|
@@ -33045,39 +33130,40 @@ async function vaultDemoteEvm(args, ctx) {
|
|
|
33045
33130
|
chain: "evm",
|
|
33046
33131
|
acceptUnknownExposure: parsed.booleans.has("--accept-unknown-exposure")
|
|
33047
33132
|
});
|
|
33048
|
-
hold(await patchTee(ctx, vault, entry.id, (tee) => ({ ...tee, vaultDestination: destination.address })));
|
|
33133
|
+
vault = hold(await patchTee(ctx, vault, entry.id, (tee) => ({ ...tee, vaultDestination: destination.address })));
|
|
33134
|
+
entry = findEvmTee(vault, address);
|
|
33049
33135
|
}
|
|
33050
33136
|
if (entry.linkedWalletId === undefined) {
|
|
33051
33137
|
ctx.deps.stdout.write(`No linkedWalletId is recorded for ${entry.address}; disable was not called and remote authority stays unknown.
|
|
33052
33138
|
`);
|
|
33053
33139
|
emergency = true;
|
|
33054
33140
|
}
|
|
33055
|
-
|
|
33056
|
-
|
|
33057
|
-
|
|
33058
|
-
|
|
33059
|
-
|
|
33060
|
-
|
|
33061
|
-
|
|
33062
|
-
|
|
33063
|
-
|
|
33064
|
-
ctx.deps.stdout.write(`The disable is not yet verified; the sweep below will wait for quarantine. If the provider is down or theft is suspected, run: candle vault demote ${address} --emergency
|
|
33141
|
+
if (!emergency) {
|
|
33142
|
+
await printIdentity(ctx);
|
|
33143
|
+
const disabled = await disableEvmEntry(ctx, vault, hold, entry, true);
|
|
33144
|
+
if (disabled.code !== 0 && disabled.code !== 3)
|
|
33145
|
+
return disabled.code;
|
|
33146
|
+
vault = disabled.vault;
|
|
33147
|
+
entry = findEvmTee(vault, address);
|
|
33148
|
+
if (disabled.code === 3) {
|
|
33149
|
+
ctx.deps.stdout.write(`The disable is not yet verified; the sweep below will wait for quarantine. If the provider is down or theft is suspected, run: candle vault demote ${address} --emergency
|
|
33065
33150
|
`);
|
|
33151
|
+
}
|
|
33066
33152
|
}
|
|
33067
|
-
|
|
33068
|
-
|
|
33069
|
-
|
|
33070
|
-
|
|
33071
|
-
|
|
33072
|
-
|
|
33073
|
-
|
|
33074
|
-
|
|
33075
|
-
|
|
33076
|
-
|
|
33077
|
-
|
|
33078
|
-
|
|
33079
|
-
|
|
33080
|
-
|
|
33153
|
+
if (!ctx.json)
|
|
33154
|
+
ctx.deps.stdout.write(`Sweeping remaining funds to ${entry.tee?.vaultDestination}...
|
|
33155
|
+
`);
|
|
33156
|
+
return sweepEvmWallet({
|
|
33157
|
+
ctx,
|
|
33158
|
+
vault,
|
|
33159
|
+
hold,
|
|
33160
|
+
entry,
|
|
33161
|
+
client: options.client,
|
|
33162
|
+
emergency,
|
|
33163
|
+
tokenFlags: options.tokens,
|
|
33164
|
+
fromBlock: options.fromBlock
|
|
33165
|
+
});
|
|
33166
|
+
});
|
|
33081
33167
|
}
|
|
33082
33168
|
var MAX_TOKEN_TRANSFER_GAS = 1000000n;
|
|
33083
33169
|
var init_tee_evm = __esm(() => {
|
|
@@ -33445,6 +33531,9 @@ async function commitTee(deps, opened, mutate, opts = {}) {
|
|
|
33445
33531
|
};
|
|
33446
33532
|
}
|
|
33447
33533
|
}
|
|
33534
|
+
function vaultActiveTee(ctx, resolved) {
|
|
33535
|
+
return { source: "vault", resolved, entry: resolved.legacyView, path: "vault", ctx };
|
|
33536
|
+
}
|
|
33448
33537
|
function applyKeystoreViewToVaultEntry(entry, view) {
|
|
33449
33538
|
if (view.linkedWalletId !== undefined)
|
|
33450
33539
|
entry.linkedWalletId = view.linkedWalletId;
|
|
@@ -33499,18 +33588,8 @@ async function openActiveTee(ctx, parsed, address, access3) {
|
|
|
33499
33588
|
const resolved = await resolveTeeAddress(ctx, parsed, address, () => openExistingTeeStore(ctx, parsed), access3);
|
|
33500
33589
|
if (!resolved.ok)
|
|
33501
33590
|
return { ok: false, code: resolved.code };
|
|
33502
|
-
if (resolved.resolved.source === "vault")
|
|
33503
|
-
return {
|
|
33504
|
-
ok: true,
|
|
33505
|
-
active: {
|
|
33506
|
-
source: "vault",
|
|
33507
|
-
resolved: resolved.resolved,
|
|
33508
|
-
entry: resolved.resolved.legacyView,
|
|
33509
|
-
path: "vault",
|
|
33510
|
-
ctx
|
|
33511
|
-
}
|
|
33512
|
-
};
|
|
33513
|
-
}
|
|
33591
|
+
if (resolved.resolved.source === "vault")
|
|
33592
|
+
return { ok: true, active: vaultActiveTee(ctx, resolved.resolved) };
|
|
33514
33593
|
return {
|
|
33515
33594
|
ok: true,
|
|
33516
33595
|
active: {
|
|
@@ -34120,7 +34199,7 @@ async function teeStatus(args, ctx) {
|
|
|
34120
34199
|
releaseResolvedTee(local);
|
|
34121
34200
|
}
|
|
34122
34201
|
}
|
|
34123
|
-
async function teeDisable(args, ctx) {
|
|
34202
|
+
async function teeDisable(args, ctx, demote) {
|
|
34124
34203
|
const { deps, apiUrl, json } = ctx;
|
|
34125
34204
|
if (namesEvmWallet(args))
|
|
34126
34205
|
return teeDisableEvm(args, ctx);
|
|
@@ -34133,7 +34212,7 @@ async function teeDisable(args, ctx) {
|
|
|
34133
34212
|
if (!address || extra !== undefined)
|
|
34134
34213
|
return usage2(ctx, "Usage: candle tee disable <address>");
|
|
34135
34214
|
await printIdentity(ctx);
|
|
34136
|
-
const openedActive = await openActiveTee(ctx, parsed, address, "read");
|
|
34215
|
+
const openedActive = demote ? { ok: true, active: vaultActiveTee(ctx, demote.resolved) } : await openActiveTee(ctx, parsed, address, "read");
|
|
34137
34216
|
if (!openedActive.ok)
|
|
34138
34217
|
return openedActive.code;
|
|
34139
34218
|
const { active } = openedActive;
|
|
@@ -34202,17 +34281,37 @@ ${suggestion}
|
|
|
34202
34281
|
if (outcome.complete) {
|
|
34203
34282
|
deps.stdout.write(`Stopped ${address}. Remote signing denial verified; the wallet is quarantined.
|
|
34204
34283
|
`);
|
|
34205
|
-
|
|
34284
|
+
if (!demote)
|
|
34285
|
+
deps.stdout.write(`Recover the funds: candle tee sweep ${address}
|
|
34206
34286
|
`);
|
|
34207
34287
|
return 0;
|
|
34208
34288
|
}
|
|
34209
34289
|
deps.stdout.write(`Agent trading stopped at Candle for ${address}. Remote policy verification is pending${outcome.reasonCode ? ` (${outcome.reasonCode})` : ""}.
|
|
34210
34290
|
Funds remain in the TEE wallet and its TEE signing authority may still be active. Re-run: candle tee disable ${address}
|
|
34211
|
-
If the provider is down or theft is suspected: candle tee sweep ${address} --emergency
|
|
34291
|
+
If the provider is down or theft is suspected: ${demote ? `candle vault demote ${address}` : `candle tee sweep ${address}`} --emergency
|
|
34212
34292
|
`);
|
|
34213
34293
|
return 3;
|
|
34214
34294
|
} finally {
|
|
34215
|
-
|
|
34295
|
+
if (!demote)
|
|
34296
|
+
releaseActiveTee(active);
|
|
34297
|
+
}
|
|
34298
|
+
}
|
|
34299
|
+
async function nothingLeftToSweep(solana, entry) {
|
|
34300
|
+
const tee = entry.tee;
|
|
34301
|
+
if ((tee?.sweepPending?.length ?? 0) > 0)
|
|
34302
|
+
return false;
|
|
34303
|
+
if ((tee?.sweepReceipts?.length ?? 0) > 0 && tee?.sweptAt === undefined)
|
|
34304
|
+
return false;
|
|
34305
|
+
try {
|
|
34306
|
+
if (await solana.rpc.getBalance(entry.address) > 0n)
|
|
34307
|
+
return false;
|
|
34308
|
+
for (const programId of [TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID]) {
|
|
34309
|
+
if ((await solana.rpc.getTokenAccountsByOwner(entry.address, programId)).length > 0)
|
|
34310
|
+
return false;
|
|
34311
|
+
}
|
|
34312
|
+
return true;
|
|
34313
|
+
} catch {
|
|
34314
|
+
return false;
|
|
34216
34315
|
}
|
|
34217
34316
|
}
|
|
34218
34317
|
function refusalState(raw) {
|
|
@@ -34320,7 +34419,7 @@ async function broadcastMessage(rpc, ctx, secret, message, blockhash, pending, r
|
|
|
34320
34419
|
error: `transaction ${signature} was not finalized within ${CONFIRM_MAX_POLLS * CONFIRM_POLL_MS / 1000}s; it may still land${echoNote}`
|
|
34321
34420
|
};
|
|
34322
34421
|
}
|
|
34323
|
-
async function teeSweep(args, ctx) {
|
|
34422
|
+
async function teeSweep(args, ctx, demote) {
|
|
34324
34423
|
const { deps, apiUrl, json } = ctx;
|
|
34325
34424
|
if (namesEvmWallet(args))
|
|
34326
34425
|
return teeSweepEvm(args, ctx);
|
|
@@ -34336,11 +34435,24 @@ async function teeSweep(args, ctx) {
|
|
|
34336
34435
|
const [address, extra] = parsed.positionals;
|
|
34337
34436
|
if (!address || extra !== undefined)
|
|
34338
34437
|
return usage2(ctx, "Usage: candle tee sweep <address> [--rpc-url <url>] [--emergency]");
|
|
34339
|
-
const solana = await openSolanaClient(ctx, parsed.values["--rpc-url"]);
|
|
34438
|
+
const solana = demote ? demote.solana : await openSolanaClient(ctx, parsed.values["--rpc-url"]);
|
|
34340
34439
|
if ("error" in solana)
|
|
34341
34440
|
return usage2(ctx, solana.error);
|
|
34342
34441
|
const emergency = parsed.booleans.has("--emergency");
|
|
34343
|
-
|
|
34442
|
+
let openedActive;
|
|
34443
|
+
if (demote) {
|
|
34444
|
+
try {
|
|
34445
|
+
await grantSignAccess(demote.resolved);
|
|
34446
|
+
} catch (error) {
|
|
34447
|
+
if (!isVaultError(error))
|
|
34448
|
+
throw error;
|
|
34449
|
+
writeLocalFailure(deps, { code: error.code, message: error.message }, json);
|
|
34450
|
+
return error.exitCode;
|
|
34451
|
+
}
|
|
34452
|
+
openedActive = { ok: true, active: vaultActiveTee(ctx, demote.resolved) };
|
|
34453
|
+
} else {
|
|
34454
|
+
openedActive = await openActiveTee(ctx, parsed, address, "sign");
|
|
34455
|
+
}
|
|
34344
34456
|
if (!openedActive.ok)
|
|
34345
34457
|
return openedActive.code;
|
|
34346
34458
|
const { active } = openedActive;
|
|
@@ -35124,7 +35236,8 @@ Stop the agent from your Candle session if you have not, and re-run candle tee d
|
|
|
35124
35236
|
`);
|
|
35125
35237
|
return 3;
|
|
35126
35238
|
} finally {
|
|
35127
|
-
|
|
35239
|
+
if (!demote)
|
|
35240
|
+
releaseActiveTee(active);
|
|
35128
35241
|
}
|
|
35129
35242
|
}
|
|
35130
35243
|
var MIN_PASSPHRASE_LENGTH = 12, USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", CONFIRM_POLL_MS = 2000, CONFIRM_MAX_POLLS = 45, StoreChangedError, TOKEN_ACCOUNT_STATE_OFFSET = 108, TOKEN_ACCOUNT_STATE_FROZEN = 2;
|
|
@@ -35139,6 +35252,7 @@ var init_tee = __esm(() => {
|
|
|
35139
35252
|
init_solana_endpoint();
|
|
35140
35253
|
init_solana_lite();
|
|
35141
35254
|
init_token_2022();
|
|
35255
|
+
init_errors();
|
|
35142
35256
|
init_store();
|
|
35143
35257
|
init_tee_resolve();
|
|
35144
35258
|
init_wallet_import_flow();
|
|
@@ -50758,7 +50872,7 @@ var init_convert = __esm(() => {
|
|
|
50758
50872
|
});
|
|
50759
50873
|
|
|
50760
50874
|
// ../mcp/src/version.ts
|
|
50761
|
-
var SERVER_VERSION = "0.10.
|
|
50875
|
+
var SERVER_VERSION = "0.10.7";
|
|
50762
50876
|
|
|
50763
50877
|
// ../mcp/src/update-notice.ts
|
|
50764
50878
|
function newer(a, b) {
|
|
@@ -52063,7 +52177,7 @@ function planTableMarkdown(table) {
|
|
|
52063
52177
|
return [line(headers2), line(headers2.map(() => "---")), ...rows.map(line)].join(`
|
|
52064
52178
|
`);
|
|
52065
52179
|
}
|
|
52066
|
-
var PLAN_CAPABILITY_NOTE2 = "A capability marked yes is what the plan allows. It is subject to the deployment's own switches (perps and own-wallet bridges each have one) and to the wallet, scopes and setup the feature needs.", PLAN_CAPABILITY_LABELS2, PLAN_LABELS2;
|
|
52180
|
+
var PLAN_CAPABILITY_NOTE2 = "A capability marked yes is what the plan allows. It is subject to the deployment's own switches (perps and own-wallet bridges each have one) and to the wallet, scopes and setup the feature needs. Every plan can launch a token from its embedded wallet, with an optional dev buy (same transaction on Solana; best-effort follow-up on Hood), through the headless launch; the two launch rows are additional routes, not the only ones.", PLAN_CAPABILITY_LABELS2, PLAN_LABELS2;
|
|
52067
52181
|
var init_plans = __esm(() => {
|
|
52068
52182
|
PLAN_CAPABILITY_LABELS2 = {
|
|
52069
52183
|
tradeCandleTokens: "Trade Candle-launched tokens",
|
|
@@ -52072,8 +52186,8 @@ var init_plans = __esm(() => {
|
|
|
52072
52186
|
sellExternalTokens: "Sell tokens not launched on Candle",
|
|
52073
52187
|
buyExternalTokens: "Buy tokens not launched on Candle",
|
|
52074
52188
|
hyperliquidPerps: "Hyperliquid perps (when enabled)",
|
|
52075
|
-
selfLaunch: "
|
|
52076
|
-
atomicLaunch: "Atomic launch
|
|
52189
|
+
selfLaunch: "Launch from a linked or TEE wallet (self-signed)",
|
|
52190
|
+
atomicLaunch: "Atomic launch: launch + 1–4 first buys in one bundle",
|
|
52077
52191
|
createLinkedWallets: "Create linked wallets",
|
|
52078
52192
|
importLinkedWallets: "Import linked wallets",
|
|
52079
52193
|
limitOrders: "Limit orders",
|
|
@@ -52288,7 +52402,7 @@ function registerTools(server, env = process.env) {
|
|
|
52288
52402
|
};
|
|
52289
52403
|
register("candle_launch_token", {
|
|
52290
52404
|
title: "Launch a token on Candle",
|
|
52291
|
-
description: "Launch a new token via the Candle headless launch API. Set dryRun: true to validate without spending anything.",
|
|
52405
|
+
description: "Launch a new token via the Candle headless launch API, from the account's embedded wallet. Works on every plan, Free included. Set dryRun: true to validate without spending anything.",
|
|
52292
52406
|
inputSchema: launchTokenShape
|
|
52293
52407
|
}, async (args) => callAndRelay("candle_launch_token", args, cfg));
|
|
52294
52408
|
register("candle_get_market", {
|
|
@@ -52454,7 +52568,7 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
|
|
|
52454
52568
|
});
|
|
52455
52569
|
register("candle_launch_and_seed", {
|
|
52456
52570
|
title: "Launch a token and seed it",
|
|
52457
|
-
description: "Launch a new token with an optional dev-buy seed
|
|
52571
|
+
description: "Launch a new token from the account's embedded wallet, with an optional dev-buy seed (in the " + "launch transaction on solana, a best-effort follow-up on hood). Works on every plan, Free included (this is not the atomic launch). Then " + "return the fresh market state and token links in one result. MOVES REAL FUNDS unless " + "dryRun. Seeds above the platform dev-buy ceiling are rejected (DEV_BUY_TOO_HIGH); " + "launch, then top up with candle_trade.",
|
|
52458
52572
|
inputSchema: launchAndSeedShape
|
|
52459
52573
|
}, async (args) => {
|
|
52460
52574
|
const result = await executeLaunchAndSeed(args, cfg, fetch);
|
|
@@ -52631,7 +52745,7 @@ var init_tools = __esm(() => {
|
|
|
52631
52745
|
launchAndSeedShape = {
|
|
52632
52746
|
...seedableLaunchShape,
|
|
52633
52747
|
clientLaunchId: exports_external.string().optional().describe("Idempotency key. Auto-generated when omitted and echoed in the result."),
|
|
52634
|
-
devBuy: exports_external.string().optional().describe('Seed buy in DECIMAL units of the quote asset this launch selects (e.g. "0.25" SOL, or ' + "ETH on hood), bundled into the launch transaction itself. Follows quoteAsset, which " + "defaults to sol on solana and eth on hood. Capped by the platform dev-buy ceiling; for " + "a larger seed, launch then follow with candle_trade.")
|
|
52748
|
+
devBuy: exports_external.string().optional().describe('Seed buy in DECIMAL units of the quote asset this launch selects (e.g. "0.25" SOL, or ' + "ETH on hood), bundled into the launch transaction itself on solana and sent as a follow-up " + "transaction on hood. Paid from the account's embedded wallet. Follows quoteAsset, which " + "defaults to sol on solana and eth on hood. Capped by the platform dev-buy ceiling; for " + "a larger seed, launch then follow with candle_trade.")
|
|
52635
52749
|
};
|
|
52636
52750
|
transferShape = {
|
|
52637
52751
|
chain: exports_external.enum(["solana", "hood"]).describe("Which chain the transfer executes on"),
|
|
@@ -52770,7 +52884,7 @@ init_render();
|
|
|
52770
52884
|
init_secret_store();
|
|
52771
52885
|
|
|
52772
52886
|
// src/version.ts
|
|
52773
|
-
var CLI_VERSION = "0.11.
|
|
52887
|
+
var CLI_VERSION = "0.11.18";
|
|
52774
52888
|
|
|
52775
52889
|
// src/commands/auth.ts
|
|
52776
52890
|
init_keys_embedded_wallet();
|
|
@@ -52794,7 +52908,7 @@ async function authLogin(args, ctx) {
|
|
|
52794
52908
|
}
|
|
52795
52909
|
const scopes = parsed.values["--scopes"] ? parseScopesList(parsed.values["--scopes"]) : undefined;
|
|
52796
52910
|
if (scopes?.includes("transfer:bound")) {
|
|
52797
|
-
writeUsageFailure(deps, "transfer:bound is not available on a device login. Mint a
|
|
52911
|
+
writeUsageFailure(deps, "transfer:bound is not available on a device login. Mint a Withdraw key with: candle keys create --access withdraw, or widen an existing key with: candle keys access <prefix> --access withdraw", json);
|
|
52798
52912
|
return 2;
|
|
52799
52913
|
}
|
|
52800
52914
|
const label = parsed.values["--label"]?.trim();
|
|
@@ -53282,7 +53396,7 @@ var HELP = {
|
|
|
53282
53396
|
swap: {
|
|
53283
53397
|
group: "Trade",
|
|
53284
53398
|
summary: "Quote, confirm and swap on Solana or Hood, bridge between them; read an operation by id",
|
|
53285
|
-
description: "Swaps run through a TEE wallet's bound key: the quote is shown and confirmed before anything is sent
|
|
53399
|
+
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, the key needs a raw cap on the origin asset, 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 sell a token it holds that was not launched on Candle; buying such a token needs Pro or Max (TIER_REQUIRED otherwise). 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.",
|
|
53286
53400
|
usage: ["candle swap <from> <to> [flags]", "candle swap status <id>"],
|
|
53287
53401
|
rows: [
|
|
53288
53402
|
{
|
|
@@ -53407,7 +53521,7 @@ var HELP = {
|
|
|
53407
53521
|
transfer: {
|
|
53408
53522
|
group: "Trade",
|
|
53409
53523
|
summary: "Move funds out of a TEE wallet to your own wallets or its vault, through its bound key",
|
|
53410
|
-
description: "Moves one asset out of a TEE wallet this machine can sign for. The bound key must be a
|
|
53524
|
+
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. To a linked wallet the amount counts against the key's spend caps and 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.",
|
|
53411
53525
|
usage: [
|
|
53412
53526
|
"candle transfer --to <address|wallet name|vault> --asset <SOL|USDC|CNDL|ETH|USDG>|--mint <mint>|--token <0x...> --amount <decimal|max> [--wallet <tee>] [--yes] [--json]"
|
|
53413
53527
|
],
|
|
@@ -53440,10 +53554,10 @@ var HELP = {
|
|
|
53440
53554
|
},
|
|
53441
53555
|
launch: {
|
|
53442
53556
|
group: "Trade",
|
|
53443
|
-
summary: "Create a token on Solana or Hood
|
|
53444
|
-
description: "Creates a token
|
|
53557
|
+
summary: "Create a token on Solana or Hood, with an optional first buy",
|
|
53558
|
+
description: "Creates a token from a TEE wallet. Without --buy it makes no first buy, so the launch and the position are two decisions, and the buy is a separate candle swap. --buy <amount> adds a first buy from the same wallet, in whole units of the quote asset (SOL, USDC or CNDL on Solana, ETH on Hood; not USDG), within the dev-buy ceilings: on Solana it rides in the launch transaction, and on Hood it is a last leg after the curve and the fee. Candle reserves the buy against the key's spend window before anything is signed. --buy needs a server with TEE_LAUNCH_BUYS_ENABLED on, which is off by default; while it is off Candle refuses the buy and nothing is built. Needs the launch:write scope and allowLaunch on the wallet, which only the account owner can turn on (candle wallets allow-launch <wallet>, over the device token; an agent key cannot). The wallet decides the chain: a Hood TEE wallet launches on Hood, needs --dex-version, and signs one leg at a time (the curve, then the fee, then the buy with --buy), each only after the one before it landed. Launching from a TEE wallet needs Pro or Max (TIER_REQUIRED otherwise; candle plans). Every plan, Free included, can launch from its embedded wallet with an optional dev buy (same transaction on Solana; best-effort follow-up on Hood), through the MCP tool candle_launch_and_seed or the SDK's launch(); this command does not use that path.",
|
|
53445
53559
|
usage: [
|
|
53446
|
-
"candle launch --name <name> --symbol <symbol> --image-url <url> --wallet <tee> [--quote-asset <asset>] [--dex-version v3|v4]"
|
|
53560
|
+
"candle launch --name <name> --symbol <symbol> --image-url <url> --wallet <tee> [--buy <amount>] [--quote-asset <asset>] [--dex-version v3|v4]"
|
|
53447
53561
|
],
|
|
53448
53562
|
rows: [],
|
|
53449
53563
|
flags: [
|
|
@@ -53451,6 +53565,10 @@ var HELP = {
|
|
|
53451
53565
|
{ invocation: "--symbol <symbol>", description: "The token's ticker" },
|
|
53452
53566
|
{ invocation: "--image-url <url>", description: "The token image, already hosted" },
|
|
53453
53567
|
{ invocation: "--wallet <tee>", description: "The TEE wallet that creates it" },
|
|
53568
|
+
{
|
|
53569
|
+
invocation: "--buy <amount>",
|
|
53570
|
+
description: "A first buy from the same wallet, in whole units of the quote asset; needs TEE_LAUNCH_BUYS_ENABLED on the server"
|
|
53571
|
+
},
|
|
53454
53572
|
{ invocation: "--quote-asset <asset>", description: "sol, usdc or cndl on Solana; eth or usdg on Hood" },
|
|
53455
53573
|
{
|
|
53456
53574
|
invocation: "--dex-version v3|v4",
|
|
@@ -53459,7 +53577,8 @@ var HELP = {
|
|
|
53459
53577
|
],
|
|
53460
53578
|
examples: [
|
|
53461
53579
|
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne",
|
|
53462
|
-
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet HoodAgent --dex-version v4"
|
|
53580
|
+
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet HoodAgent --dex-version v4",
|
|
53581
|
+
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne --buy 0.1"
|
|
53463
53582
|
],
|
|
53464
53583
|
env: ENV_API
|
|
53465
53584
|
},
|
|
@@ -53520,14 +53639,14 @@ var HELP = {
|
|
|
53520
53639
|
rows: [
|
|
53521
53640
|
{
|
|
53522
53641
|
invocation: "list [--scopes]",
|
|
53523
|
-
description: "List API keys: name and Read,
|
|
53642
|
+
description: "List API keys: name and Read, Write or Withdraw access; --scopes adds the raw scopes"
|
|
53524
53643
|
},
|
|
53525
53644
|
{
|
|
53526
|
-
invocation: "create [--access read|
|
|
53527
|
-
description: "Create an API key; --access mints one of the three levels (
|
|
53645
|
+
invocation: "create [--access read|write|withdraw | --scopes <a,b,c>] [--label <name>] [--expires-in <days>] [--tx-limit <usd> [--reset daily|weekly|monthly|never]] [--embedded-wallet deny|allow]",
|
|
53646
|
+
description: "Create an API key; --access mints one of the three levels (withdraw can move funds out of the wallet it runs). --embedded-wallet defaults to deny; allow lets the key spend the account's embedded wallet and is confirmed at a terminal"
|
|
53528
53647
|
},
|
|
53529
53648
|
{
|
|
53530
|
-
invocation: "access <prefix|label|self> (--access read|
|
|
53649
|
+
invocation: "access <prefix|label|self> (--access read|write|withdraw [--yes] | --history)",
|
|
53531
53650
|
description: "Change an existing key's level in place (same key, wallets and caps). Widening needs the device token and the prefix typed back at a terminal; --yes skips the prompt when narrowing; self narrows the profile's own key. --history lists the key's changes and who made them"
|
|
53532
53651
|
},
|
|
53533
53652
|
{
|
|
@@ -53562,9 +53681,9 @@ var HELP = {
|
|
|
53562
53681
|
],
|
|
53563
53682
|
examples: [
|
|
53564
53683
|
"candle keys list",
|
|
53565
|
-
"candle keys create --access
|
|
53684
|
+
"candle keys create --access withdraw --label rebalancer",
|
|
53566
53685
|
"candle keys create --scopes trade:write --label agent-one",
|
|
53567
|
-
"candle keys access cndl --access
|
|
53686
|
+
"candle keys access cndl --access withdraw",
|
|
53568
53687
|
"candle keys access self --access read --yes",
|
|
53569
53688
|
"candle keys update agent-one --embedded-wallet deny --yes",
|
|
53570
53689
|
"candle keys self embedded-wallet deny",
|
|
@@ -53596,6 +53715,14 @@ var HELP = {
|
|
|
53596
53715
|
invocation: "untrust <label|address|id|prefix*>... [--yes]",
|
|
53597
53716
|
description: "Clear the mark; moving funds in needs the withdrawal allowlist again (owner only)"
|
|
53598
53717
|
},
|
|
53718
|
+
{
|
|
53719
|
+
invocation: "allow-launch <label|address|id|prefix*>...",
|
|
53720
|
+
description: "Let TEE wallets pay for a launch (candle launch), after a screen of label, address and bound key (owner only; typed confirm)"
|
|
53721
|
+
},
|
|
53722
|
+
{
|
|
53723
|
+
invocation: "disallow-launch <label|address|id|prefix*>... [--yes]",
|
|
53724
|
+
description: "Stop TEE wallets paying for a launch; trading is unchanged (owner only)"
|
|
53725
|
+
},
|
|
53599
53726
|
{
|
|
53600
53727
|
invocation: "close-empty [--wallet embedded] [--keep <mint>]... [--client-trade-id <id>] [--yes]",
|
|
53601
53728
|
description: "Close the embedded wallet's empty token accounts and return their rent to it, after a preview (transfer:write)"
|
|
@@ -53606,6 +53733,7 @@ var HELP = {
|
|
|
53606
53733
|
"candle wallet import --chain solana --key-file ./signer.json",
|
|
53607
53734
|
"candle wallet revoke wal_123",
|
|
53608
53735
|
"candle wallet trust 'tr-*' 'dest-*'",
|
|
53736
|
+
"candle wallet allow-launch launcher-1",
|
|
53609
53737
|
"candle wallet close-empty --keep EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
|
|
53610
53738
|
],
|
|
53611
53739
|
env: ENV_API
|
|
@@ -59348,10 +59476,14 @@ init_promote_support();
|
|
|
59348
59476
|
init_keys();
|
|
59349
59477
|
init_tee_rebind();
|
|
59350
59478
|
var KEYS_PATH4 = "/api/v1/agent/keys";
|
|
59351
|
-
var USAGE_ACCESS = `Usage: candle keys access <prefix|label|self> --access <read|
|
|
59479
|
+
var USAGE_ACCESS = `Usage: candle keys access <prefix|label|self> --access <read|write|withdraw> [--yes] [--json]
|
|
59352
59480
|
` + " candle keys access <prefix|label> --history [--json]";
|
|
59353
59481
|
var SELF = "self";
|
|
59354
|
-
var CLI_SPELLING =
|
|
59482
|
+
var CLI_SPELLING = {
|
|
59483
|
+
read: "read",
|
|
59484
|
+
readwrite: "write",
|
|
59485
|
+
readwritetransfer: "withdraw"
|
|
59486
|
+
};
|
|
59355
59487
|
var NO_API_KEY2 = {
|
|
59356
59488
|
code: "API_KEY_REQUIRED",
|
|
59357
59489
|
message: "candle keys access self changes the profile's own API key, and this profile holds none.",
|
|
@@ -59496,7 +59628,7 @@ async function keysAccess(args, ctx) {
|
|
|
59496
59628
|
}
|
|
59497
59629
|
const preset = ACCESS_LEVELS[accessFlag];
|
|
59498
59630
|
if (preset === undefined) {
|
|
59499
|
-
writeUsageFailure(deps,
|
|
59631
|
+
writeUsageFailure(deps, "--access must be one of: read, write, withdraw.", json);
|
|
59500
59632
|
return 2;
|
|
59501
59633
|
}
|
|
59502
59634
|
await printIdentity(ctx);
|
|
@@ -59851,7 +59983,7 @@ init_solana_lite();
|
|
|
59851
59983
|
init_trading();
|
|
59852
59984
|
init_swap();
|
|
59853
59985
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
59854
|
-
var USAGE2 = "Usage: candle launch --name <name> --symbol <symbol> --image-url <https-url> --wallet <tee> [--client-trade-id <id>] [--quote-asset sol|usdc|cndl|eth|usdg] [--dex-version v3|v4] [--mode <mode>] [--rpc-url <url>] [--yes]. A Hood TEE wallet launches on Hood and needs --dex-version.";
|
|
59986
|
+
var USAGE2 = "Usage: candle launch --name <name> --symbol <symbol> --image-url <https-url> --wallet <tee> [--buy <amount>] [--client-trade-id <id>] [--quote-asset sol|usdc|cndl|eth|usdg] [--dex-version v3|v4] [--mode <mode>] [--rpc-url <url>] [--yes]. A Hood TEE wallet launches on Hood and needs --dex-version.";
|
|
59855
59987
|
var QUOTE_CHAIN = {
|
|
59856
59988
|
sol: "solana",
|
|
59857
59989
|
usdc: "solana",
|
|
@@ -59860,6 +59992,34 @@ var QUOTE_CHAIN = {
|
|
|
59860
59992
|
usdg: "hood"
|
|
59861
59993
|
};
|
|
59862
59994
|
var HOOD_LAUNCH_LEGS = ["createCurve", "feeTransfer"];
|
|
59995
|
+
var HOOD_LAUNCH_BUY_LEGS = ["createCurve", "feeTransfer", "trade"];
|
|
59996
|
+
function launchBuy(flags, chain2) {
|
|
59997
|
+
const amount = flags["--buy"];
|
|
59998
|
+
if (amount === undefined)
|
|
59999
|
+
return;
|
|
60000
|
+
const quote = (flags["--quote-asset"] ?? (chain2 === "hood" ? "eth" : "sol")).toUpperCase();
|
|
60001
|
+
const decimals = chain2 === "hood" ? HOOD_BASES[quote]?.decimals : BASES[quote]?.decimals;
|
|
60002
|
+
if (decimals === undefined)
|
|
60003
|
+
throw new TradingError("INVALID_AMOUNT", `--buy is in the launch's quote asset, and ${safeText(quote)} is not one on ${chainName(chain2)}. Nothing was built.`);
|
|
60004
|
+
const raw = rawAmount(amount, decimals);
|
|
60005
|
+
return { raw, display: `${decimalAmount(raw, decimals)} ${quote}` };
|
|
60006
|
+
}
|
|
60007
|
+
function buyRefusal(error) {
|
|
60008
|
+
if (!(error instanceof TradingError) || error.code !== "VALIDATION_FAILED" || error.details?.field !== "buyAmount")
|
|
60009
|
+
return error;
|
|
60010
|
+
return new TradingError("VALIDATION_FAILED", `Candle refused the first buy on this TEE launch: ${safeText(error.message)}. A TEE wallet's launch takes --buy only where the server has TEE_LAUNCH_BUYS_ENABLED on, and it is off on this deployment. Nothing was built or signed.`, {
|
|
60011
|
+
suggestion: "Launch without --buy under a new --client-trade-id, then make the first buy with candle swap once the token lands.",
|
|
60012
|
+
details: error.details
|
|
60013
|
+
});
|
|
60014
|
+
}
|
|
60015
|
+
function buyNotice(buy, chain2) {
|
|
60016
|
+
if (!buy)
|
|
60017
|
+
return `Launch creates the token only. Make the first buy with a separate candle swap. Price impact does not apply to creation.
|
|
60018
|
+
`;
|
|
60019
|
+
return chain2 === "hood" ? `Launch creates the token, then spends ${buy.display} on a first buy on the new curve, as the last leg. Candle reserves the buy against this key's spend window before any leg is signed. If the buy leg never lands, the token exists with no first buy. Price impact does not apply to creation.
|
|
60020
|
+
` : `Launch creates the token and spends ${buy.display} on a first buy in the same transaction. Candle reserves the whole debit against this key's spend window before anything is signed. Price impact does not apply to creation.
|
|
60021
|
+
`;
|
|
60022
|
+
}
|
|
59863
60023
|
function flagChain(flags) {
|
|
59864
60024
|
const said = [];
|
|
59865
60025
|
const named = walletNameChain(flags["--wallet"] ?? "");
|
|
@@ -59889,7 +60049,8 @@ async function launch(args, ctx) {
|
|
|
59889
60049
|
"--rpc-url",
|
|
59890
60050
|
"--quote-asset",
|
|
59891
60051
|
"--mode",
|
|
59892
|
-
"--dex-version"
|
|
60052
|
+
"--dex-version",
|
|
60053
|
+
"--buy"
|
|
59893
60054
|
],
|
|
59894
60055
|
booleanFlags: ["--yes"]
|
|
59895
60056
|
});
|
|
@@ -59904,6 +60065,8 @@ async function launch(args, ctx) {
|
|
|
59904
60065
|
return 2;
|
|
59905
60066
|
}
|
|
59906
60067
|
try {
|
|
60068
|
+
if (flags["--buy"] !== undefined)
|
|
60069
|
+
rawAmount(flags["--buy"], 18);
|
|
59907
60070
|
const hinted = flagChain(flags);
|
|
59908
60071
|
if (hinted === "hood" && flags["--dex-version"] === undefined) {
|
|
59909
60072
|
writeUsageFailure(ctx.deps, `A Hood launch needs --dex-version v3|v4. ${USAGE2}`, ctx.json);
|
|
@@ -59929,8 +60092,10 @@ async function launch(args, ctx) {
|
|
|
59929
60092
|
if (wallet.chain === "evm") {
|
|
59930
60093
|
if (flags["--dex-version"] === undefined)
|
|
59931
60094
|
throw new TradingUsage(`${wallet.address} is a Hood TEE wallet, and a Hood launch needs --dex-version v3|v4.`);
|
|
59932
|
-
|
|
60095
|
+
const buy2 = launchBuy(flags, "hood");
|
|
60096
|
+
return await hoodLaunch(ctx, key, { flags, id, wallet, buy: buy2, yes: parsed.booleans.has("--yes") });
|
|
59933
60097
|
}
|
|
60098
|
+
const buy = launchBuy(flags, "solana");
|
|
59934
60099
|
const solana = await tradingSolanaClient(ctx, flags["--rpc-url"]);
|
|
59935
60100
|
if (!await claimOperation(ctx, key, id, "launch"))
|
|
59936
60101
|
throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this launch id; no write was resent.");
|
|
@@ -59939,7 +60104,7 @@ async function launch(args, ctx) {
|
|
|
59939
60104
|
const built = launchBuildSchema.parse(await request(ctx, key, "/api/v1/launch/self/build", {
|
|
59940
60105
|
clientLaunchId: id,
|
|
59941
60106
|
chain: "solana",
|
|
59942
|
-
buyAmount: 0,
|
|
60107
|
+
buyAmount: buy ? buy.raw : 0,
|
|
59943
60108
|
name: flags["--name"],
|
|
59944
60109
|
symbol: flags["--symbol"],
|
|
59945
60110
|
imageUrl: flags["--image-url"],
|
|
@@ -59947,21 +60112,22 @@ async function launch(args, ctx) {
|
|
|
59947
60112
|
...flags["--description"] ? { description: flags["--description"] } : {},
|
|
59948
60113
|
...flags["--quote-asset"] ? { quoteAsset: flags["--quote-asset"] } : {},
|
|
59949
60114
|
...flags["--mode"] ? { mode: flags["--mode"] } : {}
|
|
60115
|
+
}).catch((error) => {
|
|
60116
|
+
throw buyRefusal(error);
|
|
59950
60117
|
}));
|
|
59951
60118
|
if (!built.transaction || !/^\d+$/.test(built.maxDebitLamports ?? ""))
|
|
59952
60119
|
throw new TradingError("INVALID_RESPONSE", "Candle did not return a launch transaction and maximum debit; nothing was signed.");
|
|
59953
60120
|
const quote = {
|
|
59954
|
-
intent: `Launch ${flags["--name"]} (${flags["--symbol"]})`,
|
|
60121
|
+
intent: `Launch ${flags["--name"]} (${flags["--symbol"]})${buy ? ` with a first buy of ${buy.display}` : ""}`,
|
|
59955
60122
|
wallet: wallet.address,
|
|
59956
60123
|
venue: "curve launch",
|
|
59957
60124
|
priceImpactPct: null,
|
|
59958
60125
|
fee: built.fee ?? { bps: 0, feeRaw: "0" },
|
|
59959
|
-
minimumReceived: "0 tokens (no first buy)",
|
|
60126
|
+
minimumReceived: buy ? "not quoted (the first buy on the new curve)" : "0 tokens (no first buy)",
|
|
59960
60127
|
maxDebitLamports: built.maxDebitLamports,
|
|
59961
60128
|
tokenRisks: []
|
|
59962
60129
|
};
|
|
59963
|
-
ctx.deps.stderr.write(
|
|
59964
|
-
`);
|
|
60130
|
+
ctx.deps.stderr.write(buyNotice(buy, "solana"));
|
|
59965
60131
|
if (!await confirmQuote(ctx, quote, parsed.booleans.has("--yes")))
|
|
59966
60132
|
return printTradingResult(ctx, { success: true, status: "cancelled", clientTradeId: id, kind: "launch", quote });
|
|
59967
60133
|
if (!Number.isFinite(built.expiresAt) || built.expiresAt <= ctx.deps.now())
|
|
@@ -59987,7 +60153,8 @@ async function launch(args, ctx) {
|
|
|
59987
60153
|
}
|
|
59988
60154
|
}
|
|
59989
60155
|
async function hoodLaunch(ctx, key, args) {
|
|
59990
|
-
const { flags, id, wallet } = args;
|
|
60156
|
+
const { flags, id, wallet, buy } = args;
|
|
60157
|
+
const allowedLegs = buy ? HOOD_LAUNCH_BUY_LEGS : HOOD_LAUNCH_LEGS;
|
|
59991
60158
|
if (!await claimOperation(ctx, key, id, "launch"))
|
|
59992
60159
|
throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this launch id; no write was resent.");
|
|
59993
60160
|
ctx.deps.stderr.write(`Operation: ${id}
|
|
@@ -59995,7 +60162,7 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
59995
60162
|
const built = await request(ctx, key, "/api/v1/launch/self/build", {
|
|
59996
60163
|
clientLaunchId: id,
|
|
59997
60164
|
chain: "hood",
|
|
59998
|
-
buyAmount: 0,
|
|
60165
|
+
buyAmount: buy ? buy.raw : 0,
|
|
59999
60166
|
name: flags["--name"],
|
|
60000
60167
|
symbol: flags["--symbol"],
|
|
60001
60168
|
imageUrl: flags["--image-url"],
|
|
@@ -60004,6 +60171,8 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
60004
60171
|
...flags["--description"] ? { description: flags["--description"] } : {},
|
|
60005
60172
|
...flags["--quote-asset"] ? { quoteAsset: flags["--quote-asset"].toLowerCase() } : {},
|
|
60006
60173
|
...flags["--mode"] ? { mode: flags["--mode"] } : {}
|
|
60174
|
+
}).catch((error) => {
|
|
60175
|
+
throw buyRefusal(error);
|
|
60007
60176
|
});
|
|
60008
60177
|
const parsed = sequencedSchema.safeParse(built);
|
|
60009
60178
|
if (!parsed.success)
|
|
@@ -60012,26 +60181,25 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
60012
60181
|
const curve = typeof built.curveAddress === "string" ? checkEvmAddress(built.curveAddress) : undefined;
|
|
60013
60182
|
if (built.chain !== "hood" || built.clientLaunchId !== id || typeof built.walletAddress !== "string" || !sameEvmAddress(built.walletAddress, wallet.address) || !curve?.ok)
|
|
60014
60183
|
throw new TradingError("INVALID_RESPONSE", "The Hood launch build does not name this launch and payer; nothing was signed.");
|
|
60015
|
-
if (first.legKind !== "createCurve" || first.plannedLegCount >
|
|
60184
|
+
if (first.legKind !== "createCurve" || first.plannedLegCount > allowedLegs.length || buy !== undefined && first.plannedLegCount < 2)
|
|
60016
60185
|
throw new TradingError("INVALID_RESPONSE", `A Hood launch starts with its createCurve leg, and Candle offered ${safeText(first.legKind)} of ${first.plannedLegCount}; nothing was signed.`);
|
|
60017
60186
|
const leg = first.nextLeg;
|
|
60018
60187
|
const maxFee = BigInt(leg.maxFeePerGas);
|
|
60019
60188
|
const reserve = sweepReserveFloor(maxFee);
|
|
60020
60189
|
const quote = {
|
|
60021
|
-
intent: `Launch ${flags["--name"]} (${flags["--symbol"]}) on Hood`,
|
|
60190
|
+
intent: `Launch ${flags["--name"]} (${flags["--symbol"]}) on Hood${buy ? ` with a first buy of ${buy.display}` : ""}`,
|
|
60022
60191
|
wallet: wallet.address,
|
|
60023
60192
|
venue: "curve launch",
|
|
60024
60193
|
priceImpactPct: null,
|
|
60025
|
-
minimumReceived: "0 tokens (no first buy)",
|
|
60194
|
+
minimumReceived: buy ? "not quoted (Candle's buy leg sets its floor on the new curve)" : "0 tokens (no first buy)",
|
|
60026
60195
|
tokenRisks: [],
|
|
60027
|
-
legs: first.plannedLegCount
|
|
60028
|
-
gas: `the createCurve leg up to ${formatUnits(BigInt(leg.gas) * maxFee, 18)} ETH (gas ${leg.gas} at ${formatUnits(maxFee, 9)} gwei)${first.plannedLegCount
|
|
60196
|
+
legs: hoodLaunchLegNames(first.plannedLegCount, buy !== undefined),
|
|
60197
|
+
gas: `the createCurve leg up to ${formatUnits(BigInt(leg.gas) * maxFee, 18)} ETH (gas ${leg.gas} at ${formatUnits(maxFee, 9)} gwei)${hoodLaterLegsGas(first.plannedLegCount, buy !== undefined)}`,
|
|
60029
60198
|
reserve: `at least ${formatUnits(reserve.wei, 18)} ETH stays in the wallet for a sweep home (${reserve.erc20Transfers} ERC-20 transfers and the final ETH transfer at twice the fee)`,
|
|
60030
60199
|
curveAddress: curve.address,
|
|
60031
60200
|
operationId: first.operationId
|
|
60032
60201
|
};
|
|
60033
|
-
ctx.deps.stderr.write(
|
|
60034
|
-
`);
|
|
60202
|
+
ctx.deps.stderr.write(buyNotice(buy, "hood"));
|
|
60035
60203
|
if (!await confirmQuote(ctx, quote, args.yes)) {
|
|
60036
60204
|
ctx.deps.stderr.write(`Nothing was signed. Operation ${first.operationId} holds this wallet until ${new Date(first.expiresAt).toISOString()}; a new Hood operation from it is refused as WALLET_BUSY until then.
|
|
60037
60205
|
`);
|
|
@@ -60046,6 +60214,8 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
60046
60214
|
walletHeldUntil: first.expiresAt
|
|
60047
60215
|
});
|
|
60048
60216
|
}
|
|
60217
|
+
const confirmedPlan = buy ? first.plannedLegCount === 3 ? ["createCurve", "feeTransfer", "trade"] : ["createCurve", "trade"] : undefined;
|
|
60218
|
+
let position = 0;
|
|
60049
60219
|
const run2 = await runSequencedLegs(ctx, key, {
|
|
60050
60220
|
wallet,
|
|
60051
60221
|
first,
|
|
@@ -60055,10 +60225,16 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
60055
60225
|
clientId: id,
|
|
60056
60226
|
kind: "launch",
|
|
60057
60227
|
primaryLeg: "createCurve",
|
|
60058
|
-
allowedLegs
|
|
60059
|
-
checkLeg: (kind, leg2) =>
|
|
60228
|
+
allowedLegs,
|
|
60229
|
+
checkLeg: (kind, leg2) => {
|
|
60230
|
+
if (confirmedPlan && confirmedPlan[position++] !== kind)
|
|
60231
|
+
return false;
|
|
60232
|
+
return kind === "createCurve" ? leg2.value === "0" : kind !== "trade" || buy !== undefined && leg2.value === buy.raw && sameEvmAddress(leg2.to, curve.address);
|
|
60233
|
+
},
|
|
60060
60234
|
onLanded: async () => {}
|
|
60061
60235
|
});
|
|
60236
|
+
if (confirmedPlan && run2.landed.map((done) => done.kind).join() !== confirmedPlan.join())
|
|
60237
|
+
throw new TradingError("INVALID_RESPONSE", `Candle ended the launch before every confirmed leg landed (${confirmedPlan.join(", ")}).${run2.landed.length === 0 ? " No leg landed." : ` Landed: ${run2.landed.map((done) => `${done.kind} ${safeText(done.hash)}`).join(", ")}.`}`, { details: { operationId: first.operationId, landedLegs: run2.landed } });
|
|
60062
60238
|
return printTradingResult(ctx, {
|
|
60063
60239
|
...run2.final,
|
|
60064
60240
|
...await recordLaunchedToken(ctx, wallet, run2.final),
|
|
@@ -60071,6 +60247,16 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
60071
60247
|
landedLegs: run2.landed
|
|
60072
60248
|
});
|
|
60073
60249
|
}
|
|
60250
|
+
function hoodLaterLegsGas(count, buys) {
|
|
60251
|
+
if (count < 2)
|
|
60252
|
+
return "";
|
|
60253
|
+
return buys ? "; the later legs are priced by Candle when each becomes next" : "; the fee leg is priced by Candle when it becomes next";
|
|
60254
|
+
}
|
|
60255
|
+
function hoodLaunchLegNames(count, buys) {
|
|
60256
|
+
if (!buys)
|
|
60257
|
+
return count === 2 ? ["create curve", "fee"] : ["create curve"];
|
|
60258
|
+
return count === 3 ? ["create curve", "fee", "first buy"] : ["create curve", "first buy"];
|
|
60259
|
+
}
|
|
60074
60260
|
async function recordLaunchedToken(ctx, wallet, final) {
|
|
60075
60261
|
const mint = typeof final.mint === "string" ? checkEvmAddress(final.mint) : undefined;
|
|
60076
60262
|
if (!mint?.ok) {
|
|
@@ -61633,7 +61819,7 @@ async function perpsDeposit(args, ctx) {
|
|
|
61633
61819
|
init_args();
|
|
61634
61820
|
|
|
61635
61821
|
// src/plans.ts
|
|
61636
|
-
var PLAN_CAPABILITY_NOTE = "A capability marked yes is what the plan allows. It is subject to the deployment's own switches (perps and own-wallet bridges each have one) and to the wallet, scopes and setup the feature needs.";
|
|
61822
|
+
var PLAN_CAPABILITY_NOTE = "A capability marked yes is what the plan allows. It is subject to the deployment's own switches (perps and own-wallet bridges each have one) and to the wallet, scopes and setup the feature needs. Every plan can launch a token from its embedded wallet, with an optional dev buy (same transaction on Solana; best-effort follow-up on Hood), through the headless launch; the two launch rows are additional routes, not the only ones.";
|
|
61637
61823
|
var PLAN_CAPABILITY_LABELS = {
|
|
61638
61824
|
tradeCandleTokens: "Trade Candle-launched tokens",
|
|
61639
61825
|
tradeBaseAssets: "Trade base assets",
|
|
@@ -61641,8 +61827,8 @@ var PLAN_CAPABILITY_LABELS = {
|
|
|
61641
61827
|
sellExternalTokens: "Sell tokens not launched on Candle",
|
|
61642
61828
|
buyExternalTokens: "Buy tokens not launched on Candle",
|
|
61643
61829
|
hyperliquidPerps: "Hyperliquid perps (when enabled)",
|
|
61644
|
-
selfLaunch: "
|
|
61645
|
-
atomicLaunch: "Atomic launch
|
|
61830
|
+
selfLaunch: "Launch from a linked or TEE wallet (self-signed)",
|
|
61831
|
+
atomicLaunch: "Atomic launch: launch + 1–4 first buys in one bundle",
|
|
61646
61832
|
createLinkedWallets: "Create linked wallets",
|
|
61647
61833
|
importLinkedWallets: "Import linked wallets",
|
|
61648
61834
|
limitOrders: "Limit orders",
|
|
@@ -62263,7 +62449,7 @@ async function pnl(args, ctx) {
|
|
|
62263
62449
|
writeLocalFailure(deps, {
|
|
62264
62450
|
code: "SCOPE_MISSING",
|
|
62265
62451
|
message: "The account's P&L needs a key with the Read scope (account:read); this profile's key has none.",
|
|
62266
|
-
suggestion: "Log in with a Read or
|
|
62452
|
+
suggestion: "Log in with a Read or Write key, or read this key's own P&L: candle pnl --profile <name>"
|
|
62267
62453
|
}, json);
|
|
62268
62454
|
return 1;
|
|
62269
62455
|
}
|
|
@@ -64260,7 +64446,7 @@ async function transfer(args, ctx) {
|
|
|
64260
64446
|
if (payer.kind === "embedded")
|
|
64261
64447
|
throw new TradingError("PAYER_UNSUPPORTED", "This command moves a linked wallet this machine can sign for. The embedded wallet transfers through the agent transfer rail (MCP candle_transfer), not from here. Name a TEE wallet with --wallet.");
|
|
64262
64448
|
if (!payer.scopes.includes("transfer:bound"))
|
|
64263
|
-
throw new TradingError("SCOPE_MISSING", `Moving funds out of a TEE wallet needs a
|
|
64449
|
+
throw new TradingError("SCOPE_MISSING", `Moving funds out of a TEE wallet needs a Withdraw key. Widen the bound key with: candle keys access ${apiKeyPrefix(key) ?? "<bound prefix>"} --access withdraw. Or mint one with: candle keys create --access withdraw, then bind the wallet to it with: candle tee rebind`);
|
|
64264
64450
|
const wallet = payer.wallet;
|
|
64265
64451
|
const destination = await resolveDestination(ctx, key, wallet, to);
|
|
64266
64452
|
const label = asset ?? flags["--mint"];
|
|
@@ -64332,7 +64518,7 @@ async function hoodTransfer(ctx, args) {
|
|
|
64332
64518
|
if (payer.kind === "embedded")
|
|
64333
64519
|
throw new TradingError("PAYER_UNSUPPORTED", "This command moves a linked wallet this machine can sign for. The embedded wallet transfers through the agent transfer rail (MCP candle_transfer), not from here. Name a Hood TEE wallet with --wallet.");
|
|
64334
64520
|
if (!payer.scopes.includes("transfer:bound"))
|
|
64335
|
-
throw new TradingError("SCOPE_MISSING", `Moving funds out of a TEE wallet needs a
|
|
64521
|
+
throw new TradingError("SCOPE_MISSING", `Moving funds out of a TEE wallet needs a Withdraw key. Widen the bound key with: candle keys access ${apiKeyPrefix(key) ?? "<bound prefix>"} --access withdraw. Or mint one with: candle keys create --access withdraw, then bind the wallet to it with: candle tee rebind`);
|
|
64336
64522
|
const wallet = payer.wallet;
|
|
64337
64523
|
const destination = await resolveDestination(ctx, key, wallet, to, "hood");
|
|
64338
64524
|
const decimals = asset ? HOOD_BASES[asset]?.decimals ?? 18 : await hoodDecimals({ chain: "hood", asset: tokenAddress }, lazyEvmRpc(ctx, flags["--rpc-url"]));
|
|
@@ -65696,10 +65882,10 @@ function recordCopyLine(outcome) {
|
|
|
65696
65882
|
// src/commands/vault-demote.ts
|
|
65697
65883
|
init_args();
|
|
65698
65884
|
init_render();
|
|
65885
|
+
init_solana_endpoint();
|
|
65699
65886
|
init_errors();
|
|
65700
65887
|
init_promote_support();
|
|
65701
65888
|
init_reconcile_grant();
|
|
65702
|
-
init_store();
|
|
65703
65889
|
init_tee_resolve();
|
|
65704
65890
|
init_tee();
|
|
65705
65891
|
init_tee_evm();
|
|
@@ -65718,7 +65904,6 @@ async function vaultDemote(args, ctx) {
|
|
|
65718
65904
|
if (!address || extra !== undefined) {
|
|
65719
65905
|
return usage(ctx, "Usage: candle vault demote <tee-address> [--rpc-url <url>] [--emergency] [--sweep-to <label>]");
|
|
65720
65906
|
}
|
|
65721
|
-
const rpcUrl = parsed.values["--rpc-url"];
|
|
65722
65907
|
if (!refuseEnvPassphrase(ctx))
|
|
65723
65908
|
return 1;
|
|
65724
65909
|
if (!requireTty(ctx, "vault demote"))
|
|
@@ -65729,6 +65914,9 @@ async function vaultDemote(args, ctx) {
|
|
|
65729
65914
|
const path = resolvedVault.path;
|
|
65730
65915
|
const emergency = parsed.booleans.has("--emergency");
|
|
65731
65916
|
const sweepTo = parsed.values["--sweep-to"];
|
|
65917
|
+
const solana = await openSolanaClient(ctx, parsed.values["--rpc-url"]);
|
|
65918
|
+
if ("error" in solana)
|
|
65919
|
+
return usage(ctx, solana.error);
|
|
65732
65920
|
const resolved = await resolveTeeAddress(ctx, parsed, address, async () => ({
|
|
65733
65921
|
ok: false,
|
|
65734
65922
|
code: 1
|
|
@@ -65751,49 +65939,33 @@ async function vaultDemote(args, ctx) {
|
|
|
65751
65939
|
}, ctx.json);
|
|
65752
65940
|
return 1;
|
|
65753
65941
|
}
|
|
65754
|
-
|
|
65942
|
+
const vaultResolved = resolved.resolved;
|
|
65755
65943
|
try {
|
|
65756
|
-
const vaultResolved = resolved.resolved;
|
|
65757
65944
|
const reconciled = await maybeReconcileVaultTee(ctx, vaultResolved, "demote");
|
|
65758
65945
|
if (reconciled.code !== null)
|
|
65759
65946
|
return reconciled.code;
|
|
65760
65947
|
const entry = reconciled.entry;
|
|
65948
|
+
const step = { resolved: vaultResolved, solana };
|
|
65761
65949
|
if (entry.tee?.vaultDestination === undefined) {
|
|
65762
65950
|
if (sweepTo === undefined) {
|
|
65763
65951
|
throw new VaultError("GRANT_DESTINATION_UNRESOLVED", `${address} has no pinned vault destination.`, {
|
|
65764
65952
|
suggestion: "Pass --sweep-to <vault-key-label> (subject to the recovery destination rule), or adopt the server's pin."
|
|
65765
65953
|
});
|
|
65766
65954
|
}
|
|
65767
|
-
|
|
65768
|
-
|
|
65769
|
-
|
|
65770
|
-
|
|
65771
|
-
|
|
65772
|
-
|
|
65773
|
-
}
|
|
65774
|
-
const vault = hold(opened.vault);
|
|
65775
|
-
const destination = assertColdVaultDestination(vault.index, sweepTo, {});
|
|
65776
|
-
await commitVault(vault, {
|
|
65777
|
-
index: {
|
|
65778
|
-
hd: vault.index.hd,
|
|
65779
|
-
entries: vault.index.entries.map((e) => e.address === address ? {
|
|
65780
|
-
...e,
|
|
65781
|
-
tee: {
|
|
65782
|
-
...e.tee ?? { network: "solana-mainnet", lifecycle: "local-candidate" },
|
|
65783
|
-
vaultDestination: destination.address
|
|
65784
|
-
}
|
|
65785
|
-
} : e)
|
|
65786
|
-
}
|
|
65787
|
-
}, ctx.deps);
|
|
65788
|
-
return demoteWithAdapter(ctx, { linkedWalletId: entry.linkedWalletId }, address, rpcUrl, true);
|
|
65955
|
+
await assertNotOlderCopy(ctx, path, vaultResolved.vault.raw, parsed.booleans.has("--accept-older-copy"));
|
|
65956
|
+
const destination = assertColdVaultDestination(vaultResolved.vault.index, sweepTo, {});
|
|
65957
|
+
const next = await commitVaultTeeEntry(ctx, vaultResolved.vault, entry.id, (target) => {
|
|
65958
|
+
target.tee = {
|
|
65959
|
+
...target.tee ?? { network: "solana-mainnet", lifecycle: "local-candidate" },
|
|
65960
|
+
vaultDestination: destination.address
|
|
65961
|
+
};
|
|
65789
65962
|
});
|
|
65963
|
+
applyVault(vaultResolved, next);
|
|
65964
|
+
return await demoteWithAdapter(ctx, step, address, parsed.values["--rpc-url"], true);
|
|
65790
65965
|
}
|
|
65791
65966
|
requireTeeDestination(entry);
|
|
65792
65967
|
const needsEmergency = emergency || entry.linkedWalletId === undefined || entry.tee?.lifecycle === "stranded";
|
|
65793
|
-
|
|
65794
|
-
releaseResolvedTee(resolved.resolved);
|
|
65795
|
-
released = true;
|
|
65796
|
-
return demoteWithAdapter(ctx, snapshot, address, rpcUrl, needsEmergency);
|
|
65968
|
+
return await demoteWithAdapter(ctx, step, address, parsed.values["--rpc-url"], needsEmergency);
|
|
65797
65969
|
} catch (error) {
|
|
65798
65970
|
if (error instanceof VaultError) {
|
|
65799
65971
|
writeLocalFailure(ctx.deps, {
|
|
@@ -65805,23 +65977,39 @@ async function vaultDemote(args, ctx) {
|
|
|
65805
65977
|
}
|
|
65806
65978
|
throw error;
|
|
65807
65979
|
} finally {
|
|
65808
|
-
|
|
65809
|
-
releaseResolvedTee(resolved.resolved);
|
|
65980
|
+
releaseResolvedTee(vaultResolved);
|
|
65810
65981
|
}
|
|
65811
65982
|
}
|
|
65812
|
-
async function demoteWithAdapter(ctx,
|
|
65813
|
-
|
|
65814
|
-
|
|
65983
|
+
async function demoteWithAdapter(ctx, step, address, rpcUrl, emergency) {
|
|
65984
|
+
const linkedWalletId = step.resolved.entry.linkedWalletId;
|
|
65985
|
+
let disableCode = null;
|
|
65986
|
+
if (linkedWalletId !== undefined) {
|
|
65987
|
+
disableCode = await teeDisable([address], ctx, step);
|
|
65815
65988
|
if (disableCode !== 0 && disableCode !== 3 && !emergency)
|
|
65816
65989
|
return disableCode;
|
|
65817
65990
|
} else {
|
|
65818
65991
|
ctx.deps.stdout.write(`No linkedWalletId is recorded for ${address}; the grant could not be identified, so disable was not called and remote authority stays unknown.
|
|
65819
65992
|
`);
|
|
65820
65993
|
}
|
|
65994
|
+
const destination = step.resolved.entry.tee?.vaultDestination;
|
|
65995
|
+
if (await nothingLeftToSweep(step.solana, step.resolved.entry)) {
|
|
65996
|
+
const code = disableCode === 0 ? 0 : 3;
|
|
65997
|
+
if (ctx.json) {
|
|
65998
|
+
ctx.deps.stdout.write(`${JSON.stringify({ address, vaultDestination: destination, swept: false, nothingToSweep: true, lamports: "0", tokenAccounts: 0 })}
|
|
65999
|
+
`);
|
|
66000
|
+
} else {
|
|
66001
|
+
ctx.deps.stdout.write(`Nothing to sweep: 0 SOL, 0 token accounts.
|
|
66002
|
+
`);
|
|
66003
|
+
}
|
|
66004
|
+
return code;
|
|
66005
|
+
}
|
|
66006
|
+
if (!ctx.json)
|
|
66007
|
+
ctx.deps.stdout.write(`Sweeping remaining funds to ${destination}...
|
|
66008
|
+
`);
|
|
65821
66009
|
const sweepArgs = rpcUrl === undefined ? [address] : [address, "--rpc-url", rpcUrl];
|
|
65822
|
-
if (emergency ||
|
|
66010
|
+
if (emergency || linkedWalletId === undefined)
|
|
65823
66011
|
sweepArgs.push("--emergency");
|
|
65824
|
-
return teeSweep(sweepArgs, ctx);
|
|
66012
|
+
return teeSweep(sweepArgs, ctx, step);
|
|
65825
66013
|
}
|
|
65826
66014
|
|
|
65827
66015
|
// src/commands/vault-export-key.ts
|
|
@@ -72327,6 +72515,176 @@ function messageOf3(error) {
|
|
|
72327
72515
|
// src/index.ts
|
|
72328
72516
|
init_wallets();
|
|
72329
72517
|
|
|
72518
|
+
// src/commands/wallets-allow-launch.ts
|
|
72519
|
+
init_args();
|
|
72520
|
+
init_deps();
|
|
72521
|
+
init_profiles();
|
|
72522
|
+
init_render();
|
|
72523
|
+
init_promote_support();
|
|
72524
|
+
var PREVIEW_PATH = "/api/v1/agent/tee-wallets/allow-launch/preview";
|
|
72525
|
+
var capabilityPath = (id) => `/api/v1/agent/wallets/${encodeURIComponent(id)}/capabilities`;
|
|
72526
|
+
var USAGE_ALLOW = "Usage: candle wallets allow-launch <label|address|id|prefix*>... [--json]";
|
|
72527
|
+
var USAGE_DISALLOW = "Usage: candle wallets disallow-launch <label|address|id|prefix*>... [--yes] [--json]";
|
|
72528
|
+
var DEVICE_TOKEN_REQUIRED2 = {
|
|
72529
|
+
code: "DEVICE_TOKEN_REQUIRED",
|
|
72530
|
+
message: "Changing whether a TEE wallet may launch needs the device token, the owner's credential; an API key cannot do it.",
|
|
72531
|
+
suggestion: "Run: candle auth login"
|
|
72532
|
+
};
|
|
72533
|
+
var nameOf = (row) => row.label ?? row.address;
|
|
72534
|
+
function allowLaunchTable(rows) {
|
|
72535
|
+
return renderTable(["line", "label", "wallet", "address", "bound key"], rows.map((row, i) => [String(i + 1), row.label ?? "-", row.chain, row.address, row.boundKeyPrefix ?? "-"]));
|
|
72536
|
+
}
|
|
72537
|
+
function nothingToChangeLine(shown, enabled) {
|
|
72538
|
+
const bits = [];
|
|
72539
|
+
if (shown.unchanged.length === 1)
|
|
72540
|
+
bits.push(`that wallet already has allowLaunch ${enabled ? "on" : "off"}`);
|
|
72541
|
+
else if (shown.unchanged.length > 1) {
|
|
72542
|
+
bits.push(`all ${shown.unchanged.length} wallets already have allowLaunch ${enabled ? "on" : "off"}`);
|
|
72543
|
+
}
|
|
72544
|
+
if (shown.notTee.length > 0) {
|
|
72545
|
+
bits.push(`${shown.notTee.map(nameOf).join(", ")} ${shown.notTee.length === 1 ? "is" : "are"} not a TEE wallet`);
|
|
72546
|
+
}
|
|
72547
|
+
if (bits.length === 0)
|
|
72548
|
+
return `Nothing to change.
|
|
72549
|
+
`;
|
|
72550
|
+
return `Nothing to change: ${bits.join("; ")}.
|
|
72551
|
+
`;
|
|
72552
|
+
}
|
|
72553
|
+
function writeFailure2(ctx, result) {
|
|
72554
|
+
const { deps, apiUrl, json } = ctx;
|
|
72555
|
+
let code = result.code;
|
|
72556
|
+
let message = result.message;
|
|
72557
|
+
let suggestion;
|
|
72558
|
+
if (result.status === 404) {
|
|
72559
|
+
code = "ALLOW_LAUNCH_UNSUPPORTED";
|
|
72560
|
+
message = "This Candle API does not offer allowLaunch on TEE wallets (an older API, or TEE launch is off); nothing changed.";
|
|
72561
|
+
} else {
|
|
72562
|
+
const error = result.raw?.error;
|
|
72563
|
+
if (error?.reason === "ambiguous" && Array.isArray(error.matches)) {
|
|
72564
|
+
const ids = error.matches.map((m) => String(m.id)).join(", ");
|
|
72565
|
+
suggestion = `Name one of them by id or address: ${ids}`;
|
|
72566
|
+
} else if (error?.reason === "not_found") {
|
|
72567
|
+
suggestion = "Run: candle wallets, to see this account's linked wallets and their labels.";
|
|
72568
|
+
}
|
|
72569
|
+
}
|
|
72570
|
+
const envelope = errorEnvelope({ ...result, code, message }, { apiUrl, authType: "device" });
|
|
72571
|
+
if (json) {
|
|
72572
|
+
deps.stdout.write(`${JSON.stringify({ ...envelope, ...suggestion ? { suggestion } : {} })}
|
|
72573
|
+
`);
|
|
72574
|
+
} else {
|
|
72575
|
+
deps.stderr.write(`${code ?? `HTTP ${result.status}`}: ${message}${suggestion ? ` ${suggestion}` : ""}
|
|
72576
|
+
`);
|
|
72577
|
+
}
|
|
72578
|
+
return 1;
|
|
72579
|
+
}
|
|
72580
|
+
async function setAllowLaunch(args, ctx, enabled) {
|
|
72581
|
+
const { deps, apiUrl, json } = ctx;
|
|
72582
|
+
const usage6 = enabled ? USAGE_ALLOW : USAGE_DISALLOW;
|
|
72583
|
+
const command = enabled ? "wallets allow-launch" : "wallets disallow-launch";
|
|
72584
|
+
const parsed = parseArgs(args, enabled ? {} : { booleanFlags: ["--yes"] });
|
|
72585
|
+
if ("error" in parsed) {
|
|
72586
|
+
writeUsageFailure(deps, parsed.error, json);
|
|
72587
|
+
return 2;
|
|
72588
|
+
}
|
|
72589
|
+
if (parsed.positionals.length === 0) {
|
|
72590
|
+
writeUsageFailure(deps, `Name at least one wallet. ${usage6}`, json);
|
|
72591
|
+
return 2;
|
|
72592
|
+
}
|
|
72593
|
+
const skipConfirm = !enabled && parsed.booleans.has("--yes");
|
|
72594
|
+
await printIdentity(ctx);
|
|
72595
|
+
const deviceToken = await resolveDeviceToken(deps, ctx.profile);
|
|
72596
|
+
if (!deviceToken) {
|
|
72597
|
+
writeLocalFailure(deps, DEVICE_TOKEN_REQUIRED2, json);
|
|
72598
|
+
return 1;
|
|
72599
|
+
}
|
|
72600
|
+
if (!skipConfirm && (!deps.isTTY.stdin || !deps.isTTY.stdout)) {
|
|
72601
|
+
writeLocalFailure(deps, {
|
|
72602
|
+
code: "ALLOW_LAUNCH_REQUIRES_TTY",
|
|
72603
|
+
message: `candle ${command} needs a terminal: the acknowledgement is typed, and nothing else supplies it.`,
|
|
72604
|
+
suggestion: enabled ? "Run it in an interactive shell; there is no flag and no environment variable for confirm." : "Run it in an interactive shell, or pass --yes to turn it off without the prompt."
|
|
72605
|
+
}, json);
|
|
72606
|
+
return 1;
|
|
72607
|
+
}
|
|
72608
|
+
const request2 = (path, method, body) => apiRequest(path, {
|
|
72609
|
+
method,
|
|
72610
|
+
body,
|
|
72611
|
+
auth: "device",
|
|
72612
|
+
credentials: { deviceToken },
|
|
72613
|
+
apiUrl,
|
|
72614
|
+
fetch: deps.fetch,
|
|
72615
|
+
env: deps.env
|
|
72616
|
+
});
|
|
72617
|
+
const preview = await request2(PREVIEW_PATH, "POST", { wallets: parsed.positionals, enabled });
|
|
72618
|
+
if (!preview.ok)
|
|
72619
|
+
return writeFailure2(ctx, preview);
|
|
72620
|
+
const shown = preview.body;
|
|
72621
|
+
const changing = shown.changed;
|
|
72622
|
+
if (changing.length === 0) {
|
|
72623
|
+
if (json)
|
|
72624
|
+
deps.stdout.write(`${JSON.stringify({ ...shown, command })}
|
|
72625
|
+
`);
|
|
72626
|
+
else
|
|
72627
|
+
deps.stdout.write(nothingToChangeLine(shown, enabled));
|
|
72628
|
+
return shown.unchanged.length === 0 && shown.notTee.length > 0 ? 1 : 0;
|
|
72629
|
+
}
|
|
72630
|
+
const n = changing.length;
|
|
72631
|
+
const screen = [
|
|
72632
|
+
allowLaunchTable(changing),
|
|
72633
|
+
...shown.unchanged.length > 0 ? [`${shown.unchanged.length} already ${enabled ? "on" : "off"}: ${shown.unchanged.map(nameOf).join(", ")}`] : [],
|
|
72634
|
+
...shown.notTee.length > 0 ? [`Not a TEE wallet, left alone: ${shown.notTee.map(nameOf).join(", ")}`] : [],
|
|
72635
|
+
"",
|
|
72636
|
+
enabled ? `${n === 1 ? "This wallet" : `These ${n} wallets`} will be allowed to pay for a launch: the bound key, with launch:write, can create tokens from ${n === 1 ? "it" : "them"} (candle launch).` : `${n === 1 ? "This wallet" : `These ${n} wallets`} will no longer be able to pay for a launch. Trading is unchanged.`,
|
|
72637
|
+
""
|
|
72638
|
+
];
|
|
72639
|
+
deps.stderr.write(`${screen.join(`
|
|
72640
|
+
`)}
|
|
72641
|
+
`);
|
|
72642
|
+
if (!skipConfirm) {
|
|
72643
|
+
const typed = await deps.promptLine(`Type ${CONFIRM_WORD} to ${enabled ? "allow" : "stop"} launches from ${n === 1 ? "this wallet" : `these ${n} wallets`}: `);
|
|
72644
|
+
if (typed.trim().toLowerCase() !== CONFIRM_WORD) {
|
|
72645
|
+
writeLocalFailure(deps, {
|
|
72646
|
+
code: "ALLOW_LAUNCH_NOT_ACKNOWLEDGED",
|
|
72647
|
+
message: `The acknowledgement is the word ${CONFIRM_WORD}; nothing changed.`,
|
|
72648
|
+
suggestion: `Run the command again and type ${CONFIRM_WORD} at the prompt.`
|
|
72649
|
+
}, json);
|
|
72650
|
+
return 1;
|
|
72651
|
+
}
|
|
72652
|
+
}
|
|
72653
|
+
const done = [];
|
|
72654
|
+
const failed = [];
|
|
72655
|
+
for (const row of changing) {
|
|
72656
|
+
const result = await request2(capabilityPath(row.id), "PUT", { capability: "allowLaunch", enabled });
|
|
72657
|
+
if (result.ok)
|
|
72658
|
+
done.push({ ...row, allowLaunch: enabled });
|
|
72659
|
+
else {
|
|
72660
|
+
failed.push({
|
|
72661
|
+
id: row.id,
|
|
72662
|
+
code: result.status === 404 ? "WALLET_NOT_FOUND" : result.code ?? null,
|
|
72663
|
+
message: result.status === 404 ? "no longer an active TEE wallet on this account" : result.message
|
|
72664
|
+
});
|
|
72665
|
+
}
|
|
72666
|
+
}
|
|
72667
|
+
if (json) {
|
|
72668
|
+
deps.stdout.write(`${JSON.stringify({ success: failed.length === 0, command, enabled, changed: done, unchanged: shown.unchanged, notTee: shown.notTee, failed })}
|
|
72669
|
+
`);
|
|
72670
|
+
return failed.length > 0 ? 1 : 0;
|
|
72671
|
+
}
|
|
72672
|
+
const verb = enabled ? "Allowed launches from" : "Stopped launches from";
|
|
72673
|
+
deps.stdout.write(`${verb} ${done.length} wallet${done.length === 1 ? "" : "s"}.
|
|
72674
|
+
`);
|
|
72675
|
+
for (const failure of failed) {
|
|
72676
|
+
deps.stderr.write(`Not changed: ${failure.id}: ${failure.message}${failure.code ? ` (${failure.code})` : ""}.
|
|
72677
|
+
`);
|
|
72678
|
+
}
|
|
72679
|
+
return failed.length > 0 ? 1 : 0;
|
|
72680
|
+
}
|
|
72681
|
+
function walletsAllowLaunch(args, ctx) {
|
|
72682
|
+
return setAllowLaunch(args, ctx, true);
|
|
72683
|
+
}
|
|
72684
|
+
function walletsDisallowLaunch(args, ctx) {
|
|
72685
|
+
return setAllowLaunch(args, ctx, false);
|
|
72686
|
+
}
|
|
72687
|
+
|
|
72330
72688
|
// src/commands/wallets-close-empty.ts
|
|
72331
72689
|
init_args();
|
|
72332
72690
|
init_render();
|
|
@@ -72484,7 +72842,7 @@ init_promote_support();
|
|
|
72484
72842
|
var TRUST_PATH = "/api/v1/agent/linked-wallets/trust";
|
|
72485
72843
|
var USAGE_TRUST = "Usage: candle wallets trust <label|address|id|prefix*>... [--json]";
|
|
72486
72844
|
var USAGE_UNTRUST = "Usage: candle wallets untrust <label|address|id|prefix*>... [--yes] [--json]";
|
|
72487
|
-
var
|
|
72845
|
+
var DEVICE_TOKEN_REQUIRED3 = {
|
|
72488
72846
|
code: "DEVICE_TOKEN_REQUIRED",
|
|
72489
72847
|
message: "Marking a wallet trusted needs the device token, the owner's credential; an API key cannot do it.",
|
|
72490
72848
|
suggestion: "Run: candle auth login"
|
|
@@ -72495,7 +72853,7 @@ function sessionLinkedLine(rows) {
|
|
|
72495
72853
|
const names = rows.map((row) => row.label ?? row.address).join(", ");
|
|
72496
72854
|
return `${n} linked while signed in (${names}): always yours while linked. Revoke ${it} to remove ${it}.`;
|
|
72497
72855
|
}
|
|
72498
|
-
function
|
|
72856
|
+
function nothingToChangeLine2(shown, trusted) {
|
|
72499
72857
|
const session = shown.sessionLinked ?? [];
|
|
72500
72858
|
const unchanged = shown.unchanged;
|
|
72501
72859
|
if (session.length > 0 && unchanged.length === 0) {
|
|
@@ -72562,7 +72920,7 @@ async function setTrust(args, ctx, trusted) {
|
|
|
72562
72920
|
await printIdentity(ctx);
|
|
72563
72921
|
const deviceToken = await resolveDeviceToken(deps, ctx.profile);
|
|
72564
72922
|
if (!deviceToken) {
|
|
72565
|
-
writeLocalFailure(deps,
|
|
72923
|
+
writeLocalFailure(deps, DEVICE_TOKEN_REQUIRED3, json);
|
|
72566
72924
|
return 1;
|
|
72567
72925
|
}
|
|
72568
72926
|
if (!skipConfirm && (!deps.isTTY.stdin || !deps.isTTY.stdout)) {
|
|
@@ -72592,7 +72950,7 @@ async function setTrust(args, ctx, trusted) {
|
|
|
72592
72950
|
deps.stdout.write(`${JSON.stringify({ ...shown, command })}
|
|
72593
72951
|
`);
|
|
72594
72952
|
} else {
|
|
72595
|
-
deps.stdout.write(
|
|
72953
|
+
deps.stdout.write(nothingToChangeLine2(shown, trusted));
|
|
72596
72954
|
}
|
|
72597
72955
|
return 0;
|
|
72598
72956
|
}
|
|
@@ -72859,6 +73217,8 @@ var COMMANDS = {
|
|
|
72859
73217
|
revoke: walletsRevoke,
|
|
72860
73218
|
trust: walletsTrust,
|
|
72861
73219
|
untrust: walletsUntrust,
|
|
73220
|
+
"allow-launch": walletsAllowLaunch,
|
|
73221
|
+
"disallow-launch": walletsDisallowLaunch,
|
|
72862
73222
|
"close-empty": walletsCloseEmpty
|
|
72863
73223
|
},
|
|
72864
73224
|
bare: wallets
|