@candledottv/cli 0.11.28 → 0.11.29

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.
Files changed (3) hide show
  1. package/README.md +3 -2
  2. package/dist/index.js +1312 -127
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -92,6 +92,21 @@ function insecureApiUrlFault(url, env = process.env) {
92
92
  return;
93
93
  return `Refusing to send credentials in the clear to ${parsed.origin}. Use https://` + (isPrivateHost(parsed.hostname) ? `, or set ${ALLOW_INSECURE_HTTP_ENV}=1 if this really is a trusted local endpoint.` : `. ${ALLOW_INSECURE_HTTP_ENV} does not apply here: it covers private networks only, and this is a public address.`);
94
94
  }
95
+ function browserUrl(url) {
96
+ let parsed;
97
+ try {
98
+ parsed = new URL(url);
99
+ } catch {
100
+ return null;
101
+ }
102
+ if (parsed.username || parsed.password)
103
+ return null;
104
+ if (parsed.protocol === "https:")
105
+ return parsed.href;
106
+ if (parsed.protocol === "http:" && isLoopbackHost(parsed.hostname))
107
+ return parsed.href;
108
+ return null;
109
+ }
95
110
  function resolveApiUrl(configuredApiUrl, env = process.env) {
96
111
  const fromEnv = env.CANDLE_API_URL?.trim();
97
112
  const resolved = fromEnv || configuredApiUrl?.trim() || DEFAULT_API_URL;
@@ -109,6 +124,27 @@ function buildHeaders(opts) {
109
124
  }
110
125
  return headers;
111
126
  }
127
+ async function fetchSameOriginRedirects(doFetch, url, init) {
128
+ let current = url;
129
+ let next = { ...init, redirect: "manual" };
130
+ for (let hops = 0;; hops++) {
131
+ const response = await doFetch(current, next);
132
+ const location = REDIRECT_STATUSES.has(response.status) ? response.headers?.get?.("location") : null;
133
+ if (!location)
134
+ return response;
135
+ const from = new URL(current);
136
+ const target = new URL(location, from);
137
+ if (target.origin !== from.origin)
138
+ throw new CrossOriginRedirectError(from.origin, target.origin);
139
+ if (hops >= MAX_REDIRECTS)
140
+ throw new Error(`More than ${MAX_REDIRECTS} redirects from ${url}`);
141
+ const method = (next.method ?? "GET").toUpperCase();
142
+ if (response.status === 303 && method !== "GET" && method !== "HEAD" || (response.status === 301 || response.status === 302) && method === "POST") {
143
+ next = { ...next, method: "GET", body: undefined };
144
+ }
145
+ current = target.href;
146
+ }
147
+ }
112
148
  function buildUrl(apiUrl, path) {
113
149
  const base = trimTrailingSlashes(apiUrl);
114
150
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
@@ -161,14 +197,15 @@ async function apiRequest(path, opts) {
161
197
  const headers = buildHeaders(opts);
162
198
  const body = opts.raw ? opts.raw.bytes : opts.body === undefined ? undefined : JSON.stringify(opts.body);
163
199
  const doFetch = opts.fetch ?? fetch;
200
+ const init = { method: opts.method ?? "GET", headers, body };
201
+ const credentialed = "x-api-key" in headers || "authorization" in headers;
164
202
  let response;
165
203
  try {
166
- response = await doFetch(url, {
167
- method: opts.method ?? "GET",
168
- headers,
169
- body
170
- });
204
+ response = credentialed ? await fetchSameOriginRedirects(doFetch, url, init) : await doFetch(url, init);
171
205
  } catch (err) {
206
+ if (err instanceof CrossOriginRedirectError) {
207
+ return { ok: false, status: 0, code: "CROSS_ORIGIN_REDIRECT", message: err.message, raw: undefined };
208
+ }
172
209
  const reason = err instanceof Error ? err.message : String(err);
173
210
  const env = opts.env ?? process.env;
174
211
  const envOverride = env.CANDLE_API_URL?.trim();
@@ -190,7 +227,20 @@ async function apiRequest(path, opts) {
190
227
  const classified = classifyError(response.status, raw);
191
228
  return { ok: false, status: response.status, raw, ...classified };
192
229
  }
193
- var DEFAULT_API_URL = "https://api.alpha.candle.tv", ALLOW_INSECURE_HTTP_ENV = "CANDLE_ALLOW_INSECURE_HTTP", latestCliVersionSeen = null;
230
+ var DEFAULT_API_URL = "https://api.alpha.candle.tv", ALLOW_INSECURE_HTTP_ENV = "CANDLE_ALLOW_INSECURE_HTTP", REDIRECT_STATUSES, MAX_REDIRECTS = 5, CrossOriginRedirectError, latestCliVersionSeen = null;
231
+ var init_client = __esm(() => {
232
+ REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
233
+ CrossOriginRedirectError = class CrossOriginRedirectError extends Error {
234
+ from;
235
+ to;
236
+ constructor(from, to) {
237
+ super(`The API at ${from} redirected to ${to}, another origin. Not followed, so the credential was not sent there.`);
238
+ this.from = from;
239
+ this.to = to;
240
+ this.name = "CrossOriginRedirectError";
241
+ }
242
+ };
243
+ });
194
244
 
195
245
  // src/args.ts
196
246
  function refuseUnexpandedTilde(name, value) {
@@ -9099,7 +9149,12 @@ function allSignerWallets(view) {
9099
9149
  function activeSigner(view) {
9100
9150
  if (view.state !== "active" || !view.signerQuorumId || !view.fingerprint || !view.spkiSha256)
9101
9151
  return null;
9102
- return { fingerprint: view.fingerprint, spkiSha256: view.spkiSha256, signerQuorumId: view.signerQuorumId };
9152
+ return {
9153
+ fingerprint: view.fingerprint,
9154
+ spkiSha256: view.spkiSha256,
9155
+ signerQuorumId: view.signerQuorumId,
9156
+ publicKeyDer: typeof view.publicKeyDer === "string" ? view.publicKeyDer : null
9157
+ };
9103
9158
  }
9104
9159
  async function readSigner(ctx, keyPrefix, auth) {
9105
9160
  return apiRequest(`/api/v1/agent/keys/${encodeURIComponent(keyPrefix)}/signer`, {
@@ -9145,9 +9200,48 @@ function errorDetails(result) {
9145
9200
  const error = result.raw?.error;
9146
9201
  return error && typeof error === "object" ? error : {};
9147
9202
  }
9148
- async function confirmSignerPin(ctx, keyPrefix, active, opts = {}) {
9203
+ function verifiedSigner(signer) {
9204
+ let spkiSha256 = signer.spkiSha256;
9205
+ if (signer.publicKeyDer !== undefined) {
9206
+ if (typeof signer.publicKeyDer !== "string" || signer.publicKeyDer.length === 0) {
9207
+ return { ok: false, problem: "the server did not send the signer's public key" };
9208
+ }
9209
+ try {
9210
+ const key = createPublicKey({ key: Buffer.from(signer.publicKeyDer, "base64"), format: "der", type: "spki" });
9211
+ if (key.asymmetricKeyType !== "ec" || key.asymmetricKeyDetails?.namedCurve !== "prime256v1") {
9212
+ return { ok: false, problem: "the server's public key is not a P-256 key" };
9213
+ }
9214
+ } catch {
9215
+ return { ok: false, problem: "the server's public key is not a valid SPKI DER key" };
9216
+ }
9217
+ spkiSha256 = spkiSha256Of(signer.publicKeyDer);
9218
+ if (spkiSha256 !== signer.spkiSha256) {
9219
+ return { ok: false, problem: "the hash the server reports is not the hash of the public key it sent" };
9220
+ }
9221
+ }
9222
+ if (!isSpkiSha256(spkiSha256))
9223
+ return { ok: false, problem: "the server's signer hash is not a sha256" };
9224
+ const fingerprint = keySignerFingerprint(spkiSha256);
9225
+ if (normalizeFingerprint(signer.fingerprint) !== normalizeFingerprint(fingerprint)) {
9226
+ return { ok: false, problem: "the fingerprint the server reports is not the one its hash gives" };
9227
+ }
9228
+ return { ok: true, fingerprint, spkiSha256 };
9229
+ }
9230
+ async function confirmSignerPin(ctx, keyPrefix, signer, opts = {}) {
9149
9231
  const { deps } = ctx;
9150
9232
  const nothing = opts.nothing ?? "Nothing was imported and nothing was pinned.";
9233
+ const verified = verifiedSigner(signer);
9234
+ if (!verified.ok) {
9235
+ return {
9236
+ ok: false,
9237
+ failure: {
9238
+ code: "KEY_SIGNER_UNVERIFIED",
9239
+ message: `Key ${keyPrefix}'s signer cannot be checked on this machine: ${verified.problem}. ${nothing}`,
9240
+ suggestion: "Do not trust this signer. Check the API address this CLI uses, and run the command again."
9241
+ }
9242
+ };
9243
+ }
9244
+ const active = { fingerprint: verified.fingerprint, spkiSha256: verified.spkiSha256 };
9151
9245
  const pin = await readPin(deps, keyPrefix);
9152
9246
  if (pin && pin.spkiSha256 === active.spkiSha256)
9153
9247
  return { ok: true, pinned: "same" };
@@ -9206,6 +9300,7 @@ function signerSlotProblem(stored, expectSpkiSha256) {
9206
9300
  }
9207
9301
  var KEY_PREFIX_RE, CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
9208
9302
  var init_key_signers = __esm(() => {
9303
+ init_client();
9209
9304
  init_secret_store();
9210
9305
  KEY_PREFIX_RE = /^[A-Za-z0-9_-]{8}$/;
9211
9306
  });
@@ -10851,6 +10946,7 @@ var init_errors2 = __esm(() => {
10851
10946
  "SIGN_LOOKUP_TABLE_UNRESOLVED",
10852
10947
  "SIGN_TRANSACTION_UNDECODABLE",
10853
10948
  "SIGN_BROADCAST_FAILED",
10949
+ "SIGN_CONTROL_CHANGE",
10854
10950
  "VAULT_LABEL_NOT_FOUND",
10855
10951
  "VAULT_LABEL_AMBIGUOUS",
10856
10952
  "VAULT_LABEL_TAKEN",
@@ -11472,7 +11568,7 @@ async function runSequencedLegs(ctx, key, opts) {
11472
11568
  throw failWith("INVALID_RESPONSE", `Candle offered another ${current.legKind} leg after the ${plannedCount} planned ${plannedCount === 1 ? "leg" : "legs"}; nothing was signed.`, 1);
11473
11569
  if (signedKinds.has(current.legKind) || landed.some((done) => done.kind === current.legKind))
11474
11570
  throw failWith("INVALID_RESPONSE", `Candle offered another ${current.legKind} leg after one of that kind already landed; nothing was signed.`, 1);
11475
- if (opts.checkLeg && !opts.checkLeg(current.legKind, leg))
11571
+ if (!await opts.checkLeg(current.legKind, leg))
11476
11572
  throw failWith("INVALID_RESPONSE", `The ${current.legKind} leg does not match what this operation confirmed; nothing was signed.`, 1);
11477
11573
  if (!Number.isFinite(current.expiresAt) || current.expiresAt <= ctx.deps.now())
11478
11574
  throw failWith("QUOTE_EXPIRED", `Operation ${current.operationId}'s window closed before the ${current.legKind} leg was signed.`, 1);
@@ -11692,6 +11788,7 @@ var init_trading = __esm(() => {
11692
11788
  init_sha3();
11693
11789
  init_esm();
11694
11790
  init_zod();
11791
+ init_client();
11695
11792
  init_deps();
11696
11793
  init_evm_lite();
11697
11794
  init_key_signers();
@@ -12128,6 +12225,20 @@ function tokenBalanceOf(view) {
12128
12225
  tokenProgram: view.owner
12129
12226
  };
12130
12227
  }
12228
+ function optionalPubkey(data, at) {
12229
+ const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
12230
+ return dv.getUint32(at, true) === 0 ? null : encodePubkey(data.subarray(at + 4, at + 36));
12231
+ }
12232
+ function tokenAuthoritiesOf(view) {
12233
+ if (view === null || tokenBalanceOf(view) === undefined)
12234
+ return;
12235
+ const dv = new DataView(view.data.buffer, view.data.byteOffset, view.data.byteLength);
12236
+ return {
12237
+ delegate: optionalPubkey(view.data, 72),
12238
+ delegatedAmount: dv.getBigUint64(121, true),
12239
+ closeAuthority: optionalPubkey(view.data, 129)
12240
+ };
12241
+ }
12131
12242
  function computeDeltas(snapshots) {
12132
12243
  const sol = [];
12133
12244
  const tokens = [];
@@ -12142,9 +12253,15 @@ function computeDeltas(snapshots) {
12142
12253
  const shape = post ?? pre;
12143
12254
  if (shape === undefined)
12144
12255
  continue;
12256
+ const preAuth = tokenAuthoritiesOf(snapshot.before);
12257
+ const postAuth = tokenAuthoritiesOf(snapshot.after);
12145
12258
  tokens.push({
12146
12259
  account: snapshot.address,
12147
12260
  owner: shape.owner,
12261
+ ...pre ? { ownerBefore: pre.owner } : {},
12262
+ ...post ? { ownerAfter: post.owner } : {},
12263
+ ...preAuth ? { delegateBefore: preAuth.delegate, closeAuthorityBefore: preAuth.closeAuthority } : {},
12264
+ ...postAuth ? { delegateAfter: postAuth.delegate, closeAuthorityAfter: postAuth.closeAuthority } : {},
12148
12265
  mint: shape.mint,
12149
12266
  tokenProgram: shape.tokenProgram,
12150
12267
  before: pre?.amount ?? 0n,
@@ -12153,6 +12270,64 @@ function computeDeltas(snapshots) {
12153
12270
  }
12154
12271
  return { sol, tokens };
12155
12272
  }
12273
+ function controlChanges(snapshots, controlled) {
12274
+ const changes = [];
12275
+ for (const { address, before, after } of snapshots) {
12276
+ if (after === null)
12277
+ continue;
12278
+ if (controlled(address) && before !== null && before.owner === SYSTEM_PROGRAM_ID) {
12279
+ if (after.owner !== before.owner || after.data.length !== before.data.length)
12280
+ changes.push({
12281
+ account: address,
12282
+ controller: address,
12283
+ what: "program owner",
12284
+ from: before.owner,
12285
+ to: after.data.length !== before.data.length ? `${after.owner} with ${after.data.length} data bytes` : after.owner
12286
+ });
12287
+ continue;
12288
+ }
12289
+ const pre = tokenBalanceOf(before);
12290
+ const preAuth = tokenAuthoritiesOf(before);
12291
+ const post = tokenBalanceOf(after);
12292
+ const postAuth = tokenAuthoritiesOf(after);
12293
+ if (pre !== undefined && preAuth !== undefined && controlled(pre.owner)) {
12294
+ const base = { account: address, controller: pre.owner, mint: pre.mint };
12295
+ if (post === undefined || postAuth === undefined) {
12296
+ changes.push({ ...base, what: "owner", from: pre.owner, to: `${after.owner} (no longer a token account)` });
12297
+ continue;
12298
+ }
12299
+ if (post.owner !== pre.owner)
12300
+ changes.push({ ...base, what: "owner", from: pre.owner, to: post.owner });
12301
+ if (postAuth.delegate !== null && (postAuth.delegate !== preAuth.delegate || postAuth.delegatedAmount > preAuth.delegatedAmount))
12302
+ changes.push({
12303
+ ...base,
12304
+ what: "delegate",
12305
+ from: preAuth.delegate,
12306
+ to: `${postAuth.delegate} (may move up to ${postAuth.delegatedAmount} raw)`
12307
+ });
12308
+ if (postAuth.closeAuthority !== null && postAuth.closeAuthority !== preAuth.closeAuthority)
12309
+ changes.push({ ...base, what: "close authority", from: preAuth.closeAuthority, to: postAuth.closeAuthority });
12310
+ continue;
12311
+ }
12312
+ if (pre === undefined && post !== undefined && postAuth !== undefined && controlled(post.owner)) {
12313
+ const base = { account: address, controller: post.owner, mint: post.mint };
12314
+ if (postAuth.delegate !== null && postAuth.delegate !== post.owner)
12315
+ changes.push({
12316
+ ...base,
12317
+ what: "delegate",
12318
+ from: null,
12319
+ to: `${postAuth.delegate} (may move up to ${postAuth.delegatedAmount} raw)`
12320
+ });
12321
+ if (postAuth.closeAuthority !== null && postAuth.closeAuthority !== post.owner)
12322
+ changes.push({ ...base, what: "close authority", from: null, to: postAuth.closeAuthority });
12323
+ }
12324
+ }
12325
+ return changes;
12326
+ }
12327
+ function describeControlChange(change) {
12328
+ const subject = change.mint === undefined ? `wallet ${change.account}` : `token account ${change.account} (mint ${change.mint}, owned by ${change.controller})`;
12329
+ return `${subject}: ${change.what} changes from ${change.from ?? "none"} to ${change.to ?? "none"}`;
12330
+ }
12156
12331
  function programNameOf(programId) {
12157
12332
  switch (programId) {
12158
12333
  case SYSTEM_PROGRAM_ID:
@@ -12173,7 +12348,7 @@ function programNameOf(programId) {
12173
12348
  return programId;
12174
12349
  }
12175
12350
  }
12176
- var ADDRESS_LOOKUP_TABLE_PROGRAM_ID = "AddressLookupTab1e1111111111111111111111111", COMPUTE_BUDGET_PROGRAM_ID = "ComputeBudget111111111111111111111111111111", MEMO_PROGRAM_ID = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr", TransactionDecodeError, LookupTableError, SIGNATURE_BYTES = 64, PUBKEY_BYTES = 32, BLOCKHASH_BYTES = 32, LOOKUP_TABLE_META_SIZE = 56, TOKEN_ACCOUNT_SIZE = 165;
12351
+ var ADDRESS_LOOKUP_TABLE_PROGRAM_ID = "AddressLookupTab1e1111111111111111111111111", COMPUTE_BUDGET_PROGRAM_ID = "ComputeBudget111111111111111111111111111111", MEMO_PROGRAM_ID = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr", TransactionDecodeError, LookupTableError, SIGNATURE_BYTES = 64, PUBKEY_BYTES = 32, BLOCKHASH_BYTES = 32, LOOKUP_TABLE_META_SIZE = 56, TOKEN_ACCOUNT_SIZE = 165, TOKEN_CONTROL_RANGES;
12177
12352
  var init_solana_alt = __esm(() => {
12178
12353
  init_esm();
12179
12354
  init_solana_lite();
@@ -12181,6 +12356,14 @@ var init_solana_alt = __esm(() => {
12181
12356
  };
12182
12357
  LookupTableError = class LookupTableError extends Error {
12183
12358
  };
12359
+ TOKEN_CONTROL_RANGES = [
12360
+ [0, 32, "mint"],
12361
+ [32, 64, "authority"],
12362
+ [72, 108, "delegate"],
12363
+ [108, 109, "state"],
12364
+ [121, 129, "delegated amount"],
12365
+ [129, 165, "close authority"]
12366
+ ];
12184
12367
  });
12185
12368
 
12186
12369
  // src/bridge.ts
@@ -12416,6 +12599,683 @@ var init_bridge = __esm(() => {
12416
12599
  BRIDGE_WAIT_MS = 10 * 60 * 1000;
12417
12600
  });
12418
12601
 
12602
+ // src/hood-legs.ts
12603
+ function bytesOf(hex2) {
12604
+ try {
12605
+ return hexToBytes2(hex2);
12606
+ } catch {
12607
+ return;
12608
+ }
12609
+ }
12610
+ function selectorOf(data) {
12611
+ return data.length < 4 ? "" : bytesToHex2(data.subarray(0, 4));
12612
+ }
12613
+ function word2(data, index, base = 4) {
12614
+ const start = base + index * 32;
12615
+ return start + 32 <= data.length ? data.subarray(start, start + 32) : undefined;
12616
+ }
12617
+ function wordUint2(w) {
12618
+ return w === undefined ? undefined : BigInt(bytesToHex2(w));
12619
+ }
12620
+ function wordAddress2(w) {
12621
+ if (w === undefined || w.subarray(0, 12).some((b) => b !== 0))
12622
+ return;
12623
+ return bytesToHex2(w.subarray(12));
12624
+ }
12625
+ function isDecimal(value) {
12626
+ return /^\d+$/.test(value);
12627
+ }
12628
+ function among(address, list) {
12629
+ return list.some((known) => sameEvmAddress(address, known));
12630
+ }
12631
+ function decodeApprove(data) {
12632
+ if (data.length !== 68 || selectorOf(data) !== ERC20_APPROVE_SELECTOR)
12633
+ return;
12634
+ const spender = wordAddress2(word2(data, 0));
12635
+ const amount = wordUint2(word2(data, 1));
12636
+ return spender === undefined || amount === undefined ? undefined : { spender, amount };
12637
+ }
12638
+ function isDynamicType(type) {
12639
+ if (typeof type === "string")
12640
+ return type === "bytes";
12641
+ return "array" in type || type.tuple.some(isDynamicType);
12642
+ }
12643
+ function headSize(type) {
12644
+ return typeof type === "object" && "tuple" in type && !isDynamicType(type) ? type.tuple.reduce((sum, member) => sum + headSize(member), 0) : 32;
12645
+ }
12646
+ function readWord(data, position) {
12647
+ if (!Number.isSafeInteger(position) || position < 0 || position + 32 > data.length)
12648
+ throw new Error("abi: short data");
12649
+ return data.subarray(position, position + 32);
12650
+ }
12651
+ function readNumber(data, position) {
12652
+ const value = BigInt(bytesToHex2(readWord(data, position)));
12653
+ if (value > BigInt(data.length))
12654
+ throw new Error("abi: offset past the data");
12655
+ return Number(value);
12656
+ }
12657
+ function decodeAbiTuple(data, start, types2) {
12658
+ const out = [];
12659
+ let cursor = start;
12660
+ for (const type of types2) {
12661
+ if (isDynamicType(type)) {
12662
+ out.push(decodeAbiDynamic(data, start + readNumber(data, cursor), type));
12663
+ cursor += 32;
12664
+ } else {
12665
+ out.push(decodeAbiStatic(data, cursor, type));
12666
+ cursor += headSize(type);
12667
+ }
12668
+ }
12669
+ return out;
12670
+ }
12671
+ function decodeAbiStatic(data, position, type) {
12672
+ if (typeof type === "object" && "tuple" in type)
12673
+ return decodeAbiTuple(data, position, type.tuple);
12674
+ const w = readWord(data, position);
12675
+ if (type === "address") {
12676
+ const address = wordAddress2(w);
12677
+ if (address === undefined)
12678
+ throw new Error("abi: dirty address");
12679
+ return address;
12680
+ }
12681
+ if (type === "bool")
12682
+ return BigInt(bytesToHex2(w)) === 1n;
12683
+ return BigInt(bytesToHex2(w));
12684
+ }
12685
+ function decodeAbiDynamic(data, position, type) {
12686
+ if (type === "bytes") {
12687
+ const length = readNumber(data, position);
12688
+ if (position + 32 + length > data.length)
12689
+ throw new Error("abi: short bytes");
12690
+ return data.subarray(position + 32, position + 32 + length);
12691
+ }
12692
+ if (typeof type === "object" && "array" in type) {
12693
+ const length = readNumber(data, position);
12694
+ if (length > MAX_ABI_ARRAY)
12695
+ throw new Error("abi: array too long");
12696
+ return decodeAbiTuple(data, position + 32, Array.from({ length }, () => type.array));
12697
+ }
12698
+ return decodeAbiTuple(data, position, type.tuple);
12699
+ }
12700
+ function decodeArgs(data, types2, base = 4) {
12701
+ try {
12702
+ return decodeAbiTuple(data, base, types2);
12703
+ } catch {
12704
+ return;
12705
+ }
12706
+ }
12707
+ function v2CallOk(data, value, terms) {
12708
+ const selector = selectorOf(data);
12709
+ if (selector === V2_SWAP_ETH_FOR_TOKENS_SELECTOR) {
12710
+ const args = decodeArgs(data, ["uint", ADDRESSES, "address", "uint"]);
12711
+ if (!args || terms.inputToken !== undefined)
12712
+ return false;
12713
+ const path = asList(args[1]);
12714
+ return path.length >= 2 && sameEvmAddress(asAddress(path[0]), HOOD_WETH_ADDRESS) && terms.outputToken !== undefined && sameEvmAddress(asAddress(path[path.length - 1]), terms.outputToken) && asNumber(args[0]) >= terms.minOutRaw && sameEvmAddress(asAddress(args[2]), terms.wallet);
12715
+ }
12716
+ if (selector === V2_SWAP_TOKENS_FOR_ETH_SELECTOR) {
12717
+ const args = decodeArgs(data, ["uint", "uint", ADDRESSES, "address", "uint"]);
12718
+ if (!args || terms.inputToken === undefined || value !== 0n)
12719
+ return false;
12720
+ const path = asList(args[2]);
12721
+ return asNumber(args[0]) <= terms.amountRaw && asNumber(args[1]) >= terms.minOutRaw && terms.outputToken === undefined && path.length >= 2 && sameEvmAddress(asAddress(path[0]), terms.inputToken) && sameEvmAddress(asAddress(path[path.length - 1]), HOOD_WETH_ADDRESS) && sameEvmAddress(asAddress(args[3]), terms.wallet);
12722
+ }
12723
+ return false;
12724
+ }
12725
+ function v3SwapOf(call) {
12726
+ const selector = selectorOf(call);
12727
+ if (selector === V3_EXACT_INPUT_SINGLE_SELECTOR) {
12728
+ const args = decodeArgs(call, [V3_SINGLE]);
12729
+ if (!args)
12730
+ return;
12731
+ const fields = asList(args[0]);
12732
+ return {
12733
+ tokenIn: asAddress(fields[0]),
12734
+ tokenOut: asAddress(fields[1]),
12735
+ amountIn: asNumber(fields[4]),
12736
+ minimum: asNumber(fields[5]),
12737
+ recipient: asAddress(fields[3])
12738
+ };
12739
+ }
12740
+ if (selector === V3_EXACT_INPUT_SELECTOR) {
12741
+ const args = decodeArgs(call, [V3_PATH]);
12742
+ if (!args)
12743
+ return;
12744
+ const fields = asList(args[0]);
12745
+ const path = asBytes(fields[0]);
12746
+ if (path.length < 43 || (path.length - 20) % 23 !== 0)
12747
+ return;
12748
+ return {
12749
+ tokenIn: bytesToHex2(path.subarray(0, 20)),
12750
+ tokenOut: bytesToHex2(path.subarray(-20)),
12751
+ amountIn: asNumber(fields[2]),
12752
+ minimum: asNumber(fields[3]),
12753
+ recipient: asAddress(fields[1])
12754
+ };
12755
+ }
12756
+ return;
12757
+ }
12758
+ function v3CallOk(data, terms) {
12759
+ if (selectorOf(data) !== V3_MULTICALL_SELECTOR)
12760
+ return false;
12761
+ const args = decodeArgs(data, ["uint", { array: "bytes" }]);
12762
+ if (!args)
12763
+ return false;
12764
+ const calls = asList(args[1]).map(asBytes);
12765
+ if (calls.length === 0 || calls.length > 4)
12766
+ return false;
12767
+ let swaps = 0;
12768
+ let spent = 0n;
12769
+ let heldByRouter = false;
12770
+ let unwrappedToWallet = false;
12771
+ for (const call of calls) {
12772
+ const selector = selectorOf(call);
12773
+ if (selector === V3_REFUND_ETH_SELECTOR) {
12774
+ if (call.length !== 4)
12775
+ return false;
12776
+ } else if (selector === V3_UNWRAP_WETH9_SELECTOR) {
12777
+ const unwrap = decodeArgs(call, ["uint", "address"]);
12778
+ if (!unwrap || terms.outputToken !== undefined || !heldByRouter || unwrappedToWallet || asNumber(unwrap[0]) < terms.minOutRaw || !sameEvmAddress(asAddress(unwrap[1]), terms.wallet))
12779
+ return false;
12780
+ unwrappedToWallet = true;
12781
+ } else {
12782
+ const swap = v3SwapOf(call);
12783
+ if (!swap || unwrappedToWallet || !sameEvmAddress(swap.tokenIn, routedInput(terms)) || !sameEvmAddress(swap.tokenOut, routedOutput(terms)) || swap.minimum < terms.minOutRaw || !sameEvmAddress(swap.recipient, terms.outputToken === undefined ? ADDRESS_THIS : terms.wallet))
12784
+ return false;
12785
+ if (!sameEvmAddress(swap.recipient, terms.wallet))
12786
+ heldByRouter = true;
12787
+ swaps++;
12788
+ spent += swap.amountIn;
12789
+ }
12790
+ }
12791
+ return swaps > 0 && spent <= terms.amountRaw && (!heldByRouter || unwrappedToWallet);
12792
+ }
12793
+ function bytesAre(bytes, expected) {
12794
+ return bytes.length === expected.length && expected.every((byte, i) => bytes[i] === byte);
12795
+ }
12796
+ function v4Sides(params) {
12797
+ const key = asList(params[0]);
12798
+ const zeroForOne = params[1] === true;
12799
+ return { input: asAddress(zeroForOne ? key[0] : key[1]), output: asAddress(zeroForOne ? key[1] : key[0]) };
12800
+ }
12801
+ function v4InputIs(currency, terms) {
12802
+ return sameEvmAddress(currency, terms.inputToken ?? NATIVE_CURRENCY);
12803
+ }
12804
+ function v4CallOk(data, terms) {
12805
+ if (selectorOf(data) !== UNIVERSAL_EXECUTE_SELECTOR)
12806
+ return false;
12807
+ const args = decodeArgs(data, ["bytes", { array: "bytes" }, "uint"]);
12808
+ if (!args)
12809
+ return false;
12810
+ const commands = asBytes(args[0]);
12811
+ const inputs = asList(args[1]).map(asBytes);
12812
+ try {
12813
+ if (bytesAre(commands, [UR_V4_SWAP]) && inputs.length === 1)
12814
+ return v4DirectOk(inputs[0], terms);
12815
+ if (bytesAre(commands, [UR_WRAP_ETH, UR_V3_SWAP_EXACT_IN, UR_V4_SWAP]) && inputs.length === 3)
12816
+ return v4BridgedBuyOk(inputs, terms);
12817
+ if (bytesAre(commands, [UR_V4_SWAP, UR_V3_SWAP_EXACT_IN, UR_UNWRAP_WETH]) && inputs.length === 3)
12818
+ return v4BridgedSellOk(inputs, terms);
12819
+ } catch {
12820
+ return false;
12821
+ }
12822
+ return false;
12823
+ }
12824
+ function v4DirectOk(input, terms) {
12825
+ const top = decodeArgs(input, ["bytes", { array: "bytes" }], 0);
12826
+ if (!top || !bytesAre(asBytes(top[0]), [V4_SWAP_EXACT_IN_SINGLE, V4_SETTLE_ALL, V4_TAKE_ALL]))
12827
+ return false;
12828
+ const params = asList(top[1]).map(asBytes);
12829
+ if (params.length !== 3)
12830
+ return false;
12831
+ const swap = decodeArgs(params[0], [V4_SWAP_PARAMS], 0);
12832
+ const settle = decodeArgs(params[1], ["address", "uint"], 0);
12833
+ const take2 = decodeArgs(params[2], ["address", "uint"], 0);
12834
+ if (!swap || !settle || !take2)
12835
+ return false;
12836
+ const fields = asList(swap[0]);
12837
+ const { input: inCurrency, output: outCurrency } = v4Sides(fields);
12838
+ return v4InputIs(inCurrency, terms) && sameEvmAddress(outCurrency, terms.outputToken ?? NATIVE_CURRENCY) && asNumber(fields[3]) >= terms.minOutRaw && asNumber(take2[1]) >= terms.minOutRaw && asNumber(fields[2]) <= terms.amountRaw && sameEvmAddress(asAddress(settle[0]), inCurrency) && asNumber(settle[1]) <= terms.amountRaw && sameEvmAddress(asAddress(take2[0]), outCurrency);
12839
+ }
12840
+ function v3IntoRouterOk(input, firstToken, lastToken) {
12841
+ const args = decodeArgs(input, ["address", "uint", "uint", "bytes", "bool"], 0);
12842
+ if (!args)
12843
+ return false;
12844
+ const path = asBytes(args[3]);
12845
+ return sameEvmAddress(asAddress(args[0]), ADDRESS_THIS) && args[4] === false && path.length >= 43 && (path.length - 20) % 23 === 0 && sameEvmAddress(bytesToHex2(path.subarray(0, 20)), firstToken) && sameEvmAddress(bytesToHex2(path.subarray(-20)), lastToken);
12846
+ }
12847
+ function v4BridgedBuyOk([wrap, v3, v4], terms) {
12848
+ if (terms.inputToken !== undefined || terms.outputToken === undefined)
12849
+ return false;
12850
+ const wrapped = decodeArgs(wrap, ["address", "uint"], 0);
12851
+ if (!wrapped || !sameEvmAddress(asAddress(wrapped[0]), ADDRESS_THIS) || asNumber(wrapped[1]) > terms.amountRaw)
12852
+ return false;
12853
+ const top = decodeArgs(v4, ["bytes", { array: "bytes" }], 0);
12854
+ if (!top || !bytesAre(asBytes(top[0]), [V4_SETTLE, V4_SWAP_EXACT_IN_SINGLE, V4_TAKE_ALL]))
12855
+ return false;
12856
+ const params = asList(top[1]).map(asBytes);
12857
+ if (params.length !== 3)
12858
+ return false;
12859
+ const settle = decodeArgs(params[0], ["address", "uint", "bool"], 0);
12860
+ const swap = decodeArgs(params[1], [V4_SWAP_PARAMS], 0);
12861
+ const take2 = decodeArgs(params[2], ["address", "uint"], 0);
12862
+ if (!settle || !swap || !take2 || settle[2] !== false)
12863
+ return false;
12864
+ const fields = asList(swap[0]);
12865
+ const { input, output } = v4Sides(fields);
12866
+ return v3IntoRouterOk(v3, HOOD_WETH_ADDRESS, input) && sameEvmAddress(output, terms.outputToken) && asNumber(fields[3]) >= terms.minOutRaw && asNumber(take2[1]) >= terms.minOutRaw && sameEvmAddress(asAddress(settle[0]), input) && sameEvmAddress(asAddress(take2[0]), output);
12867
+ }
12868
+ function v4BridgedSellOk([v4, v3, unwrap], terms) {
12869
+ if (terms.inputToken === undefined || terms.outputToken !== undefined)
12870
+ return false;
12871
+ const top = decodeArgs(v4, ["bytes", { array: "bytes" }], 0);
12872
+ if (!top || !bytesAre(asBytes(top[0]), [V4_SETTLE, V4_SWAP_EXACT_IN_SINGLE, V4_TAKE]))
12873
+ return false;
12874
+ const params = asList(top[1]).map(asBytes);
12875
+ if (params.length !== 3)
12876
+ return false;
12877
+ const settle = decodeArgs(params[0], ["address", "uint", "bool"], 0);
12878
+ const swap = decodeArgs(params[1], [V4_SWAP_PARAMS], 0);
12879
+ const take2 = decodeArgs(params[2], ["address", "address", "uint"], 0);
12880
+ if (!settle || !swap || !take2)
12881
+ return false;
12882
+ const { input, output } = v4Sides(asList(swap[0]));
12883
+ if (!v4InputIs(input, terms) || !sameEvmAddress(asAddress(settle[0]), input) || asNumber(settle[1]) > terms.amountRaw || settle[2] !== true || !sameEvmAddress(asAddress(take2[0]), output) || !sameEvmAddress(asAddress(take2[1]), HOOD_V4_UNIVERSAL_ROUTER))
12884
+ return false;
12885
+ if (!v3IntoRouterOk(v3, output, HOOD_WETH_ADDRESS))
12886
+ return false;
12887
+ const unwrapped = decodeArgs(unwrap, ["address", "uint"], 0);
12888
+ return unwrapped !== undefined && asNumber(unwrapped[1]) >= terms.minOutRaw && sameEvmAddress(asAddress(unwrapped[0]), terms.wallet);
12889
+ }
12890
+ function kyberCallOk(data, terms) {
12891
+ const selector = selectorOf(data);
12892
+ let desc;
12893
+ if (selector === KYBER_SWAP_SELECTOR || selector === KYBER_SWAP_GENERIC_SELECTOR) {
12894
+ const args = decodeArgs(data, [KYBER_EXECUTION]);
12895
+ desc = args && asList(asList(args[0])[3]);
12896
+ } else if (selector === KYBER_SWAP_SIMPLE_SELECTOR) {
12897
+ const args = decodeArgs(data, ["address", KYBER_DESC, "bytes", "bytes"]);
12898
+ desc = args && asList(args[1]);
12899
+ }
12900
+ if (!desc)
12901
+ return false;
12902
+ const amount = asNumber(desc[7]);
12903
+ return sameEvmAddress(asAddress(desc[0]), terms.inputToken ?? KYBER_NATIVE) && sameEvmAddress(asAddress(desc[1]), terms.outputToken ?? KYBER_NATIVE) && asNumber(desc[8]) >= terms.minOutRaw && sameEvmAddress(asAddress(desc[6]), terms.wallet) && amount === terms.amountRaw && asList(desc[3]).reduce((sum, part) => sum + asNumber(part), 0n) <= amount && asList(desc[4]).length === 0 && asList(desc[5]).every((part) => asNumber(part) === 0n) && asBytes(desc[10]).length === 0;
12904
+ }
12905
+ function dexCallOk(to, data, value, terms) {
12906
+ if (sameEvmAddress(to, HOOD_UNIV2_ROUTER))
12907
+ return v2CallOk(data, value, terms);
12908
+ if (sameEvmAddress(to, HOOD_UNIV3_ROUTER))
12909
+ return v3CallOk(data, terms);
12910
+ if (sameEvmAddress(to, HOOD_V4_UNIVERSAL_ROUTER))
12911
+ return v4CallOk(data, terms);
12912
+ if (sameEvmAddress(to, HOOD_KYBER_ROUTER))
12913
+ return kyberCallOk(data, terms);
12914
+ return false;
12915
+ }
12916
+ async function createCurveLegOk(leg, wallet, rpc) {
12917
+ if (leg.value !== "0" || !sameEvmAddress(leg.to, HOOD_CURVE_FACTORY))
12918
+ return false;
12919
+ const data = bytesOf(leg.data);
12920
+ if (!data || selectorOf(data) !== CREATE_CURVE_SELECTOR)
12921
+ return false;
12922
+ const offset = wordUint2(word2(data, 0));
12923
+ if (offset === undefined || offset % 32n !== 0n || offset > BigInt(data.length))
12924
+ return false;
12925
+ const creator = wordAddress2(word2(data, 5, 4 + Number(offset)));
12926
+ if (creator === undefined || !sameEvmAddress(creator, wallet))
12927
+ return false;
12928
+ const splitter = wordAddress2(word2(data, 3, 4 + Number(offset)));
12929
+ if (splitter === undefined)
12930
+ return false;
12931
+ try {
12932
+ const vouched = await rpc.call({
12933
+ to: HOOD_SPLITTER_FACTORY,
12934
+ data: hexToBytes2(`${SPLITTER_IS_SPLITTER_SELECTOR}${"0".repeat(24)}${splitter.slice(2)}`)
12935
+ });
12936
+ if (wordUint2(word2(vouched, 0, 0)) !== 1n)
12937
+ return false;
12938
+ const dev = wordAddress2(word2(await rpc.call({ to: splitter, data: hexToBytes2(SPLITTER_DEV_SELECTOR) }), 0, 0));
12939
+ return dev !== undefined && sameEvmAddress(dev, wallet);
12940
+ } catch {
12941
+ return false;
12942
+ }
12943
+ }
12944
+ function launchFeeCap(buyRaw) {
12945
+ return (BigInt(buyRaw) * LAUNCH_FEE_MAX_BPS + 9999n) / 10000n;
12946
+ }
12947
+ function feeLegOk(leg, fee) {
12948
+ if (!isDecimal(leg.value))
12949
+ return false;
12950
+ if (fee.asset === "eth") {
12951
+ if (leg.data !== "0x" && leg.data !== "")
12952
+ return false;
12953
+ if (BigInt(leg.value) > fee.cap)
12954
+ return false;
12955
+ return fee.treasury === undefined || sameEvmAddress(leg.to, fee.treasury);
12956
+ }
12957
+ if (leg.value !== "0" || !sameEvmAddress(leg.to, HOOD_USDG_ADDRESS))
12958
+ return false;
12959
+ const data = bytesOf(leg.data);
12960
+ const decoded = data ? decodeErc20Transfer(data) : undefined;
12961
+ if (!decoded || decoded.amount > fee.cap)
12962
+ return false;
12963
+ return fee.treasury === undefined || sameEvmAddress(decoded.recipient, fee.treasury);
12964
+ }
12965
+ function hoodTradeLegCheck(terms) {
12966
+ let curve;
12967
+ const registeredCurve = () => {
12968
+ curve ??= terms.curve ? terms.curve().catch(() => {
12969
+ return;
12970
+ }) : Promise.resolve(undefined);
12971
+ return curve;
12972
+ };
12973
+ const spenderOk = async (spender) => {
12974
+ if (terms.venue !== "curve")
12975
+ return among(spender, DEX_SPENDERS);
12976
+ const registered = await registeredCurve();
12977
+ return registered !== undefined && sameEvmAddress(spender, registered);
12978
+ };
12979
+ return async (kind, leg) => {
12980
+ if (!isDecimal(leg.value))
12981
+ return false;
12982
+ const data = bytesOf(leg.data);
12983
+ if (!data)
12984
+ return false;
12985
+ if (kind === "feeTransfer")
12986
+ return terms.fee !== undefined && feeLegOk(leg, terms.fee);
12987
+ if (kind === "approval") {
12988
+ if (terms.inputToken === undefined || leg.value !== "0" || !sameEvmAddress(leg.to, terms.inputToken))
12989
+ return false;
12990
+ const approve = decodeApprove(data);
12991
+ return approve !== undefined && approve.amount <= terms.amountRaw && await spenderOk(approve.spender);
12992
+ }
12993
+ if (kind === "permit2Approval") {
12994
+ if (terms.inputToken === undefined || terms.venue !== "dex" || leg.value !== "0")
12995
+ return false;
12996
+ if (!sameEvmAddress(leg.to, HOOD_PERMIT2) || data.length !== 132)
12997
+ return false;
12998
+ if (selectorOf(data) !== PERMIT2_APPROVE_SELECTOR)
12999
+ return false;
13000
+ const token = wordAddress2(word2(data, 0));
13001
+ const spender = wordAddress2(word2(data, 1));
13002
+ const amount = wordUint2(word2(data, 2));
13003
+ return token !== undefined && sameEvmAddress(token, terms.inputToken) && spender !== undefined && sameEvmAddress(spender, HOOD_V4_UNIVERSAL_ROUTER) && amount !== undefined && amount <= terms.amountRaw;
13004
+ }
13005
+ if (kind !== "trade")
13006
+ return false;
13007
+ const value = BigInt(leg.value);
13008
+ if (terms.inputToken !== undefined ? value !== 0n : value > terms.amountRaw)
13009
+ return false;
13010
+ if (terms.venue === "dex")
13011
+ return dexCallOk(leg.to, data, value, {
13012
+ wallet: terms.wallet,
13013
+ ...terms.inputToken !== undefined ? { inputToken: terms.inputToken } : {},
13014
+ amountRaw: terms.amountRaw,
13015
+ outputToken: terms.outputToken,
13016
+ minOutRaw: terms.minOutRaw
13017
+ });
13018
+ const registered = await registeredCurve();
13019
+ if (registered === undefined || !sameEvmAddress(leg.to, registered))
13020
+ return false;
13021
+ const selector = selectorOf(data);
13022
+ if (terms.inputToken === undefined)
13023
+ return selector === CURVE_BUY_SELECTOR && data.length === 36 && wordUint2(word2(data, 0)) >= terms.minOutRaw;
13024
+ if (data.length !== 68)
13025
+ return false;
13026
+ const amountIn = wordUint2(word2(data, 0));
13027
+ if (amountIn > terms.amountRaw || wordUint2(word2(data, 1)) < terms.minOutRaw)
13028
+ return false;
13029
+ return selector === (sameEvmAddress(terms.inputToken, HOOD_USDG_ADDRESS) ? CURVE_BUY_WITH_QUOTE_SELECTOR : CURVE_SELL_SELECTOR);
13030
+ };
13031
+ }
13032
+ async function registeredCurveFor(rpc, token) {
13033
+ const call = hexToBytes2(`${CURVE_FOR_BASE_TOKEN_SELECTOR}${"0".repeat(24)}${token.toLowerCase().replace(/^0x/, "")}`);
13034
+ for (const registry of HOOD_CURVE_REGISTRIES) {
13035
+ let answer;
13036
+ try {
13037
+ answer = await rpc.call({ to: registry, data: call });
13038
+ } catch {
13039
+ continue;
13040
+ }
13041
+ const curve = wordAddress2(word2(answer, 0, 0));
13042
+ if (curve !== undefined && BigInt(curve) !== 0n)
13043
+ return curve;
13044
+ }
13045
+ return;
13046
+ }
13047
+ var HOOD_CURVE_FACTORY = "0xD08f8489F026957EdeD3Bb8Ef8A9d151A7B1DF22", HOOD_CURVE_REGISTRIES, HOOD_UNIV2_ROUTER = "0x89e5db8b5aa49aa85ac63f691524311aeb649eba", HOOD_UNIV3_ROUTER = "0xCaf681a66D020601342297493863E78C959E5cb2", HOOD_V4_UNIVERSAL_ROUTER = "0x8876789976decbfcbbbe364623c63652db8c0904", HOOD_KYBER_ROUTER = "0x6131B5fae19EA4f9D964eAc0408E4408b66337b5", HOOD_PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3", HOOD_SPLITTER_FACTORY = "0x9318bD328Ac32DBAe08bbD85B634E6B6B06683BF", KYBER_NATIVE = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", ADDRESS_THIS = "0x0000000000000000000000000000000000000002", DEX_SPENDERS, CREATE_CURVE_SELECTOR = "0xe5ab9610", ERC20_APPROVE_SELECTOR = "0x095ea7b3", PERMIT2_APPROVE_SELECTOR = "0x87517c45", CURVE_BUY_SELECTOR = "0xd96a094a", CURVE_BUY_WITH_QUOTE_SELECTOR = "0x6818735c", CURVE_SELL_SELECTOR = "0xd79875eb", V2_SWAP_ETH_FOR_TOKENS_SELECTOR = "0xb6f9de95", V2_SWAP_TOKENS_FOR_ETH_SELECTOR = "0x791ac947", V3_MULTICALL_SELECTOR = "0x5ae401dc", V3_EXACT_INPUT_SINGLE_SELECTOR = "0x04e45aaf", V3_EXACT_INPUT_SELECTOR = "0xb858183f", V3_UNWRAP_WETH9_SELECTOR = "0x49404b7c", V3_REFUND_ETH_SELECTOR = "0x12210e8a", UNIVERSAL_EXECUTE_SELECTOR = "0x3593564c", KYBER_SWAP_SELECTOR = "0xe21fd0e9", KYBER_SWAP_GENERIC_SELECTOR = "0x59e50fed", KYBER_SWAP_SIMPLE_SELECTOR = "0x8af033fb", SPLITTER_DEV_SELECTOR = "0x91cca3db", SPLITTER_IS_SPLITTER_SELECTOR = "0xa4c597d1", CURVE_FOR_BASE_TOKEN_SELECTOR = "0x446b9253", LAUNCH_FEE_MAX_BPS = 1000n, MAX_ABI_ARRAY = 64, ADDRESSES, V3_SINGLE, V3_PATH, KYBER_DESC, KYBER_EXECUTION, V4_POOL_KEY, V4_SWAP_PARAMS, asBytes = (value) => value, asAddress = (value) => value, asNumber = (value) => value, asList = (value) => value, routedInput = (terms) => terms.inputToken ?? HOOD_WETH_ADDRESS, routedOutput = (terms) => terms.outputToken ?? HOOD_WETH_ADDRESS, UR_V3_SWAP_EXACT_IN = 0, UR_WRAP_ETH = 11, UR_UNWRAP_WETH = 12, UR_V4_SWAP = 16, V4_SWAP_EXACT_IN_SINGLE = 6, V4_SETTLE = 11, V4_SETTLE_ALL = 12, V4_TAKE = 14, V4_TAKE_ALL = 15, NATIVE_CURRENCY = "0x0000000000000000000000000000000000000000";
13048
+ var init_hood_legs = __esm(() => {
13049
+ init_evm_lite();
13050
+ HOOD_CURVE_REGISTRIES = [
13051
+ "0xb789E2711445488407c120563B29dF7c376cC9Ea",
13052
+ "0x75934c16162075583435358f9F042D8D6E42Bd5C",
13053
+ "0xFD54D094ad93349CA316bA309A3C8573E3D6C0cd"
13054
+ ];
13055
+ DEX_SPENDERS = [HOOD_UNIV2_ROUTER, HOOD_UNIV3_ROUTER, HOOD_KYBER_ROUTER, HOOD_PERMIT2];
13056
+ ADDRESSES = { array: "address" };
13057
+ V3_SINGLE = { tuple: ["address", "address", "uint", "address", "uint", "uint", "uint"] };
13058
+ V3_PATH = { tuple: ["bytes", "address", "uint", "uint"] };
13059
+ KYBER_DESC = {
13060
+ tuple: [
13061
+ "address",
13062
+ "address",
13063
+ { array: "address" },
13064
+ { array: "uint" },
13065
+ { array: "address" },
13066
+ { array: "uint" },
13067
+ "address",
13068
+ "uint",
13069
+ "uint",
13070
+ "uint",
13071
+ "bytes"
13072
+ ]
13073
+ };
13074
+ KYBER_EXECUTION = { tuple: ["address", "address", "bytes", KYBER_DESC, "bytes"] };
13075
+ V4_POOL_KEY = { tuple: ["address", "address", "uint", "uint", "address"] };
13076
+ V4_SWAP_PARAMS = { tuple: [V4_POOL_KEY, "bool", "uint", "uint", "uint", "bytes"] };
13077
+ });
13078
+
13079
+ // src/solana-verify.ts
13080
+ function isTokenProgram(program) {
13081
+ return program === TOKEN_PROGRAM_ID || program === TOKEN_2022_PROGRAM_ID;
13082
+ }
13083
+ function systemIndex(data) {
13084
+ if (data.length < 4)
13085
+ return -1;
13086
+ return new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(0, true);
13087
+ }
13088
+ function assetOf(asset) {
13089
+ return asset === NATIVE_MINT ? "SOL" : asset;
13090
+ }
13091
+ function instructionProblem(tx, compiled, wallet, expect) {
13092
+ for (const [i, ix] of tx.message.instructions.entries()) {
13093
+ const program = compiled.keys[ix.programIdIndex];
13094
+ const account = (at) => compiled.keys[ix.accountIndexes[at] ?? -1];
13095
+ const kind = ix.data[0];
13096
+ if (isTokenProgram(program)) {
13097
+ if (kind === TOKEN_IX_APPROVE || kind === TOKEN_IX_APPROVE_CHECKED)
13098
+ return `instruction ${i} approves a delegate over a token account`;
13099
+ if (kind === TOKEN_IX_SET_AUTHORITY)
13100
+ return `instruction ${i} changes a token account's authority`;
13101
+ if (kind === TOKEN_IX_CLOSE_ACCOUNT && account(1) !== wallet)
13102
+ return `instruction ${i} closes a token account to ${account(1) ?? "?"}, not this wallet`;
13103
+ }
13104
+ if (program === SYSTEM_PROGRAM_ID) {
13105
+ const index = systemIndex(ix.data);
13106
+ if ((index === SYSTEM_IX_ASSIGN || index === SYSTEM_IX_ALLOCATE) && account(0) === wallet)
13107
+ return `instruction ${i} reassigns this wallet to another program`;
13108
+ }
13109
+ if (expect.kind !== "transfer")
13110
+ continue;
13111
+ if (program === undefined || !TRANSFER_PROGRAMS.has(program))
13112
+ return `instruction ${i} calls ${program ?? "an unknown program"}, which a transfer never does`;
13113
+ if (program === SYSTEM_PROGRAM_ID && systemIndex(ix.data) !== SYSTEM_IX_TRANSFER)
13114
+ return `instruction ${i} is a System instruction other than Transfer`;
13115
+ if (isTokenProgram(program) && kind !== TOKEN_IX_TRANSFER && kind !== TOKEN_IX_TRANSFER_CHECKED)
13116
+ return `instruction ${i} is a token instruction other than Transfer`;
13117
+ if (program === ASSOCIATED_TOKEN_PROGRAM_ID) {
13118
+ const create = ix.data.length === 0 ? ATA_IX_CREATE : ix.data.length === 1 ? ix.data[0] : -1;
13119
+ if (create !== ATA_IX_CREATE && create !== ATA_IX_CREATE_IDEMPOTENT)
13120
+ return `instruction ${i} is an Associated Token instruction other than Create`;
13121
+ const owner = account(2);
13122
+ if (account(0) !== wallet || owner !== expect.recipient && owner !== wallet)
13123
+ return `instruction ${i} creates a token account for ${owner ?? "?"}, not ${expect.recipient}`;
13124
+ }
13125
+ }
13126
+ return null;
13127
+ }
13128
+ function holdings(views, wallet) {
13129
+ const out = new Map;
13130
+ const add2 = (asset, amount) => out.set(asset, (out.get(asset) ?? 0n) + amount);
13131
+ for (const { address, view } of views) {
13132
+ if (view === null)
13133
+ continue;
13134
+ if (address === wallet) {
13135
+ add2("SOL", view.lamports);
13136
+ continue;
13137
+ }
13138
+ const balance = tokenBalanceOf(view);
13139
+ if (balance === undefined || balance.owner !== wallet)
13140
+ continue;
13141
+ add2("SOL", view.lamports);
13142
+ if (balance.mint !== NATIVE_MINT)
13143
+ add2(balance.mint, balance.amount);
13144
+ }
13145
+ return out;
13146
+ }
13147
+ function walletDeltas(simulation, wallet) {
13148
+ const before = holdings(simulation.snapshots.map((s) => ({ address: s.address, view: s.before })), wallet);
13149
+ const after = holdings(simulation.snapshots.map((s) => ({ address: s.address, view: s.after })), wallet);
13150
+ const deltas = new Map;
13151
+ for (const asset of new Set([...before.keys(), ...after.keys()]))
13152
+ deltas.set(asset, (after.get(asset) ?? 0n) - (before.get(asset) ?? 0n));
13153
+ if (!deltas.has("SOL"))
13154
+ deltas.set("SOL", 0n);
13155
+ return deltas;
13156
+ }
13157
+ function transferProblem(simulation, wallet, expect) {
13158
+ const allowed = (address) => address === wallet || address === expect.recipient;
13159
+ for (const { address, before, after } of simulation.snapshots) {
13160
+ const lamportsBefore = before?.lamports ?? 0n;
13161
+ const lamportsAfter = after?.lamports ?? 0n;
13162
+ const tokenAfter = tokenBalanceOf(after);
13163
+ const tokenBefore = tokenBalanceOf(before);
13164
+ if (lamportsAfter > lamportsBefore && !allowed(address) && !allowed(tokenAfter?.owner))
13165
+ return `${address} would receive ${lamportsAfter - lamportsBefore} lamports, and only ${expect.recipient} may`;
13166
+ const amountBefore = tokenBefore?.amount ?? 0n;
13167
+ const amountAfter = tokenAfter?.amount ?? 0n;
13168
+ if (tokenAfter !== undefined && amountAfter > amountBefore && !allowed(tokenAfter.owner))
13169
+ return `${tokenAfter.owner} would receive ${amountAfter - amountBefore} raw of ${tokenAfter.mint}, and only ${expect.recipient} may`;
13170
+ }
13171
+ const asset = assetOf(expect.asset);
13172
+ const deltas = walletDeltas(simulation, wallet);
13173
+ const sol = deltas.get("SOL") ?? 0n;
13174
+ for (const [mint, delta] of deltas) {
13175
+ if (mint === "SOL" || mint === asset || delta >= 0n)
13176
+ continue;
13177
+ return `the wallet would also lose ${-delta} raw of ${mint}`;
13178
+ }
13179
+ if (asset === "SOL") {
13180
+ if (-sol < expect.amountRaw || -sol > expect.amountRaw + SOLANA_OVERHEAD_LAMPORTS)
13181
+ return `the wallet would lose ${-sol} lamports, not the ${expect.amountRaw} confirmed plus network fees`;
13182
+ const recipient = simulation.snapshots.find((s) => s.address === expect.recipient);
13183
+ const received2 = (recipient?.after?.lamports ?? 0n) - (recipient?.before?.lamports ?? 0n);
13184
+ if (received2 !== expect.amountRaw)
13185
+ return `${expect.recipient} would receive ${received2} lamports, not the ${expect.amountRaw} confirmed`;
13186
+ return null;
13187
+ }
13188
+ const moved = -(deltas.get(asset) ?? 0n);
13189
+ if (moved !== expect.amountRaw)
13190
+ return `the wallet would send ${moved} raw of ${asset}, not the ${expect.amountRaw} confirmed`;
13191
+ if (-sol > SOLANA_OVERHEAD_LAMPORTS)
13192
+ return `the wallet would also lose ${-sol} lamports, more than network fees and rent`;
13193
+ let received = 0n;
13194
+ for (const { before, after } of simulation.snapshots) {
13195
+ const post = tokenBalanceOf(after);
13196
+ if (post === undefined || post.owner !== expect.recipient || post.mint !== asset)
13197
+ continue;
13198
+ received += post.amount - (tokenBalanceOf(before)?.amount ?? 0n);
13199
+ }
13200
+ if (received <= 0n)
13201
+ return `${expect.recipient} would receive none of ${asset}`;
13202
+ return null;
13203
+ }
13204
+ function swapProblem(simulation, wallet, expect) {
13205
+ const spend = assetOf(expect.spend);
13206
+ const receive = expect.receive === undefined ? undefined : assetOf(expect.receive.asset);
13207
+ const deltas = walletDeltas(simulation, wallet);
13208
+ for (const [asset, delta] of deltas) {
13209
+ if (asset === "SOL" || asset === spend || asset === receive || delta >= 0n)
13210
+ continue;
13211
+ return `the wallet would also lose ${-delta} raw of ${asset}`;
13212
+ }
13213
+ const sol = deltas.get("SOL") ?? 0n;
13214
+ const spent = -(deltas.get(spend) ?? 0n);
13215
+ const spendLimit = expect.maxSpendRaw + (spend === "SOL" ? SOLANA_OVERHEAD_LAMPORTS : 0n);
13216
+ if (spent > spendLimit)
13217
+ return `the wallet would spend ${spent} raw of ${spend}, more than the ${expect.maxSpendRaw} confirmed${spend === "SOL" ? " plus network fees" : ""}`;
13218
+ if (spend !== "SOL" && receive !== "SOL" && -sol > SOLANA_OVERHEAD_LAMPORTS)
13219
+ return `the wallet would also lose ${-sol} lamports, more than network fees and rent`;
13220
+ if (expect.receive === undefined || receive === undefined)
13221
+ return null;
13222
+ const received = deltas.get(receive) ?? 0n;
13223
+ const floor = expect.receive.minRaw - (receive === "SOL" ? SOLANA_OVERHEAD_LAMPORTS : 0n);
13224
+ if (received < floor || receive !== "SOL" && received <= 0n)
13225
+ return `the wallet would receive ${received} raw of ${receive}, less than the ${expect.receive.minRaw} confirmed`;
13226
+ return null;
13227
+ }
13228
+ async function solanaSpendProblem(transactionBase64, wallet, rpc, expect) {
13229
+ let tx;
13230
+ try {
13231
+ tx = decodeTransaction(decodeStrictBase64(transactionBase64));
13232
+ } catch (error) {
13233
+ if (error instanceof TransactionDecodeError)
13234
+ return `the transaction does not decode: ${error.message}`;
13235
+ throw error;
13236
+ }
13237
+ if (tx.message.staticKeys[0] !== wallet)
13238
+ return `the fee payer is ${tx.message.staticKeys[0]}, not this wallet`;
13239
+ let compiled;
13240
+ try {
13241
+ compiled = await resolveCompiledKeys(tx.message, rpc);
13242
+ } catch (error) {
13243
+ if (error instanceof LookupTableError)
13244
+ return `a lookup table did not resolve: ${error.message}`;
13245
+ throw error;
13246
+ }
13247
+ const refused = instructionProblem(tx, compiled, wallet, expect);
13248
+ if (refused)
13249
+ return refused;
13250
+ let simulation;
13251
+ try {
13252
+ simulation = await simulateWithSnapshots(rpc, transactionBase64, compiled);
13253
+ } catch (error) {
13254
+ if (isRateLimited(error))
13255
+ throw error;
13256
+ return `the simulation could not be run: ${error instanceof Error ? error.message : String(error)}`;
13257
+ }
13258
+ if (simulation.result.err !== null && simulation.result.err !== undefined)
13259
+ return `the simulation failed: ${JSON.stringify(simulation.result.err)}`;
13260
+ const control = controlChanges(simulation.snapshots, (address) => address === wallet);
13261
+ if (control[0] !== undefined)
13262
+ return describeControlChange(control[0]);
13263
+ return expect.kind === "transfer" ? transferProblem(simulation, wallet, expect) : swapProblem(simulation, wallet, expect);
13264
+ }
13265
+ var NATIVE_MINT = "So11111111111111111111111111111111111111112", SOLANA_OVERHEAD_LAMPORTS = 20000000n, TOKEN_IX_TRANSFER = 3, TOKEN_IX_APPROVE = 4, TOKEN_IX_SET_AUTHORITY = 6, TOKEN_IX_CLOSE_ACCOUNT = 9, TOKEN_IX_TRANSFER_CHECKED = 12, TOKEN_IX_APPROVE_CHECKED = 13, SYSTEM_IX_ASSIGN = 1, SYSTEM_IX_TRANSFER = 2, SYSTEM_IX_ALLOCATE = 8, ATA_IX_CREATE = 0, ATA_IX_CREATE_IDEMPOTENT = 1, TRANSFER_PROGRAMS;
13266
+ var init_solana_verify = __esm(() => {
13267
+ init_solana_alt();
13268
+ init_solana_lite();
13269
+ TRANSFER_PROGRAMS = new Set([
13270
+ SYSTEM_PROGRAM_ID,
13271
+ TOKEN_PROGRAM_ID,
13272
+ TOKEN_2022_PROGRAM_ID,
13273
+ ASSOCIATED_TOKEN_PROGRAM_ID,
13274
+ COMPUTE_BUDGET_PROGRAM_ID,
13275
+ MEMO_PROGRAM_ID
13276
+ ]);
13277
+ });
13278
+
12419
13279
  // src/commands/swap.ts
12420
13280
  import { randomUUID } from "node:crypto";
12421
13281
  function tradingFailure(ctx, error, id) {
@@ -12680,15 +13540,15 @@ async function swap(args, ctx) {
12680
13540
  const outDecimals = await decimalsFor(ctx, to, solana);
12681
13541
  let amountRaw;
12682
13542
  if (percent !== undefined) {
12683
- const reader = await solana();
13543
+ const reader2 = await solana();
12684
13544
  let balance;
12685
13545
  if (from === "SOL")
12686
- balance = await tradingRead(ctx, reader, () => reader.rpc.getBalance(wallet.address));
13546
+ balance = await tradingRead(ctx, reader2, () => reader2.rpc.getBalance(wallet.address));
12687
13547
  else {
12688
13548
  const mint = BASES[from]?.mint ?? from;
12689
- const accounts = (await tradingRead(ctx, reader, () => Promise.all([
12690
- reader.rpc.getTokenAccountsByOwner(wallet.address, TOKEN_PROGRAM_ID),
12691
- reader.rpc.getTokenAccountsByOwner(wallet.address, TOKEN_2022_PROGRAM_ID)
13549
+ const accounts = (await tradingRead(ctx, reader2, () => Promise.all([
13550
+ reader2.rpc.getTokenAccountsByOwner(wallet.address, TOKEN_PROGRAM_ID),
13551
+ reader2.rpc.getTokenAccountsByOwner(wallet.address, TOKEN_2022_PROGRAM_ID)
12692
13552
  ]))).flat();
12693
13553
  balance = accounts.filter((account) => account.mint === mint).reduce((sum, account) => sum + BigInt(account.amountRaw), 0n);
12694
13554
  }
@@ -12771,6 +13631,21 @@ async function swap(args, ctx) {
12771
13631
  throw new TradingError("INVALID_RESPONSE", "A TEE swap must contain exactly one same-chain transaction.");
12772
13632
  if (!transaction)
12773
13633
  throw new TradingError("INVALID_RESPONSE", "Missing transaction.");
13634
+ const feeRaw = BigInt(data.fee.feeRaw);
13635
+ const feeInInput = kind === "trade" && Boolean(fromBase);
13636
+ const solanaAssetOf = (asset) => asset === "SOL" ? "SOL" : BASES[asset]?.mint ?? asset;
13637
+ const reader = await solana();
13638
+ const problem = await tradingRead(ctx, reader, () => solanaSpendProblem(transaction, wallet.address, reader.rpc, {
13639
+ kind: "swap",
13640
+ spend: solanaAssetOf(from),
13641
+ maxSpendRaw: BigInt(amountRaw) + (feeInInput ? feeRaw : 0n),
13642
+ receive: {
13643
+ asset: solanaAssetOf(to),
13644
+ minRaw: BigInt(minimumRaw)
13645
+ }
13646
+ }));
13647
+ if (problem)
13648
+ throw new TradingError("INVALID_RESPONSE", `The transaction Candle built does not match the confirmed quote (${safeText(problem)}); nothing was signed.`);
12774
13649
  const signed = await relaySign(ctx, key, payerWallet.wallet, transaction);
12775
13650
  const result = kind === "swap" ? await request(ctx, key, "/api/v1/agent/swap/submit", {
12776
13651
  clientTradeId: id,
@@ -12980,6 +13855,19 @@ async function hoodSwap(ctx, args) {
12980
13855
  throw chainMismatch(`TEE wallet ${wallet.id}`, wallet.chain === "evm" ? "hood" : "solana", "hood");
12981
13856
  const recorded = kind === "trade" ? token : HOOD_USDG_ADDRESS;
12982
13857
  const notices = [];
13858
+ const feeRaw = BigInt(data.fee.feeRaw);
13859
+ const treasury = data.fee.treasury;
13860
+ const feeAsset = artifacts.quoteAsset === "usdg" ? "usdg" : "eth";
13861
+ const checkLeg = hoodTradeLegCheck({
13862
+ wallet: wallet.address,
13863
+ inputToken: from.asset === "ETH" ? undefined : from.asset === "USDG" ? HOOD_USDG_ADDRESS : from.asset,
13864
+ amountRaw: BigInt(amountRaw),
13865
+ outputToken: to.asset === "ETH" ? undefined : to.asset === "USDG" ? HOOD_USDG_ADDRESS : to.asset,
13866
+ minOutRaw: BigInt(data.minOutRaw),
13867
+ venue: kind === "trade" && artifacts.venue === "curve" ? "curve" : "dex",
13868
+ ...kind === "trade" && feeRaw > 0n && typeof treasury === "string" ? { fee: { cap: feeRaw, asset: feeAsset, treasury } } : {},
13869
+ curve: () => registeredCurveFor(rpc(), token)
13870
+ });
12983
13871
  const run = await runSequencedLegs(ctx, key, {
12984
13872
  wallet,
12985
13873
  first: sequenced,
@@ -12989,6 +13877,7 @@ async function hoodSwap(ctx, args) {
12989
13877
  clientId: id,
12990
13878
  kind,
12991
13879
  allowedLegs: HOOD_TRADE_LEGS,
13880
+ checkLeg,
12992
13881
  onLanded: async (_leg) => {
12993
13882
  const notice = await recordTradedToken(ctx, wallet.address, toChecksumAddress(recorded));
12994
13883
  if (notice)
@@ -13141,6 +14030,13 @@ async function bridgeSwap(ctx, args) {
13141
14030
  const problem = await tradingRead(ctx, reader, () => relaySolanaDepositProblem(transaction, wallet.address, reader.rpc));
13142
14031
  if (problem)
13143
14032
  throw refused(problem);
14033
+ const spent = await tradingRead(ctx, reader, () => solanaSpendProblem(transaction, wallet.address, reader.rpc, {
14034
+ kind: "swap",
14035
+ spend: from === "SOL" ? "SOL" : BASES[from]?.mint ?? from,
14036
+ maxSpendRaw: BigInt(amountRaw)
14037
+ }));
14038
+ if (spent)
14039
+ throw refused(spent);
13144
14040
  }
13145
14041
  const outDecimals = BRIDGE_ASSETS[to].decimals;
13146
14042
  const minimumRaw = (BigInt(data.expectedOutRaw) * BigInt(1e4 - args.slippage) / 10000n).toString();
@@ -13253,10 +14149,12 @@ var init_swap = __esm(() => {
13253
14149
  init_args();
13254
14150
  init_bridge();
13255
14151
  init_evm_lite();
14152
+ init_hood_legs();
13256
14153
  init_profiles();
13257
14154
  init_render();
13258
14155
  init_solana_endpoint();
13259
14156
  init_solana_lite();
14157
+ init_solana_verify();
13260
14158
  init_trading();
13261
14159
  settlementSchema = exports_external.object({
13262
14160
  state: exports_external.enum(["settled", "pending", "failed", "uncertain"]),
@@ -13596,6 +14494,7 @@ var KEYS_PATH = "/api/v1/agent/keys", ACCESS_LEVELS, NO_DEVICE_TOKEN;
13596
14494
  var init_keys = __esm(() => {
13597
14495
  init_agent_key_access();
13598
14496
  init_args();
14497
+ init_client();
13599
14498
  init_deps();
13600
14499
  init_profiles();
13601
14500
  init_render();
@@ -16293,6 +17192,7 @@ async function runRoleCheck(ctx, rpc, addresses) {
16293
17192
  }
16294
17193
  var SENTENCE_PREFIX = "You are about to accept ", CONFIRM_WORD = "confirm";
16295
17194
  var init_promote_support = __esm(() => {
17195
+ init_client();
16296
17196
  init_keys();
16297
17197
  init_deps();
16298
17198
  init_profiles();
@@ -16315,11 +17215,11 @@ function parseCloseArtifact(body) {
16315
17215
  return;
16316
17216
  return { transaction, accountKeys };
16317
17217
  }
16318
- function isTokenProgram(owner) {
17218
+ function isTokenProgram2(owner) {
16319
17219
  return owner === TOKEN_PROGRAM_ID || owner === TOKEN_2022_PROGRAM_ID;
16320
17220
  }
16321
17221
  function isMintAccount(view) {
16322
- if (view === null || !isTokenProgram(view.owner))
17222
+ if (view === null || !isTokenProgram2(view.owner))
16323
17223
  return false;
16324
17224
  if (view.data.length === MINT_BASE_SIZE)
16325
17225
  return true;
@@ -16383,8 +17283,8 @@ async function verifyCloseArtifact(input) {
16383
17283
  case COMPUTE_BUDGET_PROGRAM_ID:
16384
17284
  break;
16385
17285
  case ASSOCIATED_TOKEN_PROGRAM_ID: {
16386
- const kind = ix.data.length === 0 ? ATA_IX_CREATE : ix.data.length === 1 ? ix.data[0] : -1;
16387
- if (kind !== ATA_IX_CREATE && kind !== ATA_IX_CREATE_IDEMPOTENT)
17286
+ const kind = ix.data.length === 0 ? ATA_IX_CREATE2 : ix.data.length === 1 ? ix.data[0] : -1;
17287
+ if (kind !== ATA_IX_CREATE2 && kind !== ATA_IX_CREATE_IDEMPOTENT2)
16388
17288
  return refuse2(`instruction ${i} is an Associated Token instruction other than Create, which a close never needs`);
16389
17289
  if (account(0) !== tee || !allowedOwner(account(2) ?? ""))
16390
17290
  return refuse2(`instruction ${i} creates a token account for ${account(2) ?? "?"}, not the TEE wallet or its vault`);
@@ -16392,7 +17292,7 @@ async function verifyCloseArtifact(input) {
16392
17292
  }
16393
17293
  case TOKEN_PROGRAM_ID:
16394
17294
  case TOKEN_2022_PROGRAM_ID: {
16395
- if (ix.data[0] !== TOKEN_IX_CLOSE_ACCOUNT || ix.data.length !== 1)
17295
+ if (ix.data[0] !== TOKEN_IX_CLOSE_ACCOUNT2 || ix.data.length !== 1)
16396
17296
  return refuse2(`instruction ${i} is a token instruction other than CloseAccount, which a close never issues at the top level`);
16397
17297
  if (account(1) !== tee || account(2) !== tee)
16398
17298
  return refuse2(`instruction ${i} closes a token account to ${account(1) ?? "?"}, not the TEE wallet`);
@@ -16446,9 +17346,9 @@ async function verifyCloseArtifact(input) {
16446
17346
  if (before !== null) {
16447
17347
  const controlled = tokenBalanceOf(before);
16448
17348
  if (controlled !== undefined && allowedOwner(controlled.owner)) {
16449
- if (after.data.length < TOKEN_ACCOUNT_SIZE2)
17349
+ if (after.data.length < TOKEN_ACCOUNT_SIZE)
16450
17350
  return refuse2(`token account ${address} would no longer decode as a token account`);
16451
- for (const [start, end, what] of CONTROL_RANGES) {
17351
+ for (const [start, end, what] of TOKEN_CONTROL_RANGES) {
16452
17352
  if (!bytesEqual2(before.data.subarray(start, end), after.data.subarray(start, end)))
16453
17353
  return refuse2(`token account ${address} would change its ${what}, which a close never does`);
16454
17354
  }
@@ -16488,7 +17388,7 @@ async function verifyCloseArtifact(input) {
16488
17388
  continue;
16489
17389
  return refuse2(`writable account ${key} does not exist and is not a TEE or vault associated token account for a mint this transaction names`);
16490
17390
  }
16491
- if (isTokenProgram(view.owner)) {
17391
+ if (isTokenProgram2(view.owner)) {
16492
17392
  if (key === nftMint)
16493
17393
  continue;
16494
17394
  const balance = tokenBalanceOf(view);
@@ -16548,7 +17448,7 @@ function describeClose(input) {
16548
17448
  lines.push("note the simulation is evidence, not a guarantee: a program can behave differently once signed");
16549
17449
  return lines;
16550
17450
  }
16551
- var DAMM_V2_PROGRAM_ID = "cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG", CLOSE_PROGRAM_ALLOWLIST, DAMM_POSITION_CLOSE_UNAVAILABLE = "DAMM_POSITION_CLOSE_UNAVAILABLE", DAMM_CLOSE_TRANSACTION_REFUSED = "DAMM_CLOSE_TRANSACTION_REFUSED", MINT_BASE_SIZE = 82, ACCOUNT_TYPE_OFFSET = 165, ACCOUNT_TYPE_MINT = 1, MULTISIG_SIZE = 355, TOKEN_ACCOUNT_SIZE2 = 165, TOKEN_ACCOUNT_STATE_INITIALIZED = 1, CONTROL_RANGES, TOKEN_IX_CLOSE_ACCOUNT = 9, ATA_IX_CREATE = 0, ATA_IX_CREATE_IDEMPOTENT = 1;
17451
+ var DAMM_V2_PROGRAM_ID = "cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG", CLOSE_PROGRAM_ALLOWLIST, DAMM_POSITION_CLOSE_UNAVAILABLE = "DAMM_POSITION_CLOSE_UNAVAILABLE", DAMM_CLOSE_TRANSACTION_REFUSED = "DAMM_CLOSE_TRANSACTION_REFUSED", MINT_BASE_SIZE = 82, ACCOUNT_TYPE_OFFSET = 165, ACCOUNT_TYPE_MINT = 1, MULTISIG_SIZE = 355, TOKEN_ACCOUNT_STATE_INITIALIZED = 1, TOKEN_IX_CLOSE_ACCOUNT2 = 9, ATA_IX_CREATE2 = 0, ATA_IX_CREATE_IDEMPOTENT2 = 1;
16552
17452
  var init_lp_close = __esm(() => {
16553
17453
  init_solana_alt();
16554
17454
  init_solana_lite();
@@ -16560,14 +17460,6 @@ var init_lp_close = __esm(() => {
16560
17460
  COMPUTE_BUDGET_PROGRAM_ID,
16561
17461
  SYSTEM_PROGRAM_ID
16562
17462
  ]);
16563
- CONTROL_RANGES = [
16564
- [0, 32, "mint"],
16565
- [32, 64, "authority"],
16566
- [72, 108, "delegate"],
16567
- [108, 109, "state"],
16568
- [121, 129, "delegated amount"],
16569
- [129, 165, "close authority"]
16570
- ];
16571
17463
  });
16572
17464
 
16573
17465
  // src/sweep-pending.ts
@@ -27581,14 +28473,14 @@ function availabilityText(envelope, facts) {
27581
28473
  return availability.state === "available" ? availabilityLabel(availability) : `${availabilityLabel(availability)}: ${availability.reason}`;
27582
28474
  }
27583
28475
  async function chooseFactor(ctx, vaultId, allEnvelopes, facts, requested, opts = {}) {
27584
- const among = opts.among;
27585
- const envelopes = among === undefined ? allEnvelopes : allEnvelopes.filter((envelope) => among.envelopeIds.includes(envelope.id));
28476
+ const among2 = opts.among;
28477
+ const envelopes = among2 === undefined ? allEnvelopes : allEnvelopes.filter((envelope) => among2.envelopeIds.includes(envelope.id));
27586
28478
  const hasOtherFactor = allEnvelopes.some((envelope) => !isPassphraseEnvelope(envelope));
27587
28479
  let flag = requested;
27588
- if (among !== undefined && flag !== undefined && flag !== "passphrase") {
28480
+ if (among2 !== undefined && flag !== undefined && flag !== "passphrase") {
27589
28481
  const namesExcluded = envelopes.every((envelope) => !matchesFlag(envelope, flag)) && allEnvelopes.some((envelope) => matchesFlag(envelope, flag));
27590
28482
  if (namesExcluded) {
27591
- ctx.deps.stderr.write(`--factor ${flag} is not used here: ${among.excludedBecause}
28483
+ ctx.deps.stderr.write(`--factor ${flag} is not used here: ${among2.excludedBecause}
27592
28484
  `);
27593
28485
  flag = undefined;
27594
28486
  }
@@ -27615,8 +28507,8 @@ async function chooseFactor(ctx, vaultId, allEnvelopes, facts, requested, opts =
27615
28507
  }
27616
28508
  if (!hasOtherFactor)
27617
28509
  return { kind: "passphrase" };
27618
- if (among !== undefined)
27619
- return { kind: "passphrase", onlyBecause: among.because(unusable()) };
28510
+ if (among2 !== undefined)
28511
+ return { kind: "passphrase", onlyBecause: among2.because(unusable()) };
27620
28512
  const detail = unusable().map((entry) => `${entry.id} ${entry.word}: ${entry.availability}`).join("; ");
27621
28513
  return {
27622
28514
  kind: "passphrase",
@@ -28072,7 +28964,9 @@ async function completeLinkedWalletRead(ctx, apiKey) {
28072
28964
  };
28073
28965
  }
28074
28966
  var MAX_PAGES = 100, PAGE_LIMIT = 100;
28075
- var init_linked_wallets = () => {};
28967
+ var init_linked_wallets = __esm(() => {
28968
+ init_client();
28969
+ });
28076
28970
 
28077
28971
  // src/vault/reconcile-grant.ts
28078
28972
  async function reconcileGrant(ctx, entry, opts = {}) {
@@ -28254,6 +29148,7 @@ function sameWalletAddress(a, b) {
28254
29148
  return a === b;
28255
29149
  }
28256
29150
  var init_reconcile_grant = __esm(() => {
29151
+ init_client();
28257
29152
  init_deps();
28258
29153
  init_errors2();
28259
29154
  init_format();
@@ -31222,6 +32117,7 @@ async function runImportFlow(params) {
31222
32117
  }
31223
32118
  var TEE_PROFILE = "ember-tee";
31224
32119
  var init_wallet_import_flow = __esm(() => {
32120
+ init_client();
31225
32121
  init_secret_store();
31226
32122
  init_wallet_import();
31227
32123
  });
@@ -31402,12 +32298,12 @@ async function readEvmHoldings(rpc, address) {
31402
32298
  ]);
31403
32299
  return { eth, usdg, weth };
31404
32300
  }
31405
- function holdingsLines(address, holdings, observedAt) {
32301
+ function holdingsLines(address, holdings2, observedAt) {
31406
32302
  return [
31407
32303
  `Holdings at ${address} on Hood (observed ${observedAt}):`,
31408
- ` ETH ${formatUnits(holdings.eth, NATIVE_DECIMALS)} (${holdings.eth} wei)`,
31409
- ` USDG ${formatUnits(holdings.usdg, HOOD_USDG_DECIMALS)} (${holdings.usdg} raw)`,
31410
- ` WETH ${formatUnits(holdings.weth, NATIVE_DECIMALS)} (${holdings.weth} raw)`,
32304
+ ` ETH ${formatUnits(holdings2.eth, NATIVE_DECIMALS)} (${holdings2.eth} wei)`,
32305
+ ` USDG ${formatUnits(holdings2.usdg, HOOD_USDG_DECIMALS)} (${holdings2.usdg} raw)`,
32306
+ ` WETH ${formatUnits(holdings2.weth, NATIVE_DECIMALS)} (${holdings2.weth} raw)`,
31411
32307
  " Other tokens are not listed: an EVM RPC has no token-account listing."
31412
32308
  ];
31413
32309
  }
@@ -31528,6 +32424,7 @@ function assertWalletLockFree(entry, lock, now) {
31528
32424
  }
31529
32425
  var init_evm_tee = __esm(() => {
31530
32426
  init_bridge();
32427
+ init_client();
31531
32428
  init_deps();
31532
32429
  init_evm_lite();
31533
32430
  init_errors2();
@@ -32347,6 +33244,7 @@ var TEE_PROFILES, LINKED_WALLETS_PAGE_LIMIT = 100, LINKED_WALLETS_PAGE_CAP = 25,
32347
33244
  var init_wallets = __esm(() => {
32348
33245
  init_esm();
32349
33246
  init_args();
33247
+ init_client();
32350
33248
  init_deps();
32351
33249
  init_key_signers();
32352
33250
  init_profiles();
@@ -33218,6 +34116,7 @@ var MAX_TOKEN_TRANSFER_GAS = 1000000n;
33218
34116
  var init_tee_evm = __esm(() => {
33219
34117
  init_args();
33220
34118
  init_bridge();
34119
+ init_client();
33221
34120
  init_deps();
33222
34121
  init_evm_lite();
33223
34122
  init_profiles();
@@ -33433,6 +34332,7 @@ async function teeStatusEvm(ctx, parsed, address) {
33433
34332
  }
33434
34333
  var GAS_LOW_RESERVE_MULTIPLE = 2n;
33435
34334
  var init_tee_status_evm = __esm(() => {
34335
+ init_client();
33436
34336
  init_deps();
33437
34337
  init_evm_lite();
33438
34338
  init_render();
@@ -35294,6 +36194,7 @@ var init_tee = __esm(() => {
35294
36194
  init_esm();
35295
36195
  init_args();
35296
36196
  init_bridge();
36197
+ init_client();
35297
36198
  init_deps();
35298
36199
  init_lp_close();
35299
36200
  init_profiles();
@@ -35805,6 +36706,7 @@ async function teeRebinds(args, ctx) {
35805
36706
  var REBIND_PATH = "/api/v1/agent/tee-wallets/rebind", REBINDS_PATH = "/api/v1/agent/tee-wallets/rebinds", KEYS_PATH2 = "/api/v1/agent/keys", USAGE_REBIND = "Usage: candle tee rebind <wallet...> --to-key <prefix|label> [--label-prefix <p>] [--json]", USAGE_REBINDS = "Usage: candle tee rebinds [wallet] [--json]", DEVICE_TOKEN_REQUIRED, KEY_PREFIX_RE2, REBIND_NOTHING_PINNED = "Nothing was signed, nothing moved and nothing was pinned.", RELAY_SIGNER_LINE = "The relay signer does not move: trade these wallets from the machine that promoted them.", PORTAL_REVOKE_LINE = "A device token cannot revoke devices: revoke a device you do not recognise from the portal, with a signed-in session.";
35806
36707
  var init_tee_rebind = __esm(() => {
35807
36708
  init_args();
36709
+ init_client();
35808
36710
  init_deps();
35809
36711
  init_key_signers();
35810
36712
  init_profiles();
@@ -36056,6 +36958,7 @@ function writeChanged(ctx, result, command) {
36056
36958
  var KEYS_PATH3 = "/api/v1/agent/keys", EMBEDDED_WALLET_CHOICES, USAGE_UPDATE = "Usage: candle keys update <prefix|label> --embedded-wallet <allow|deny> [--yes] [--json]", USAGE_SELF = "Usage: candle keys self embedded-wallet deny [--json]", ALLOW_REQUIRES_TTY, NO_API_KEY, SELF_CANNOT_ALLOW;
36057
36959
  var init_keys_embedded_wallet = __esm(() => {
36058
36960
  init_args();
36961
+ init_client();
36059
36962
  init_deps();
36060
36963
  init_profiles();
36061
36964
  init_render();
@@ -61108,13 +62011,52 @@ function assertTransportSecurity(apiUrl, env) {
61108
62011
  return;
61109
62012
  throw new Error(`Refusing to send credentials in the clear to ${parsed.origin}. Set CANDLE_API_URL to an https:// ` + (isPrivateHost2(parsed.hostname) ? "URL, or set CANDLE_ALLOW_INSECURE_HTTP=1 if this really is a trusted local endpoint." : "URL. CANDLE_ALLOW_INSECURE_HTTP does not apply here: it covers private networks only, " + "and this is a public address."));
61110
62013
  }
62014
+ function keyedRedirectGuard(doFetch) {
62015
+ return async (url, init) => {
62016
+ if (!init?.headers || !new Headers(init.headers).has("x-api-key"))
62017
+ return doFetch(url, init);
62018
+ let current = url;
62019
+ let next = { ...init, redirect: "manual" };
62020
+ for (let hops = 0;; hops++) {
62021
+ const response = await doFetch(current, next);
62022
+ const headers = response.headers;
62023
+ const location = REDIRECT_STATUSES2.has(response.status) ? headers?.get?.("location") : null;
62024
+ if (!location)
62025
+ return response;
62026
+ const from = new URL(current);
62027
+ const target = new URL(location, from);
62028
+ if (target.origin !== from.origin)
62029
+ throw new CrossOriginRedirectError2(from.origin, target.origin);
62030
+ if (hops >= MAX_REDIRECTS2)
62031
+ throw new Error(`More than ${MAX_REDIRECTS2} redirects from ${url}`);
62032
+ const method = (next.method ?? "GET").toUpperCase();
62033
+ if (response.status === 303 && method !== "GET" && method !== "HEAD" || (response.status === 301 || response.status === 302) && method === "POST") {
62034
+ next = { ...next, method: "GET", body: undefined };
62035
+ }
62036
+ current = target.href;
62037
+ }
62038
+ };
62039
+ }
61111
62040
  function resolveConfig(env = process.env) {
61112
62041
  const apiUrl = env.CANDLE_API_URL?.trim() || DEFAULT_API_URL2;
61113
62042
  assertTransportSecurity(apiUrl, env);
61114
62043
  const apiKey = env.CANDLE_AGENT_API_KEY?.trim() || env.CANDLE_API_KEY?.trim();
61115
62044
  return apiKey ? { apiUrl, apiKey } : { apiUrl };
61116
62045
  }
61117
- var DEFAULT_API_URL2 = "https://api.alpha.candle.tv";
62046
+ var DEFAULT_API_URL2 = "https://api.alpha.candle.tv", REDIRECT_STATUSES2, MAX_REDIRECTS2 = 5, CrossOriginRedirectError2;
62047
+ var init_client2 = __esm(() => {
62048
+ REDIRECT_STATUSES2 = new Set([301, 302, 303, 307, 308]);
62049
+ CrossOriginRedirectError2 = class CrossOriginRedirectError2 extends Error {
62050
+ from;
62051
+ to;
62052
+ constructor(from, to) {
62053
+ super(`The API at ${from} redirected to ${to}, another origin. Not followed, so the API key was not sent there.`);
62054
+ this.from = from;
62055
+ this.to = to;
62056
+ this.name = "CrossOriginRedirectError";
62057
+ }
62058
+ };
62059
+ });
61118
62060
 
61119
62061
  // ../mcp/src/convert.ts
61120
62062
  function defaultQuoteId(chain2) {
@@ -61162,7 +62104,7 @@ var init_convert = __esm(() => {
61162
62104
  });
61163
62105
 
61164
62106
  // ../mcp/src/version.ts
61165
- var SERVER_VERSION = "0.10.16";
62107
+ var SERVER_VERSION = "0.10.17";
61166
62108
 
61167
62109
  // ../mcp/src/update-notice.ts
61168
62110
  function newer(a, b) {
@@ -62136,6 +63078,65 @@ async function hyperliquidCloseOrder2(fetcher, network2, address, params) {
62136
63078
  const size = asked < held ? String(params.size) : String(szi).replace(/^-/, "");
62137
63079
  return { asset, isBuy: signed < 0n, size };
62138
63080
  }
63081
+ async function hyperliquidOrderMarket2(fetcher, network2, address, method, params) {
63082
+ const read = async (body) => {
63083
+ const res = await fetcher(HYPERLIQUID_INFO_URLS2[network2], {
63084
+ method: "POST",
63085
+ headers: { "content-type": "application/json" },
63086
+ body: JSON.stringify(body)
63087
+ });
63088
+ if (!res.ok)
63089
+ throw new Error("Cannot read the market from Hyperliquid");
63090
+ return JSON.parse(await res.text());
63091
+ };
63092
+ let coin = String(params.coin ?? "").toUpperCase();
63093
+ let order;
63094
+ if (method === "modify") {
63095
+ const status = await read({ type: "orderStatus", user: address, oid: params.cloid });
63096
+ const resting = status?.order?.order ?? null;
63097
+ if (!resting || typeof resting.coin !== "string" || decimal2(resting.limitPx) === null)
63098
+ throw new Error("No resting order with this cloid on Hyperliquid");
63099
+ coin = resting.coin.toUpperCase();
63100
+ order = { isBuy: resting.side === "B", limitPx: String(resting.limitPx) };
63101
+ }
63102
+ const [meta2, contexts] = await read({ type: "metaAndAssetCtxs" });
63103
+ const asset = meta2.universe.findIndex((m) => m.name.toUpperCase() === coin);
63104
+ if (asset < 0 || asset >= 1e4)
63105
+ throw new Error("No main-universe market for this coin on Hyperliquid");
63106
+ const context = contexts[asset];
63107
+ const midPx = context?.midPx ?? context?.markPx;
63108
+ if ((decimal2(midPx) ?? 0n) <= 0n)
63109
+ throw new Error("No Hyperliquid price for this coin");
63110
+ return { asset, midPx: String(midPx), ...order ? { order } : {} };
63111
+ }
63112
+ function checkOrderMarket2(action, intent, market) {
63113
+ const list = action.type === "modify" ? [action.order] : action.orders;
63114
+ if (!Array.isArray(list) || list.length === 0)
63115
+ return "missing requested order";
63116
+ if (list.some((o) => !o || o.a !== market.asset))
63117
+ return "order asset is not the requested coin's on Hyperliquid";
63118
+ const first = list[0];
63119
+ const p = intent.params;
63120
+ if (intent.method === "open" && p.price == null) {
63121
+ const requested = p.slippageBps;
63122
+ const slippage2 = typeof requested === "number" && Number.isInteger(requested) && requested > 0 ? requested : HYPERLIQUID_DEFAULT_SLIPPAGE_BPS2;
63123
+ const bound = BigInt(slippage2 + HYPERLIQUID_MID_DRIFT_BPS2);
63124
+ const px = decimal2(first.p) ?? 0n;
63125
+ const mid = decimal2(market.midPx) ?? 0n;
63126
+ const within = first.b === true ? px * 10000n <= mid * (10000n + bound) : px * 10000n >= mid * (10000n - bound);
63127
+ if (mid <= 0n || !within)
63128
+ return "market order price is outside its slippage of Hyperliquid's mid";
63129
+ }
63130
+ if (intent.method === "modify") {
63131
+ if (!market.order)
63132
+ return "no resting order on Hyperliquid to check the modify against";
63133
+ if (first.b !== market.order.isBuy)
63134
+ return "modify changes the resting order's side";
63135
+ if (p.price == null && decimal2(first.p) !== decimal2(market.order.limitPx))
63136
+ return "modify changes the resting order's price";
63137
+ }
63138
+ return null;
63139
+ }
62139
63140
  function verifyPerpsBuild2(build, opts) {
62140
63141
  try {
62141
63142
  const network2 = opts.network ?? "mainnet";
@@ -62163,6 +63164,13 @@ function verifyPerpsBuild2(build, opts) {
62163
63164
  if (!order || order.a !== opts.closeOrder.asset || order.b !== opts.closeOrder.isBuy || decimal2(order.s) !== decimal2(opts.closeOrder.size) || order.r !== true)
62164
63165
  return { ok: false, reason: "close side, asset or size does not match the requested position" };
62165
63166
  }
63167
+ if (opts.market) {
63168
+ if (opts.intent?.method !== "open" && opts.intent?.method !== "modify")
63169
+ return { ok: false, reason: "a market check needs an open or modify intent" };
63170
+ const marketRefusal = checkOrderMarket2(action, opts.intent, opts.market);
63171
+ if (marketRefusal)
63172
+ return { ok: false, reason: marketRefusal };
63173
+ }
62166
63174
  if (typed.primary_type === "Agent") {
62167
63175
  const type = action.type;
62168
63176
  if (typeof type !== "string" || !HYPERLIQUID_ALLOWED_ACTION_TYPES2.includes(type)) {
@@ -62220,7 +63228,7 @@ function hyperliquidExchangeBody2(action, nonce, signature) {
62220
63228
  function hyperliquidRelayBody2(typedData) {
62221
63229
  return { method: "eth_signTypedData_v4", params: { typed_data: typedData } };
62222
63230
  }
62223
- var HYPERLIQUID_ALLOWED_ACTION_TYPES2, HYPERLIQUID_EXCHANGE_URLS2, CANDLE_HYPERLIQUID_BUILDER_ADDRESS2 = null, HYPERLIQUID_MAX_BUILDER_FEE_TENTHS_BPS2 = 100, HYPERLIQUID_MAX_BUILDER_FEE_RATE2 = "0.1%", HYPERLIQUID_SIGNATURE_CHAIN_ID2 = "0xa4b1", ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000", UINT64_MAX2, INT64_MIN2, EVM_ADDRESS_RE2;
63231
+ var HYPERLIQUID_ALLOWED_ACTION_TYPES2, HYPERLIQUID_EXCHANGE_URLS2, HYPERLIQUID_INFO_URLS2, CANDLE_HYPERLIQUID_BUILDER_ADDRESS2 = null, HYPERLIQUID_MAX_BUILDER_FEE_TENTHS_BPS2 = 100, HYPERLIQUID_MAX_BUILDER_FEE_RATE2 = "0.1%", HYPERLIQUID_SIGNATURE_CHAIN_ID2 = "0xa4b1", ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000", UINT64_MAX2, INT64_MIN2, EVM_ADDRESS_RE2, HYPERLIQUID_DEFAULT_SLIPPAGE_BPS2 = 100, HYPERLIQUID_MID_DRIFT_BPS2 = 100;
62224
63232
  var init_hyperliquid = __esm(() => {
62225
63233
  init_sha3();
62226
63234
  HYPERLIQUID_ALLOWED_ACTION_TYPES2 = [
@@ -62234,6 +63242,10 @@ var init_hyperliquid = __esm(() => {
62234
63242
  mainnet: "https://api.hyperliquid.xyz/exchange",
62235
63243
  testnet: "https://api.hyperliquid-testnet.xyz/exchange"
62236
63244
  };
63245
+ HYPERLIQUID_INFO_URLS2 = {
63246
+ mainnet: "https://api.hyperliquid.xyz/info",
63247
+ testnet: "https://api.hyperliquid-testnet.xyz/info"
63248
+ };
62237
63249
  UINT64_MAX2 = (1n << 64n) - 1n;
62238
63250
  INT64_MIN2 = -(1n << 63n);
62239
63251
  EVM_ADDRESS_RE2 = /^0x[0-9a-fA-F]{40}$/;
@@ -62344,6 +63356,18 @@ async function executePerps(tool, args, cfg, env, fetch2) {
62344
63356
  if (!closeCheck.ok)
62345
63357
  return fail3("PERPS_BUILD_REFUSED", `Refused to sign this build: ${closeCheck.reason}. Nothing was signed.`);
62346
63358
  }
63359
+ if (method === "open") {
63360
+ const market = await hyperliquidOrderMarket2(fetch2, network2, row.address, method, fields);
63361
+ const marketCheck = verifyPerpsBuild2(build, {
63362
+ builder,
63363
+ network: network2,
63364
+ address: row.address,
63365
+ intent: { method, params: fields },
63366
+ market
63367
+ });
63368
+ if (!marketCheck.ok)
63369
+ return fail3("PERPS_BUILD_REFUSED", `Refused to sign this build: ${marketCheck.reason}. Nothing was signed.`);
63370
+ }
62347
63371
  if (!willSubmit || signerPem === null)
62348
63372
  return { text: JSON.stringify({ ...build, signed: false, submitted: false }) };
62349
63373
  if (!appId || !row.privyWalletId)
@@ -62705,7 +63729,7 @@ var init_plans = __esm(() => {
62705
63729
  createLinkedWallets: "Create linked wallets",
62706
63730
  importLinkedWallets: "Import linked wallets",
62707
63731
  limitOrders: "Limit orders",
62708
- quant: "Quant on Telegram"
63732
+ quant: "Quant deep analyses (Max)"
62709
63733
  };
62710
63734
  PLAN_LABELS2 = { free: "Free", believer: "Believer", pro: "Pro", max: "Max" };
62711
63735
  });
@@ -63080,7 +64104,7 @@ function registerToolSubset(server, options) {
63080
64104
  const allowed = new Set(options.tools);
63081
64105
  const getConfig = options.getConfig;
63082
64106
  const env = options.env ?? {};
63083
- const doFetch = options.fetch ?? ((url, init) => fetch(url, init));
64107
+ const doFetch = keyedRedirectGuard(options.fetch ?? ((url, init) => fetch(url, init)));
63084
64108
  const register = (name2, definition, handler) => {
63085
64109
  if (!allowed.has(name2))
63086
64110
  return;
@@ -63241,7 +64265,7 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
63241
64265
  }, async (args) => callAndRelay("candle_get_fees", args, getConfig(), doFetch));
63242
64266
  register("candle_claim_fees", {
63243
64267
  title: "Claim a token's creator fees",
63244
- description: "Collect one token's creator fees and pay them to the wallet fixed at launch. There is no destination " + "argument and the tool cannot send funds anywhere else. Candle signs with the embedded wallet, which " + "pays the network fee. Needs launch:write, and the server keeps it off until it is enabled (LAUNCH_DISABLED). " + "Call it with dryRun: true first; a real claim moves real funds into the recipient wallet, which may be " + "the wallet this agent trades from. Not idempotent by id: if the result is lost, read candle_get_fees " + "before claiming again.",
64268
+ description: "Collect one token's creator fees and pay them to the wallet fixed at launch. There is no destination " + "argument and the tool cannot send funds anywhere else. Candle signs with the embedded wallet, which " + "pays the network fee. Needs launch:write. " + "Call it with dryRun: true first; a real claim moves real funds into the recipient wallet, which may be " + "the wallet this agent trades from. Not idempotent by id: if the result is lost, read candle_get_fees " + "before claiming again.",
63245
64269
  inputSchema: claimFeesShape
63246
64270
  }, async (args) => callAndRelay("candle_claim_fees", args, getConfig(), doFetch));
63247
64271
  register("candle_get_wallets", {
@@ -63407,7 +64431,7 @@ async function registerEligibleProfileTool(server, env = process.env, doFetch =
63407
64431
  let eligible = false;
63408
64432
  if (cfg.apiKey && wanted.length > 0) {
63409
64433
  try {
63410
- const res = await doFetch(`${cfg.apiUrl}/api/v1/agent/keys/self/public-profile`, {
64434
+ const res = await keyedRedirectGuard(doFetch)(`${cfg.apiUrl}/api/v1/agent/keys/self/public-profile`, {
63411
64435
  method: "GET",
63412
64436
  headers: { "x-api-key": cfg.apiKey },
63413
64437
  signal: AbortSignal.timeout(15000)
@@ -63429,9 +64453,10 @@ async function registerEligibleProfileTool(server, env = process.env, doFetch =
63429
64453
  }
63430
64454
  return eligible;
63431
64455
  }
63432
- var VAMP_CANDIDATES_DESCRIPTION, CUSTOM_CURVE_RULE, localProfileTools, STAKING_DISCLOSURE = "No instant unstake. Read pool.withdrawalTimelockSeconds for the wait. Solana rewards accrue to the holding wallet. Outside the account's main wallet, sCNDL earns no Hood staking rewards; Pro eligibility follows the deployment's tier mode (main-wallet mode excludes it). CNDL in a pending withdrawal counts toward nothing for Pro.", stakingWriteShape, TOOL_NAMES, PROFILE_EDIT_TOOL_NAMES, isProfileEditTool = (name2) => PROFILE_EDIT_TOOL_NAMES.includes(name2), OPERATION_NOT_FOUND_RULE, launchTokenShape, getMarketShape, tokenForensicsShape, FEED_LIMIT_MESSAGE = "limit must be a whole number from 1 to 200", feedLimit, getFeedShape, reportActivityShape, getAgentProfileShape, listAgentsShape, createPostShape, deletePostShape, listAgentPostsShape, getOperationShape, vampCandidatesShape, getFeesShape, claimFeesShape, resolveTokenShape, profileWalletsShape, profilePnlShape, profileTradesShape, setProfileWalletsShape, swapShape, tradeShape, _rawBuyAmount, seedableLaunchShape, seedShape, launchAndSeedShape, localImageShape, localLaunchTokenShape, localLaunchAndSeedShape, IMAGE_PATH_DISCLOSURE, transferShape, sweepShape;
64456
+ var VAMP_CANDIDATES_DESCRIPTION, CUSTOM_CURVE_RULE, 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, PROFILE_EDIT_TOOL_NAMES, isProfileEditTool = (name2) => PROFILE_EDIT_TOOL_NAMES.includes(name2), OPERATION_NOT_FOUND_RULE, launchTokenShape, getMarketShape, tokenForensicsShape, FEED_LIMIT_MESSAGE = "limit must be a whole number from 1 to 200", feedLimit, getFeedShape, reportActivityShape, getAgentProfileShape, listAgentsShape, createPostShape, deletePostShape, listAgentPostsShape, getOperationShape, vampCandidatesShape, getFeesShape, claimFeesShape, resolveTokenShape, profileWalletsShape, profilePnlShape, profileTradesShape, setProfileWalletsShape, swapShape, tradeShape, _rawBuyAmount, seedableLaunchShape, seedShape, launchAndSeedShape, localImageShape, localLaunchTokenShape, localLaunchAndSeedShape, IMAGE_PATH_DISCLOSURE, transferShape, sweepShape;
63433
64457
  var init_tools = __esm(() => {
63434
64458
  init_zod();
64459
+ init_client2();
63435
64460
  init_convert();
63436
64461
  init_image();
63437
64462
  init_market_disclosure();
@@ -63440,7 +64465,7 @@ var init_tools = __esm(() => {
63440
64465
  init_plans();
63441
64466
  init_update_notice();
63442
64467
  VAMP_CANDIDATES_DESCRIPTION = "Lists Solana tokens that are close to graduating or graduated in the last 6 hours whose ticker other tokens " + "are copying, most-copied in the last 24 hours first. Each entry carries a launch draft (name, ticker, image URL) " + "for `candle_launch_token` or `candle_launch_and_seed`; draft notes identify fields you must supply or adjust. " + "Upload your own image through the agent image upload when the image is unavailable. Use `dryRun: true` first. " + "Counts exclude the candidate itself and cover both Solana and Hood launches at the venues returned in " + "`coverage`, not Candle's own. `coverage.since` is the returned Solana coverage date; Hood history may differ.";
63443
- CUSTOM_CURVE_RULE = 'mode "exp-custom" with curve { virtualQuote, threshold } launches a Solana curve with your own V (the virtual ' + "quote it starts with) and G (the quote it raises before graduating), in whole SOL or CNDL as decimal strings, " + "accepted when V is 1 to 100 SOL and G 1 to 250 SOL (66,000 to 6,600,000 and 66,000 to 16,500,000 CNDL), when " + "every price up to 20 x G stays under the ceiling (V + 20G)^2 < V x X at this stakerAllocationBps, when neither " + "is finer than the quote's decimals, and when the dev buy is at most 10% of G; a curve past a limit is " + "CURVE_OUT_OF_RANGE, and at the ceiling it carries maxThresholdRaw, the largest G that fits. " + "Dry-run it first (dryRun: true): the dry run spends nothing and returns curveTerms, the market cap at start and " + "at graduation and the supply sold by graduation. Only where the API has EXP_CURVES and EXP_CUSTOM_CURVES on; " + "elsewhere exp-custom is refused as an unknown mode.";
64468
+ CUSTOM_CURVE_RULE = 'mode "exp-custom" with curve { virtualQuote, threshold } launches a Solana curve with your own V (the virtual ' + "quote it starts with) and G (the quote it raises before graduating), in whole SOL or CNDL as decimal strings, " + "accepted when V is 1 to 100 SOL and G 1 to 250 SOL (66,000 to 6,600,000 and 66,000 to 16,500,000 CNDL), when " + "every price up to 20 x G stays under the ceiling (V + 20G)^2 < V x X at this stakerAllocationBps, when neither " + "is finer than the quote's decimals, and when the dev buy is at most 10% of G; a curve past a limit is " + "CURVE_OUT_OF_RANGE, and at the ceiling it carries maxThresholdRaw, the largest G that fits. " + "Dry-run it first (dryRun: true): the dry run spends nothing and returns curveTerms, the market cap at start and " + "at graduation and the supply sold by graduation. Only where the API has EXP_CUSTOM_CURVES on; " + "elsewhere exp-custom is refused as an unknown mode.";
63444
64469
  localProfileTools = new WeakMap;
63445
64470
  stakingWriteShape = {
63446
64471
  payer: exports_external.enum(["main", "linked"]).optional().describe("main is the embedded wallet; linked names a linked or TEE wallet"),
@@ -63739,6 +64764,7 @@ Full reference, error catalogue and end-to-end recipes: https://docs.candle.tv
63739
64764
  });
63740
64765
 
63741
64766
  // src/index.ts
64767
+ init_client();
63742
64768
  import { spawn as spawn3 } from "node:child_process";
63743
64769
  import { realpathSync } from "node:fs";
63744
64770
  import { chmod as chmod8, readFile as readFile10, realpath as realpath2, rename as rename6, unlink, writeFile as writeFile7 } from "node:fs/promises";
@@ -63747,6 +64773,7 @@ import { pathToFileURL } from "node:url";
63747
64773
 
63748
64774
  // src/commands/agent-post.ts
63749
64775
  init_args();
64776
+ init_client();
63750
64777
  init_render();
63751
64778
  init_trading();
63752
64779
  init_swap();
@@ -63842,6 +64869,7 @@ ${USAGE}`);
63842
64869
 
63843
64870
  // src/commands/agent-profile.ts
63844
64871
  init_args();
64872
+ init_client();
63845
64873
  init_render();
63846
64874
  init_trading();
63847
64875
  init_swap();
@@ -63907,6 +64935,7 @@ import { homedir as homedir3 } from "node:os";
63907
64935
  import { join as join8 } from "node:path";
63908
64936
 
63909
64937
  // src/account.ts
64938
+ init_client();
63910
64939
  async function fetchAccount(deps, apiUrl, apiKey) {
63911
64940
  const identity = await apiRequest("/api/v1/agent/wallets/embedded", {
63912
64941
  auth: "key",
@@ -63927,6 +64956,7 @@ init_agent_key_access();
63927
64956
  init_args();
63928
64957
 
63929
64958
  // src/checks.ts
64959
+ init_client();
63930
64960
  init_render();
63931
64961
  async function runLiveCheck(params) {
63932
64962
  const { deps, apiUrl, path, auth, credential, check, passDetail } = params;
@@ -63941,13 +64971,14 @@ async function runLiveCheck(params) {
63941
64971
  }
63942
64972
 
63943
64973
  // src/commands/auth.ts
64974
+ init_client();
63944
64975
  init_deps();
63945
64976
  init_profiles();
63946
64977
  init_render();
63947
64978
  init_secret_store();
63948
64979
 
63949
64980
  // src/version.ts
63950
- var CLI_VERSION = "0.11.28";
64981
+ var CLI_VERSION = "0.11.29";
63951
64982
 
63952
64983
  // src/commands/auth.ts
63953
64984
  init_keys_embedded_wallet();
@@ -64487,7 +65518,7 @@ var HELP = {
64487
65518
  stake: {
64488
65519
  group: "Trade",
64489
65520
  summary: "Stake CNDL, start and finish withdrawals, claim Solana staking rewards",
64490
- description: "Solana only. MOVES REAL FUNDS from the embedded wallet (default) or a named TEE wallet whose relay signer is on this machine. add takes decimal CNDL; remove takes decimal sCNDL. No instant unstake: finish waits for the pool's withdrawalTimelockSeconds, shown by candle stake. Solana rewards accrue to the holding wallet; outside the main wallet sCNDL earns no Hood rewards, and Pro follows the API's tier mode (main-wallet mode excludes it). Pending withdrawals count toward nothing for Pro. Writes require swap:write and an enabled agent staking rail. Reuse --client-stake-id on retry: a rerun posts the saved signed transaction to submit again without re-signing. The server reads the chain for that signature first, so it lands once. The signed transaction is kept in the CLI operations directory for that retry. Ordinary linked wallets use SDK builds and external signing.",
65521
+ description: "Solana only. MOVES REAL FUNDS from the embedded wallet (default) or a named TEE wallet whose relay signer is on this machine. add takes decimal CNDL; remove takes decimal sCNDL. No instant unstake: finish waits for the pool's withdrawalTimelockSeconds, shown by candle stake. Solana rewards accrue to the holding wallet; sCNDL in any eligible account wallet counts toward Pro and earns the account's Hood reward, except a wallet another account also claims, wallets past the 200-wallet cap, and an account with no EVM wallet. Pending withdrawals count toward nothing for Pro. Writes require swap:write and an enabled agent staking rail. Reuse --client-stake-id on retry: a rerun posts the saved signed transaction to submit again without re-signing. The server reads the chain for that signature first, so it lands once. The signed transaction is kept in the CLI operations directory for that retry. Ordinary linked wallets use SDK builds and external signing.",
64491
65522
  usage: [
64492
65523
  "candle stake [--wallet <wallet>]",
64493
65524
  "candle stake add <amount>",
@@ -64779,7 +65810,7 @@ var HELP = {
64779
65810
  fees: {
64780
65811
  group: "Account",
64781
65812
  summary: "Creator fees your launched tokens can pay, and the claim",
64782
- description: "Lists the tokens launched by this account's wallets and what each could pay now, from direct chain reads, with the reason a token cannot be claimed. `fees claim` collects one token's creator fees and pays them to the wallet fixed at launch: there is no destination option, Candle signs with the embedded wallet, and that wallet pays the network fee. The key needs launch:write to claim and account:read to list. The server keeps the claim off until it is enabled, and while off even --dry-run answers LAUNCH_DISABLED. A Phantom-signup account's Solana fees are credited to the external wallet, which Candle cannot sign for, so its agent cannot claim them.",
65813
+ description: "Lists the tokens launched by this account's wallets and what each could pay now, from direct chain reads, with the reason a token cannot be claimed. `fees claim` collects one token's creator fees and pays them to the wallet fixed at launch: there is no destination option, Candle signs with the embedded wallet, and that wallet pays the network fee. The key needs launch:write to claim and account:read to list. A Phantom-signup account's Solana fees are credited to the external wallet, which Candle cannot sign for, so its agent cannot claim them.",
64783
65814
  usage: ["candle fees [--cursor <cursor>] [--limit <1-50>]", "candle fees claim <mint> [--dry-run]"],
64784
65815
  rows: [
64785
65816
  {
@@ -65295,10 +66326,10 @@ var HELP = {
65295
66326
  examples: ["candle help", "candle help vault", "candle help completion"]
65296
66327
  }
65297
66328
  };
65298
- function topicFor(word2) {
65299
- if (Object.hasOwn(HELP, word2))
65300
- return HELP[word2];
65301
- return Object.values(HELP).find((topic) => topic.display === word2);
66329
+ function topicFor(word3) {
66330
+ if (Object.hasOwn(HELP, word3))
66331
+ return HELP[word3];
66332
+ return Object.values(HELP).find((topic) => topic.display === word3);
65302
66333
  }
65303
66334
  function displayName(canonical) {
65304
66335
  return HELP[canonical]?.display ?? canonical;
@@ -65332,14 +66363,14 @@ function wrap(text, width) {
65332
66363
  return [text];
65333
66364
  const lines = [];
65334
66365
  let line = "";
65335
- for (const word2 of text.split(" ")) {
66366
+ for (const word3 of text.split(" ")) {
65336
66367
  if (line === "")
65337
- line = word2;
65338
- else if (`${line} ${word2}`.length <= width)
65339
- line = `${line} ${word2}`;
66368
+ line = word3;
66369
+ else if (`${line} ${word3}`.length <= width)
66370
+ line = `${line} ${word3}`;
65340
66371
  else {
65341
66372
  lines.push(line);
65342
- line = word2;
66373
+ line = word3;
65343
66374
  }
65344
66375
  }
65345
66376
  if (line !== "")
@@ -65376,14 +66407,14 @@ function renderTopLevel() {
65376
66407
  " candle help <command> a command's subcommands, flags and examples"
65377
66408
  ];
65378
66409
  const entries = Object.entries(HELP);
65379
- const longest = Math.max(...entries.map(([word2]) => displayName(word2).length));
66410
+ const longest = Math.max(...entries.map(([word3]) => displayName(word3).length));
65380
66411
  const column = 2 + longest + 2;
65381
66412
  for (const group of GROUPS) {
65382
66413
  out.push("", group);
65383
- for (const [word2, topic] of entries) {
66414
+ for (const [word3, topic] of entries) {
65384
66415
  if (topic.group !== group)
65385
66416
  continue;
65386
- out.push(` ${displayName(word2).padEnd(column - 2)}${topic.summary}`);
66417
+ out.push(` ${displayName(word3).padEnd(column - 2)}${topic.summary}`);
65387
66418
  }
65388
66419
  }
65389
66420
  out.push("", "Global flags", ...renderRows(GLOBAL_FLAGS, 28));
@@ -65393,11 +66424,11 @@ function renderTopLevel() {
65393
66424
  `)}
65394
66425
  `;
65395
66426
  }
65396
- function renderTopic(word2) {
65397
- const topic = topicFor(word2);
66427
+ function renderTopic(word3) {
66428
+ const topic = topicFor(word3);
65398
66429
  if (!topic)
65399
66430
  return;
65400
- const canonical = Object.keys(HELP).find((key) => HELP[key] === topic) ?? word2;
66431
+ const canonical = Object.keys(HELP).find((key) => HELP[key] === topic) ?? word3;
65401
66432
  const display = displayName(canonical);
65402
66433
  const out = [wrap(`candle ${display}: ${topic.description}`, WIDTH).join(`
65403
66434
  `)];
@@ -65465,8 +66496,8 @@ function zshScript() {
65465
66496
  " _candle_flags=()",
65466
66497
  ' case "${words[2]}" in'
65467
66498
  ];
65468
- for (const { word: word2, subcommands, flags } of perWord()) {
65469
- lines.push(` ${word2})`);
66499
+ for (const { word: word3, subcommands, flags } of perWord()) {
66500
+ lines.push(` ${word3})`);
65470
66501
  if (subcommands.length > 0)
65471
66502
  lines.push(` _candle_subs=(${subcommands.join(" ")})`);
65472
66503
  if (flags.length > 0)
@@ -65495,8 +66526,8 @@ function bashScript() {
65495
66526
  " fi",
65496
66527
  ' case "${COMP_WORDS[1]}" in'
65497
66528
  ];
65498
- for (const { word: word2, subcommands, flags } of perWord()) {
65499
- lines.push(` ${word2})`);
66529
+ for (const { word: word3, subcommands, flags } of perWord()) {
66530
+ lines.push(` ${word3})`);
65500
66531
  if (subcommands.length > 0)
65501
66532
  lines.push(` candle_subs="${subcommands.join(" ")}"`);
65502
66533
  if (flags.length > 0)
@@ -65516,8 +66547,8 @@ function fishScript() {
65516
66547
  ""
65517
66548
  ];
65518
66549
  for (const [canonical, topic] of Object.entries(HELP)) {
65519
- for (const word2 of topic.display ? [canonical, topic.display] : [canonical]) {
65520
- lines.push(`complete -c candle -n '__fish_use_subcommand' -a '${word2}' -d '${safe(topic.summary)}'`);
66550
+ for (const word3 of topic.display ? [canonical, topic.display] : [canonical]) {
66551
+ lines.push(`complete -c candle -n '__fish_use_subcommand' -a '${word3}' -d '${safe(topic.summary)}'`);
65521
66552
  }
65522
66553
  }
65523
66554
  lines.push("");
@@ -65530,18 +66561,18 @@ function fishScript() {
65530
66561
  }
65531
66562
  }
65532
66563
  }
65533
- for (const { word: word2, subcommands, flags } of perWord()) {
66564
+ for (const { word: word3, subcommands, flags } of perWord()) {
65534
66565
  if (subcommands.length === 0 && flags.length === 0)
65535
66566
  continue;
65536
66567
  lines.push("");
65537
66568
  for (const sub of subcommands) {
65538
- lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${word2}' -a '${sub}'`);
66569
+ lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${word3}' -a '${sub}'`);
65539
66570
  }
65540
66571
  for (const flag of flags) {
65541
66572
  if (flag.startsWith("--")) {
65542
- lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${word2}' -l '${flag.slice(2)}'`);
66573
+ lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${word3}' -l '${flag.slice(2)}'`);
65543
66574
  } else
65544
- lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${word2}' -s '${flag.slice(1)}'`);
66575
+ lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${word3}' -s '${flag.slice(1)}'`);
65545
66576
  }
65546
66577
  }
65547
66578
  lines.push("");
@@ -65580,6 +66611,7 @@ async function completion(args, ctx) {
65580
66611
  // src/commands/doctor.ts
65581
66612
  init_agent_key_access();
65582
66613
  init_args();
66614
+ init_client();
65583
66615
  init_deps();
65584
66616
 
65585
66617
  // src/keychain.ts
@@ -65773,6 +66805,7 @@ init_store();
65773
66805
 
65774
66806
  // src/commands/key-signer.ts
65775
66807
  init_args();
66808
+ init_client();
65776
66809
  init_deps();
65777
66810
  init_key_signers();
65778
66811
  import { generateKeyPairSync as generateKeyPairSync2 } from "node:crypto";
@@ -65781,6 +66814,7 @@ init_render();
65781
66814
  init_secret_store();
65782
66815
 
65783
66816
  // src/vault/promote-to-key.ts
66817
+ init_client();
65784
66818
  init_tee_rebind();
65785
66819
  init_deps();
65786
66820
  init_key_signers();
@@ -70773,14 +71807,14 @@ async function feesClaim(args, ctx) {
70773
71807
 
70774
71808
  // src/commands/help.ts
70775
71809
  async function help(args, ctx) {
70776
- const word2 = args[0];
70777
- if (word2 === undefined) {
71810
+ const word3 = args[0];
71811
+ if (word3 === undefined) {
70778
71812
  ctx.deps.stdout.write(renderTopLevel());
70779
71813
  return 0;
70780
71814
  }
70781
- const topic = renderTopic(word2);
71815
+ const topic = renderTopic(word3);
70782
71816
  if (topic === undefined) {
70783
- ctx.deps.stderr.write(`Unknown command: ${word2}
71817
+ ctx.deps.stderr.write(`Unknown command: ${word3}
70784
71818
  `);
70785
71819
  ctx.deps.stderr.write(renderTopLevel());
70786
71820
  return 1;
@@ -70791,6 +71825,7 @@ async function help(args, ctx) {
70791
71825
 
70792
71826
  // src/commands/image.ts
70793
71827
  init_args();
71828
+ init_client();
70794
71829
  init_deps();
70795
71830
  init_render();
70796
71831
  init_trading();
@@ -70910,6 +71945,7 @@ init_keys();
70910
71945
  // src/commands/keys-access.ts
70911
71946
  init_agent_key_access();
70912
71947
  init_args();
71948
+ init_client();
70913
71949
  init_deps();
70914
71950
  init_profiles();
70915
71951
  init_render();
@@ -71236,6 +72272,7 @@ init_keys_embedded_wallet();
71236
72272
 
71237
72273
  // src/commands/keys-wallets.ts
71238
72274
  init_args();
72275
+ init_client();
71239
72276
  init_deps();
71240
72277
  init_profiles();
71241
72278
  init_render();
@@ -71418,6 +72455,7 @@ async function keysWallets(args, ctx) {
71418
72455
  // src/commands/launch.ts
71419
72456
  init_args();
71420
72457
  init_evm_lite();
72458
+ init_hood_legs();
71421
72459
  init_render();
71422
72460
  init_solana_endpoint();
71423
72461
  init_solana_lite();
@@ -71432,7 +72470,7 @@ var QUOTE_CHAIN = {
71432
72470
  eth: "hood",
71433
72471
  usdg: "hood"
71434
72472
  };
71435
- var HOOD_LAUNCH_LEGS = ["createCurve", "feeTransfer"];
72473
+ var HOOD_LAUNCH_LEGS = ["createCurve"];
71436
72474
  var HOOD_LAUNCH_BUY_LEGS = ["createCurve", "feeTransfer", "trade"];
71437
72475
  function launchBuy(flags, chain2) {
71438
72476
  const amount = flags["--buy"];
@@ -71677,6 +72715,9 @@ async function hoodLaunch(ctx, key, args) {
71677
72715
  curveAddress: curve.address,
71678
72716
  operationId: first.operationId
71679
72717
  };
72718
+ const feeCap = buy && first.plannedLegCount === 3 ? launchFeeCap(buy.raw) : undefined;
72719
+ if (feeCap !== undefined)
72720
+ quote.candleFee = `at most ${formatUnits(feeCap, 18)} ETH (the tier fee on the first buy, never above ${Number(LAUNCH_FEE_MAX_BPS) / 100}% of it)`;
71680
72721
  ctx.deps.stderr.write(buyNotice(buy, "hood"));
71681
72722
  if (!await confirmQuote(ctx, quote, args.yes)) {
71682
72723
  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.
@@ -71695,6 +72736,7 @@ async function hoodLaunch(ctx, key, args) {
71695
72736
  }
71696
72737
  const confirmedPlan = buy ? first.plannedLegCount === 3 ? ["createCurve", "feeTransfer", "trade"] : ["createCurve", "trade"] : undefined;
71697
72738
  let position = 0;
72739
+ const evm = lazyEvmRpc(ctx, flags["--rpc-url"]);
71698
72740
  const run2 = await runSequencedLegs(ctx, key, {
71699
72741
  wallet,
71700
72742
  first,
@@ -71705,10 +72747,14 @@ async function hoodLaunch(ctx, key, args) {
71705
72747
  kind: "launch",
71706
72748
  primaryLeg: "createCurve",
71707
72749
  allowedLegs,
71708
- checkLeg: (kind, leg2) => {
72750
+ checkLeg: async (kind, leg2) => {
71709
72751
  if (confirmedPlan && confirmedPlan[position++] !== kind)
71710
72752
  return false;
71711
- return kind === "createCurve" ? leg2.value === "0" : kind !== "trade" || buy !== undefined && leg2.value === buy.raw && sameEvmAddress(leg2.to, curve.address);
72753
+ if (kind === "createCurve")
72754
+ return await createCurveLegOk(leg2, wallet.address, evm());
72755
+ if (kind === "feeTransfer")
72756
+ return feeCap !== undefined && feeLegOk(leg2, { cap: feeCap, asset: "eth" });
72757
+ return kind === "trade" && buy !== undefined && leg2.value === buy.raw && sameEvmAddress(leg2.to, curve.address);
71712
72758
  },
71713
72759
  onLanded: async () => {}
71714
72760
  });
@@ -71728,13 +72774,11 @@ async function hoodLaunch(ctx, key, args) {
71728
72774
  });
71729
72775
  }
71730
72776
  function hoodLaterLegsGas(count, buys) {
71731
- if (count < 2)
71732
- return "";
71733
- return buys ? "; the later legs are priced by Candle when each becomes next" : "; the fee leg is priced by Candle when it becomes next";
72777
+ return count < 2 || !buys ? "" : "; the later legs are priced by Candle when each becomes next";
71734
72778
  }
71735
72779
  function hoodLaunchLegNames(count, buys) {
71736
72780
  if (!buys)
71737
- return count === 2 ? ["create curve", "fee"] : ["create curve"];
72781
+ return ["create curve"];
71738
72782
  return count === 3 ? ["create curve", "fee", "first buy"] : ["create curve", "first buy"];
71739
72783
  }
71740
72784
  async function recordLaunchedToken(ctx, wallet, final) {
@@ -71759,6 +72803,7 @@ async function resumeHoodLaunch(ctx, key, id, operationId, prior) {
71759
72803
 
71760
72804
  // src/commands/lp.ts
71761
72805
  init_args();
72806
+ init_client();
71762
72807
  init_render();
71763
72808
  init_solana_endpoint();
71764
72809
  init_solana_lite();
@@ -72301,6 +73346,7 @@ async function mcp(args, ctx) {
72301
73346
  // src/commands/perps.ts
72302
73347
  init_args();
72303
73348
  init_bridge();
73349
+ init_client();
72304
73350
  init_evm_lite();
72305
73351
  import { randomUUID as randomUUID4, sign as sign3 } from "node:crypto";
72306
73352
 
@@ -72317,6 +73363,10 @@ var HYPERLIQUID_EXCHANGE_URLS = {
72317
73363
  mainnet: "https://api.hyperliquid.xyz/exchange",
72318
73364
  testnet: "https://api.hyperliquid-testnet.xyz/exchange"
72319
73365
  };
73366
+ var HYPERLIQUID_INFO_URLS = {
73367
+ mainnet: "https://api.hyperliquid.xyz/info",
73368
+ testnet: "https://api.hyperliquid-testnet.xyz/info"
73369
+ };
72320
73370
  var CANDLE_HYPERLIQUID_BUILDER_ADDRESS = null;
72321
73371
  var HYPERLIQUID_MAX_BUILDER_FEE_TENTHS_BPS = 100;
72322
73372
  var HYPERLIQUID_MAX_BUILDER_FEE_RATE = "0.1%";
@@ -72622,6 +73672,67 @@ async function hyperliquidCloseOrder(fetcher, network, address, params) {
72622
73672
  const size = asked < held ? String(params.size) : String(szi).replace(/^-/, "");
72623
73673
  return { asset, isBuy: signed < 0n, size };
72624
73674
  }
73675
+ var HYPERLIQUID_DEFAULT_SLIPPAGE_BPS = 100;
73676
+ var HYPERLIQUID_MID_DRIFT_BPS = 100;
73677
+ async function hyperliquidOrderMarket(fetcher, network, address, method, params) {
73678
+ const read = async (body) => {
73679
+ const res = await fetcher(HYPERLIQUID_INFO_URLS[network], {
73680
+ method: "POST",
73681
+ headers: { "content-type": "application/json" },
73682
+ body: JSON.stringify(body)
73683
+ });
73684
+ if (!res.ok)
73685
+ throw new Error("Cannot read the market from Hyperliquid");
73686
+ return JSON.parse(await res.text());
73687
+ };
73688
+ let coin = String(params.coin ?? "").toUpperCase();
73689
+ let order;
73690
+ if (method === "modify") {
73691
+ const status = await read({ type: "orderStatus", user: address, oid: params.cloid });
73692
+ const resting = status?.order?.order ?? null;
73693
+ if (!resting || typeof resting.coin !== "string" || decimal(resting.limitPx) === null)
73694
+ throw new Error("No resting order with this cloid on Hyperliquid");
73695
+ coin = resting.coin.toUpperCase();
73696
+ order = { isBuy: resting.side === "B", limitPx: String(resting.limitPx) };
73697
+ }
73698
+ const [meta, contexts] = await read({ type: "metaAndAssetCtxs" });
73699
+ const asset = meta.universe.findIndex((m) => m.name.toUpperCase() === coin);
73700
+ if (asset < 0 || asset >= 1e4)
73701
+ throw new Error("No main-universe market for this coin on Hyperliquid");
73702
+ const context = contexts[asset];
73703
+ const midPx = context?.midPx ?? context?.markPx;
73704
+ if ((decimal(midPx) ?? 0n) <= 0n)
73705
+ throw new Error("No Hyperliquid price for this coin");
73706
+ return { asset, midPx: String(midPx), ...order ? { order } : {} };
73707
+ }
73708
+ function checkOrderMarket(action, intent, market) {
73709
+ const list = action.type === "modify" ? [action.order] : action.orders;
73710
+ if (!Array.isArray(list) || list.length === 0)
73711
+ return "missing requested order";
73712
+ if (list.some((o) => !o || o.a !== market.asset))
73713
+ return "order asset is not the requested coin's on Hyperliquid";
73714
+ const first = list[0];
73715
+ const p = intent.params;
73716
+ if (intent.method === "open" && p.price == null) {
73717
+ const requested = p.slippageBps;
73718
+ const slippage = typeof requested === "number" && Number.isInteger(requested) && requested > 0 ? requested : HYPERLIQUID_DEFAULT_SLIPPAGE_BPS;
73719
+ const bound = BigInt(slippage + HYPERLIQUID_MID_DRIFT_BPS);
73720
+ const px = decimal(first.p) ?? 0n;
73721
+ const mid = decimal(market.midPx) ?? 0n;
73722
+ const within = first.b === true ? px * 10000n <= mid * (10000n + bound) : px * 10000n >= mid * (10000n - bound);
73723
+ if (mid <= 0n || !within)
73724
+ return "market order price is outside its slippage of Hyperliquid's mid";
73725
+ }
73726
+ if (intent.method === "modify") {
73727
+ if (!market.order)
73728
+ return "no resting order on Hyperliquid to check the modify against";
73729
+ if (first.b !== market.order.isBuy)
73730
+ return "modify changes the resting order's side";
73731
+ if (p.price == null && decimal(first.p) !== decimal(market.order.limitPx))
73732
+ return "modify changes the resting order's price";
73733
+ }
73734
+ return null;
73735
+ }
72625
73736
  function verifyPerpsBuild(build, opts) {
72626
73737
  try {
72627
73738
  const network = opts.network ?? "mainnet";
@@ -72649,6 +73760,13 @@ function verifyPerpsBuild(build, opts) {
72649
73760
  if (!order || order.a !== opts.closeOrder.asset || order.b !== opts.closeOrder.isBuy || decimal(order.s) !== decimal(opts.closeOrder.size) || order.r !== true)
72650
73761
  return { ok: false, reason: "close side, asset or size does not match the requested position" };
72651
73762
  }
73763
+ if (opts.market) {
73764
+ if (opts.intent?.method !== "open" && opts.intent?.method !== "modify")
73765
+ return { ok: false, reason: "a market check needs an open or modify intent" };
73766
+ const marketRefusal = checkOrderMarket(action, opts.intent, opts.market);
73767
+ if (marketRefusal)
73768
+ return { ok: false, reason: marketRefusal };
73769
+ }
72652
73770
  if (typed.primary_type === "Agent") {
72653
73771
  const type = action.type;
72654
73772
  if (typeof type !== "string" || !HYPERLIQUID_ALLOWED_ACTION_TYPES.includes(type)) {
@@ -72709,6 +73827,7 @@ function hyperliquidRelayBody(typedData) {
72709
73827
 
72710
73828
  // src/commands/perps.ts
72711
73829
  init_render();
73830
+ init_solana_verify();
72712
73831
  init_trading();
72713
73832
  init_swap();
72714
73833
  var PERPS_SCOPE = "perps:write";
@@ -72855,6 +73974,18 @@ async function runPerpsWrite(ctx, opts) {
72855
73974
  if (!closeCheck.ok)
72856
73975
  throw new TradingError("PERPS_BUILD_REFUSED", `Refused to sign this build: ${closeCheck.reason}. Nothing was signed.`);
72857
73976
  }
73977
+ if (method === "open") {
73978
+ const market = await hyperliquidOrderMarket(ctx.deps.fetch, net, wallet.address, method, opts.body);
73979
+ const marketCheck = verifyPerpsBuild(build, {
73980
+ builder,
73981
+ network: net,
73982
+ address: wallet.address,
73983
+ intent: { method, params: opts.body },
73984
+ market
73985
+ });
73986
+ if (!marketCheck.ok)
73987
+ throw new TradingError("PERPS_BUILD_REFUSED", `Refused to sign this build: ${marketCheck.reason}. Nothing was signed.`);
73988
+ }
72858
73989
  if (!opts.submit)
72859
73990
  return printTradingResult(ctx, { ...build, signed: false, submitted: false });
72860
73991
  if (!await confirm(ctx, build, wallet.address, opts.yes)) {
@@ -73247,6 +74378,13 @@ async function perpsDeposit(args, ctx) {
73247
74378
  const solanaProblem = await tradingRead(ctx, reader, () => relaySolanaDepositProblem(transaction, wallet.address, reader.rpc));
73248
74379
  if (solanaProblem)
73249
74380
  throw refused(solanaProblem);
74381
+ const spent = await tradingRead(ctx, reader, () => solanaSpendProblem(transaction, wallet.address, reader.rpc, {
74382
+ kind: "swap",
74383
+ spend: asset === "SOL" ? "SOL" : BASES[asset]?.mint ?? asset,
74384
+ maxSpendRaw: BigInt(amountRaw)
74385
+ }));
74386
+ if (spent)
74387
+ throw refused(spent);
73250
74388
  }
73251
74389
  const decimals = Number(build.outDecimals);
73252
74390
  const minimum = decimalAmount(String(build.minimumOutRaw), decimals);
@@ -73312,6 +74450,7 @@ async function perpsDeposit(args, ctx) {
73312
74450
 
73313
74451
  // src/commands/plans.ts
73314
74452
  init_args();
74453
+ init_client();
73315
74454
 
73316
74455
  // src/plans.ts
73317
74456
  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.";
@@ -73327,7 +74466,7 @@ var PLAN_CAPABILITY_LABELS = {
73327
74466
  createLinkedWallets: "Create linked wallets",
73328
74467
  importLinkedWallets: "Import linked wallets",
73329
74468
  limitOrders: "Limit orders",
73330
- quant: "Quant on Telegram"
74469
+ quant: "Quant deep analyses (Max)"
73331
74470
  };
73332
74471
  var PLAN_LABELS = { free: "Free", believer: "Believer", pro: "Pro", max: "Max" };
73333
74472
  function planLabel(plan) {
@@ -73817,6 +74956,7 @@ async function runPlugin(name2, rawArgs, ctx) {
73817
74956
 
73818
74957
  // src/commands/pnl.ts
73819
74958
  init_args();
74959
+ init_client();
73820
74960
  init_deps();
73821
74961
  init_profiles();
73822
74962
  init_render();
@@ -74264,6 +75404,7 @@ ${renderTable(headers, rows.map((row) => row.map(terminalText)))}
74264
75404
 
74265
75405
  // src/commands/portfolio.ts
74266
75406
  init_args();
75407
+ init_client();
74267
75408
  init_deps();
74268
75409
  init_evm_lite();
74269
75410
  init_profiles();
@@ -74870,7 +76011,7 @@ function hoodHoldings(read, prices, tokensPart) {
74870
76011
  ...token.symbol ? { symbol: token.symbol } : {}
74871
76012
  });
74872
76013
  }
74873
- const holdings = raw.map(({ symbol, ...h }) => {
76014
+ const holdings2 = raw.map(({ symbol, ...h }) => {
74874
76015
  const price = prices[hoodPriceKey(h.mint)];
74875
76016
  const priceUsd = price?.priceUsd ?? null;
74876
76017
  const amount = formatAmount(h.amountRaw, h.decimals);
@@ -74887,7 +76028,7 @@ function hoodHoldings(read, prices, tokensPart) {
74887
76028
  };
74888
76029
  });
74889
76030
  return {
74890
- holdings,
76031
+ holdings: holdings2,
74891
76032
  unread: [...read.wei === null ? ["eth"] : [], ...read.tokens === null ? [tokensPart] : []],
74892
76033
  ...read.truncated ? { truncated: true } : {}
74893
76034
  };
@@ -75024,6 +76165,7 @@ ${renderTable(["GROUP", "WALLET", "POSITION", "POOL", "HOLDINGS", "UNCLAIMED FEE
75024
76165
 
75025
76166
  // src/commands/profile.ts
75026
76167
  init_args();
76168
+ init_client();
75027
76169
  init_profiles();
75028
76170
  init_render();
75029
76171
  init_solana_endpoint();
@@ -75298,6 +76440,7 @@ async function profileRemove(args, ctx) {
75298
76440
 
75299
76441
  // src/commands/setup.ts
75300
76442
  init_args();
76443
+ init_client();
75301
76444
  init_deps();
75302
76445
  init_profiles();
75303
76446
  init_render();
@@ -75495,6 +76638,10 @@ function formatAmount2(raw, decimals) {
75495
76638
  function formatSol2(lamports) {
75496
76639
  return `${formatAmount2(lamports, 9)} SOL`;
75497
76640
  }
76641
+ function signerControlChanges(signers, simulation) {
76642
+ const addresses = new Set(signers.map((entry) => entry.address));
76643
+ return controlChanges(simulation.snapshots, (address) => addresses.has(address));
76644
+ }
75498
76645
  function displayLines(input) {
75499
76646
  const { tx, compiled, signers, simulation, mints } = input;
75500
76647
  const lines = [];
@@ -75507,6 +76654,7 @@ function displayLines(input) {
75507
76654
  for (const program of programs)
75508
76655
  lines.push(`program ${programNameOf(program)}`);
75509
76656
  const deltas = computeDeltas(simulation.snapshots);
76657
+ const changes = signerControlChanges(signers, simulation);
75510
76658
  for (const entry of signers) {
75511
76659
  const sol = deltas.sol.find((delta) => delta.address === entry.address);
75512
76660
  if (sol) {
@@ -75514,7 +76662,7 @@ function displayLines(input) {
75514
76662
  } else {
75515
76663
  lines.push(`${entry.label.padEnd(11)} SOL unchanged (not a writable account of this transaction)`);
75516
76664
  }
75517
- for (const token of deltas.tokens.filter((delta) => delta.owner === entry.address)) {
76665
+ for (const token of deltas.tokens.filter((delta) => delta.ownerBefore === entry.address || delta.ownerAfter === entry.address)) {
75518
76666
  const info = mints.get(token.mint);
75519
76667
  lines.push(` ${token.mint}: ${formatAmount2(token.before, info?.decimals)} -> ${formatAmount2(token.after, info?.decimals)} (${token.after >= token.before ? "+" : ""}${formatAmount2(token.after - token.before, info?.decimals)}${info?.decimals === undefined ? "" : `, ${info.decimals} dp`}) in ${token.account}`);
75520
76668
  }
@@ -75533,10 +76681,14 @@ function displayLines(input) {
75533
76681
  const info = mints.get(token.mint);
75534
76682
  others.push(`${token.owner} receives +${formatAmount2(token.after - token.before, info?.decimals)} of ${token.mint} (account ${token.account})`);
75535
76683
  }
75536
- if (others.length === 0)
76684
+ if (others.length === 0 && changes.length === 0)
75537
76685
  lines.push("others no other account gains a balance in the simulation");
76686
+ if (others.length === 0 && changes.length > 0)
76687
+ lines.push("others no balance moves elsewhere, but control of an account changes hands (below)");
75538
76688
  for (const line of others)
75539
76689
  lines.push(`receives ${line}`);
76690
+ for (const change of changes)
76691
+ lines.push(`CONTROL ${describeControlChange(change)}`);
75540
76692
  for (const [mint, info] of mints)
75541
76693
  for (const risk of info.risks)
75542
76694
  lines.push(`warning ${mint}: ${risk}`);
@@ -75650,6 +76802,14 @@ ${simulation.result.logs.map((line) => ` ${line}`).join(`
75650
76802
  deps.stderr.write(` ${line}
75651
76803
  `);
75652
76804
  }
76805
+ const changes = signerControlChanges(signers, simulation);
76806
+ if (changes.length > 0) {
76807
+ throw new VaultError("SIGN_CONTROL_CHANGE", `The simulation hands control of a signer's account to someone else. Nothing was signed; there is no override.
76808
+ ${changes.map((change) => ` ${describeControlChange(change)}`).join(`
76809
+ `)}`, {
76810
+ suggestion: "A transfer, swap or close never needs this. If you meant to change an authority, do it from a wallet you can inspect directly."
76811
+ });
76812
+ }
75653
76813
  if (!yes)
75654
76814
  await opened.confirm(`sign with ${signers.map((entry) => entry.label).join(", ")}`);
75655
76815
  const signed = new Map;
@@ -76027,6 +77187,7 @@ init_tee_rebind();
76027
77187
 
76028
77188
  // src/commands/tee-rename.ts
76029
77189
  init_args();
77190
+ init_client();
76030
77191
  init_deps();
76031
77192
  init_profiles();
76032
77193
  init_render();
@@ -76668,6 +77829,7 @@ init_args();
76668
77829
  init_evm_lite();
76669
77830
  init_profiles();
76670
77831
  init_render();
77832
+ init_solana_verify();
76671
77833
  init_trading();
76672
77834
  init_swap();
76673
77835
  var USAGE6 = "Usage: candle transfer --to <address|wallet name|vault> --asset <SOL|USDC|CNDL|ETH|USDG>|--mint <mint>|--token <0x...> --amount <decimal|max> [--wallet <name>] [--rpc-url <url>] [--yes] [--json]";
@@ -76782,11 +77944,12 @@ async function transfer(args, ctx) {
76782
77944
  const wallet = payer.wallet;
76783
77945
  const destination = await resolveDestination(ctx, key, wallet, to);
76784
77946
  const label = asset ?? flags["--mint"];
77947
+ const solana = lazySolanaClient(ctx, flags["--rpc-url"]);
76785
77948
  let amountRaw;
76786
77949
  if (isMax)
76787
77950
  amountRaw = "max";
76788
77951
  else {
76789
- const decimals = asset ? BASES[asset]?.decimals ?? 9 : await decimalsFor(ctx, flags["--mint"], lazySolanaClient(ctx, flags["--rpc-url"]));
77952
+ const decimals = asset ? BASES[asset]?.decimals ?? 9 : await decimalsFor(ctx, flags["--mint"], solana);
76790
77953
  amountRaw = rawAmount(flags["--amount"], decimals);
76791
77954
  }
76792
77955
  const amountText2 = isMax ? `the full spendable balance of ${label}` : `${flags["--amount"]} ${label}`;
@@ -76809,8 +77972,22 @@ async function transfer(args, ctx) {
76809
77972
  throw new TradingError("INVALID_RESPONSE", "Candle did not return one unsigned transfer transaction; nothing was signed.");
76810
77973
  if (typeof built.payerAddress === "string" && built.payerAddress !== wallet.address)
76811
77974
  throw new TradingError("INVALID_RESPONSE", "The transfer build does not name the requested payer; nothing was signed.");
76812
- const builtAmount = typeof built.amountRaw === "string" ? built.amountRaw : amountRaw;
76813
- ctx.deps.stderr.write(`Built transfer ${transferId}: ${asset ? `${decimalAmount(builtAmount, BASES[asset]?.decimals ?? 0)} ${asset}` : `${builtAmount} raw units of ${label}`}${typeof built.destinationKind === "string" ? ` to ${built.destinationKind === "vault" ? "the vault" : "a linked wallet"}` : ""}.
77975
+ const builtAmount = typeof built.amountRaw === "string" && /^[1-9]\d*$/.test(built.amountRaw) ? built.amountRaw : null;
77976
+ if (builtAmount === null || !isMax && builtAmount !== amountRaw)
77977
+ throw new TradingError("INVALID_RESPONSE", "The transfer build does not name the confirmed amount; nothing was signed.");
77978
+ const amountShown = asset ? `${decimalAmount(builtAmount, BASES[asset]?.decimals ?? 0)} ${asset}` : `${builtAmount} raw units of ${label}`;
77979
+ ctx.deps.stderr.write(`Built transfer ${transferId}: ${amountShown}${typeof built.destinationKind === "string" ? ` to ${built.destinationKind === "vault" ? "the vault" : "a linked wallet"}` : ""}.
77980
+ `);
77981
+ const reader = await solana();
77982
+ const problem = await tradingRead(ctx, reader, () => solanaSpendProblem(unsigned[0], wallet.address, reader.rpc, {
77983
+ kind: "transfer",
77984
+ asset: asset === undefined ? flags["--mint"] : asset === "SOL" ? "SOL" : BASES[asset]?.mint ?? asset,
77985
+ amountRaw: BigInt(builtAmount),
77986
+ recipient: destination.address
77987
+ }));
77988
+ if (problem)
77989
+ throw new TradingError("INVALID_RESPONSE", `The transaction Candle built does not move ${safeText(amountShown)} to ${destination.address} and nothing else (${safeText(problem)}); nothing was signed.`);
77990
+ ctx.deps.stderr.write(`Checked locally: it moves ${safeText(amountShown)} from ${wallet.address} to ${destination.address} and nothing else (simulated; a program can still behave differently once signed).
76814
77991
  `);
76815
77992
  const signed = await relaySign(ctx, key, wallet, unsigned[0]);
76816
77993
  const result = await request(ctx, key, "/api/v1/agent/transfer/submit", {
@@ -77870,8 +79047,8 @@ function assertFloorCarried(copy, live) {
77870
79047
  details: { step: "floor", missing: ids }
77871
79048
  });
77872
79049
  }
77873
- function verifiedWithLine(word2, envelopeId, how, floorClause) {
77874
- return ` verified with ${word2} ${envelopeId} (${how}); ${floorClause}`;
79050
+ function verifiedWithLine(word3, envelopeId, how, floorClause) {
79051
+ return ` verified with ${word3} ${envelopeId} (${how}); ${floorClause}`;
77875
79052
  }
77876
79053
  function carriedClause(passphraseIds) {
77877
79054
  return `passphrase ${passphraseIds.join(", ")} carried byte for byte`;
@@ -79372,7 +80549,7 @@ Anyone who reads them can move every derived key's funds.
79372
80549
  deps.stdout.write(`
79373
80550
  `);
79374
80551
  for (let i = 0;i < words.length; i += 4) {
79375
- const row = words.slice(i, i + 4).map((word2, offset) => `${String(i + offset + 1).padStart(2, " ")}. ${word2.padEnd(9, " ")}`).join(" ");
80552
+ const row = words.slice(i, i + 4).map((word3, offset) => `${String(i + offset + 1).padStart(2, " ")}. ${word3.padEnd(9, " ")}`).join(" ");
79376
80553
  deps.stdout.write(` ${row.trimEnd()}
79377
80554
  `);
79378
80555
  }
@@ -79793,8 +80970,8 @@ async function collectOwn(ctx) {
79793
80970
  // src/commands/vault-factor-dispatch.ts
79794
80971
  init_vault_support();
79795
80972
  async function vaultFactor(args, ctx) {
79796
- const [word2, ...rest] = args;
79797
- switch (word2) {
80973
+ const [word3, ...rest] = args;
80974
+ switch (word3) {
79798
80975
  case "list":
79799
80976
  return vaultFactorList(rest, ctx);
79800
80977
  case "add":
@@ -79804,7 +80981,7 @@ async function vaultFactor(args, ctx) {
79804
80981
  case undefined:
79805
80982
  return usage(ctx, "Usage: candle vault factor <list | add passphrase | add security-key | add touch-id | add passkey | remove <id>>");
79806
80983
  default:
79807
- return usage(ctx, `Unknown subcommand: vault factor ${word2}. Try: list, add, remove`);
80984
+ return usage(ctx, `Unknown subcommand: vault factor ${word3}. Try: list, add, remove`);
79808
80985
  }
79809
80986
  }
79810
80987
 
@@ -81001,12 +82178,12 @@ function matches2(entry, filter) {
81001
82178
  // src/commands/vault-phrase-dispatch.ts
81002
82179
  init_vault_support();
81003
82180
  async function vaultPhrase(args, ctx) {
81004
- const [word2, ...rest] = args;
81005
- if (word2 === "show")
82181
+ const [word3, ...rest] = args;
82182
+ if (word3 === "show")
81006
82183
  return vaultPhraseShow(rest, ctx);
81007
- if (word2 === undefined)
82184
+ if (word3 === undefined)
81008
82185
  return usage(ctx, "Usage: candle vault phrase show");
81009
- return usage(ctx, `Unknown subcommand: vault phrase ${word2}. The only one is: show`);
82186
+ return usage(ctx, `Unknown subcommand: vault phrase ${word3}. The only one is: show`);
81010
82187
  }
81011
82188
 
81012
82189
  // src/commands/vault-promote.ts
@@ -81019,6 +82196,7 @@ init_solana_endpoint();
81019
82196
  init_solana_lite();
81020
82197
 
81021
82198
  // src/vault/account-room.ts
82199
+ init_client();
81022
82200
  init_deps();
81023
82201
  init_errors2();
81024
82202
  async function readAccountRoom(ctx) {
@@ -81316,8 +82494,8 @@ async function promoteEvmInPlace(input, subjectLabel, sweepTo) {
81316
82494
  ...toKey.keySigner !== undefined ? { signer: toKey.keySigner.fingerprint } : {}
81317
82495
  });
81318
82496
  const height = await promoteHeight(ctx, client);
81319
- const holdings = await readEvmHoldings(client.rpc, first.subject.address);
81320
- for (const line of holdingsLines(first.subject.address, holdings, new Date(ctx.deps.now()).toISOString())) {
82497
+ const holdings2 = await readEvmHoldings(client.rpc, first.subject.address);
82498
+ for (const line of holdingsLines(first.subject.address, holdings2, new Date(ctx.deps.now()).toISOString())) {
81321
82499
  ctx.deps.stdout.write(`${line}
81322
82500
  `);
81323
82501
  }
@@ -81411,7 +82589,7 @@ async function promoteEvmInPlace(input, subjectLabel, sweepTo) {
81411
82589
  lifecycle: updated?.tee?.lifecycle ?? null,
81412
82590
  linkedWalletId: updated?.linkedWalletId ?? null,
81413
82591
  controlledBy: controlledByJson(controlledBy),
81414
- holdings: { eth: holdings.eth.toString(), usdg: holdings.usdg.toString(), weth: holdings.weth.toString() },
82592
+ holdings: { eth: holdings2.eth.toString(), usdg: holdings2.usdg.toString(), weth: holdings2.weth.toString() },
81415
82593
  scanStart: { block: scanStart.block, recorded: scanStart.recorded },
81416
82594
  vaultVersion: reopened.file.version,
81417
82595
  ...rebound?.json ?? {}
@@ -84353,6 +85531,7 @@ init_promote_support();
84353
85531
  init_store();
84354
85532
 
84355
85533
  // src/vault/tee-transfer.ts
85534
+ init_client();
84356
85535
  init_errors2();
84357
85536
  function assertNoPendingSweep(entry) {
84358
85537
  const pending = entry.tee?.sweepPending?.length ?? 0;
@@ -84694,6 +85873,7 @@ init_wallets();
84694
85873
 
84695
85874
  // src/commands/wallets-allow-launch.ts
84696
85875
  init_args();
85876
+ init_client();
84697
85877
  init_deps();
84698
85878
  init_profiles();
84699
85879
  init_render();
@@ -85012,6 +86192,7 @@ async function walletsCloseEmpty(args, ctx) {
85012
86192
 
85013
86193
  // src/commands/wallets-trust.ts
85014
86194
  init_args();
86195
+ init_client();
85015
86196
  init_deps();
85016
86197
  init_profiles();
85017
86198
  init_render();
@@ -85269,6 +86450,7 @@ init_render();
85269
86450
  init_secret_store();
85270
86451
 
85271
86452
  // src/update-notice.ts
86453
+ init_client();
85272
86454
  init_release();
85273
86455
  var DAY_MS = 24 * 60 * 60 * 1000;
85274
86456
  async function maybeWriteUpdateNotice(deps, opts = {}) {
@@ -85469,12 +86651,12 @@ var COMMANDS = {
85469
86651
  };
85470
86652
  var ROUTED_COMMANDS = new Set(Object.keys(COMMANDS));
85471
86653
  var ALIASES = { wallet: "wallets" };
85472
- function canonicalCommand(word2) {
85473
- return word2 !== undefined && Object.hasOwn(ALIASES, word2) ? ALIASES[word2] : word2;
86654
+ function canonicalCommand(word3) {
86655
+ return word3 !== undefined && Object.hasOwn(ALIASES, word3) ? ALIASES[word3] : word3;
85474
86656
  }
85475
- var ROUTED_SUBCOMMANDS = Object.fromEntries(Object.entries(COMMANDS).filter(([, route]) => route.subcommands !== undefined).map(([word2, route]) => [word2, Object.keys(route.subcommands ?? {})]));
85476
- function routeFor(word2) {
85477
- return word2 !== undefined && Object.hasOwn(COMMANDS, word2) ? COMMANDS[word2] : undefined;
86657
+ var ROUTED_SUBCOMMANDS = Object.fromEntries(Object.entries(COMMANDS).filter(([, route]) => route.subcommands !== undefined).map(([word3, route]) => [word3, Object.keys(route.subcommands ?? {})]));
86658
+ function routeFor(word3) {
86659
+ return word3 !== undefined && Object.hasOwn(COMMANDS, word3) ? COMMANDS[word3] : undefined;
85478
86660
  }
85479
86661
  function subHandlerFor(route, sub) {
85480
86662
  const subcommands = route?.subcommands;
@@ -85516,8 +86698,8 @@ async function run2(argv, deps) {
85516
86698
  return 1;
85517
86699
  }
85518
86700
  const extractedForNotice = extractGlobalFlags(argv);
85519
- const word2 = "error" in extractedForNotice ? undefined : canonicalCommand(extractedForNotice.rest[0] === "candle" ? extractedForNotice.rest[1] : extractedForNotice.rest[0]);
85520
- await maybeWriteUpdateNotice(deps, { command: word2 });
86701
+ const word3 = "error" in extractedForNotice ? undefined : canonicalCommand(extractedForNotice.rest[0] === "candle" ? extractedForNotice.rest[1] : extractedForNotice.rest[0]);
86702
+ await maybeWriteUpdateNotice(deps, { command: word3 });
85521
86703
  return code;
85522
86704
  }
85523
86705
  async function runCommand(argv, deps) {
@@ -85528,7 +86710,7 @@ async function runCommand(argv, deps) {
85528
86710
  return 2;
85529
86711
  }
85530
86712
  const { rest, flags } = extracted;
85531
- const plugin = pluginInvocation(argv, deps.env, (word3) => ROUTED_COMMANDS.has(canonicalCommand(word3) ?? ""));
86713
+ const plugin = pluginInvocation(argv, deps.env, (word4) => ROUTED_COMMANDS.has(canonicalCommand(word4) ?? ""));
85532
86714
  if (plugin !== undefined) {
85533
86715
  const leading = extractGlobalFlags(argv.slice(0, argv.length - plugin.args.length - 1));
85534
86716
  const pluginFlags = "error" in leading ? flags : leading.flags;
@@ -85609,9 +86791,9 @@ async function runCommand(argv, deps) {
85609
86791
  vaultFactor: flags.vaultFactor,
85610
86792
  vaultDevice: flags.vaultDevice
85611
86793
  };
85612
- const word2 = cmd ?? "";
85613
- const actsAsIdentity = word2 !== "mcp" || mcpActsAsIdentity(tokens.slice(1));
85614
- if (ROUTED_COMMANDS.has(word2) && !NEVER_GUARDED.has(word2) && routesToCommand(cmd, sub) && actsAsIdentity) {
86794
+ const word3 = cmd ?? "";
86795
+ const actsAsIdentity = word3 !== "mcp" || mcpActsAsIdentity(tokens.slice(1));
86796
+ if (ROUTED_COMMANDS.has(word3) && !NEVER_GUARDED.has(word3) && routesToCommand(cmd, sub) && actsAsIdentity) {
85615
86797
  const verdict = await verifyProfileAccount(ctx, config2);
85616
86798
  if (!verdict.ok) {
85617
86799
  writeLocalFailure(deps, { code: "ACCOUNT_MISMATCH", message: verdict.message, suggestion: verdict.suggestion }, flags.json);
@@ -85645,11 +86827,11 @@ function splitFix(message) {
85645
86827
  const fixAt = message.indexOf(" Run: ");
85646
86828
  return fixAt === -1 ? { message } : { message: message.slice(0, fixAt), suggestion: message.slice(fixAt + 1) };
85647
86829
  }
85648
- function unknownCommand(deps, token, word2) {
86830
+ function unknownCommand(deps, token, word3) {
85649
86831
  if (token !== undefined)
85650
86832
  deps.stderr.write(`Unknown command: ${token}
85651
86833
  `);
85652
- deps.stderr.write((word2 === undefined ? undefined : renderTopic(word2)) ?? renderTopLevel());
86834
+ deps.stderr.write((word3 === undefined ? undefined : renderTopic(word3)) ?? renderTopLevel());
85653
86835
  return 1;
85654
86836
  }
85655
86837
  async function migrateProfiles(deps) {
@@ -85670,10 +86852,13 @@ async function migrateProfiles(deps) {
85670
86852
  await deps.writeConfig({ profiles: config2.profiles, activeProfile: config2.activeProfile });
85671
86853
  return config2;
85672
86854
  }
85673
- function realOpenBrowser(url) {
86855
+ function realOpenBrowser(serverUrl) {
86856
+ const url = browserUrl(serverUrl);
86857
+ if (url === null)
86858
+ return;
85674
86859
  try {
85675
86860
  const platform = process.platform;
85676
- const child = platform === "darwin" ? spawn3("open", [url], { stdio: "ignore", detached: true }) : platform === "win32" ? spawn3("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }) : spawn3("xdg-open", [url], { stdio: "ignore", detached: true });
86861
+ const child = platform === "darwin" ? spawn3("open", [url], { stdio: "ignore", detached: true }) : platform === "win32" ? spawn3("rundll32", ["url.dll,FileProtocolHandler", url], { stdio: "ignore", detached: true }) : spawn3("xdg-open", [url], { stdio: "ignore", detached: true });
85677
86862
  child.on("error", () => {});
85678
86863
  child.unref();
85679
86864
  } catch {}