@candledottv/cli 0.11.28 → 0.11.30

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 +1406 -128
  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) {
@@ -8848,10 +8898,12 @@ function createEvmRpc(url, fetchFn) {
8848
8898
  if (r === null || r === undefined)
8849
8899
  return null;
8850
8900
  const status = asQuantity(r.status, "eth_getTransactionReceipt");
8901
+ const logs = (Array.isArray(r.logs) ? r.logs : []).flatMap((log) => typeof log?.address === "string" && Array.isArray(log.topics) && log.topics.every((topic) => typeof topic === "string") ? [{ address: log.address, topics: log.topics }] : []);
8851
8902
  return {
8852
8903
  status: status === 1n ? 1 : 0,
8853
8904
  blockNumber: asQuantity(r.blockNumber, "eth_getTransactionReceipt"),
8854
- transactionHash: typeof r.transactionHash === "string" ? r.transactionHash : hash
8905
+ transactionHash: typeof r.transactionHash === "string" ? r.transactionHash : hash,
8906
+ logs
8855
8907
  };
8856
8908
  },
8857
8909
  async getTransactionByHash(hash) {
@@ -9099,7 +9151,12 @@ function allSignerWallets(view) {
9099
9151
  function activeSigner(view) {
9100
9152
  if (view.state !== "active" || !view.signerQuorumId || !view.fingerprint || !view.spkiSha256)
9101
9153
  return null;
9102
- return { fingerprint: view.fingerprint, spkiSha256: view.spkiSha256, signerQuorumId: view.signerQuorumId };
9154
+ return {
9155
+ fingerprint: view.fingerprint,
9156
+ spkiSha256: view.spkiSha256,
9157
+ signerQuorumId: view.signerQuorumId,
9158
+ publicKeyDer: typeof view.publicKeyDer === "string" ? view.publicKeyDer : null
9159
+ };
9103
9160
  }
9104
9161
  async function readSigner(ctx, keyPrefix, auth) {
9105
9162
  return apiRequest(`/api/v1/agent/keys/${encodeURIComponent(keyPrefix)}/signer`, {
@@ -9145,9 +9202,48 @@ function errorDetails(result) {
9145
9202
  const error = result.raw?.error;
9146
9203
  return error && typeof error === "object" ? error : {};
9147
9204
  }
9148
- async function confirmSignerPin(ctx, keyPrefix, active, opts = {}) {
9205
+ function verifiedSigner(signer) {
9206
+ let spkiSha256 = signer.spkiSha256;
9207
+ if (signer.publicKeyDer !== undefined) {
9208
+ if (typeof signer.publicKeyDer !== "string" || signer.publicKeyDer.length === 0) {
9209
+ return { ok: false, problem: "the server did not send the signer's public key" };
9210
+ }
9211
+ try {
9212
+ const key = createPublicKey({ key: Buffer.from(signer.publicKeyDer, "base64"), format: "der", type: "spki" });
9213
+ if (key.asymmetricKeyType !== "ec" || key.asymmetricKeyDetails?.namedCurve !== "prime256v1") {
9214
+ return { ok: false, problem: "the server's public key is not a P-256 key" };
9215
+ }
9216
+ } catch {
9217
+ return { ok: false, problem: "the server's public key is not a valid SPKI DER key" };
9218
+ }
9219
+ spkiSha256 = spkiSha256Of(signer.publicKeyDer);
9220
+ if (spkiSha256 !== signer.spkiSha256) {
9221
+ return { ok: false, problem: "the hash the server reports is not the hash of the public key it sent" };
9222
+ }
9223
+ }
9224
+ if (!isSpkiSha256(spkiSha256))
9225
+ return { ok: false, problem: "the server's signer hash is not a sha256" };
9226
+ const fingerprint = keySignerFingerprint(spkiSha256);
9227
+ if (normalizeFingerprint(signer.fingerprint) !== normalizeFingerprint(fingerprint)) {
9228
+ return { ok: false, problem: "the fingerprint the server reports is not the one its hash gives" };
9229
+ }
9230
+ return { ok: true, fingerprint, spkiSha256 };
9231
+ }
9232
+ async function confirmSignerPin(ctx, keyPrefix, signer, opts = {}) {
9149
9233
  const { deps } = ctx;
9150
9234
  const nothing = opts.nothing ?? "Nothing was imported and nothing was pinned.";
9235
+ const verified = verifiedSigner(signer);
9236
+ if (!verified.ok) {
9237
+ return {
9238
+ ok: false,
9239
+ failure: {
9240
+ code: "KEY_SIGNER_UNVERIFIED",
9241
+ message: `Key ${keyPrefix}'s signer cannot be checked on this machine: ${verified.problem}. ${nothing}`,
9242
+ suggestion: "Do not trust this signer. Check the API address this CLI uses, and run the command again."
9243
+ }
9244
+ };
9245
+ }
9246
+ const active = { fingerprint: verified.fingerprint, spkiSha256: verified.spkiSha256 };
9151
9247
  const pin = await readPin(deps, keyPrefix);
9152
9248
  if (pin && pin.spkiSha256 === active.spkiSha256)
9153
9249
  return { ok: true, pinned: "same" };
@@ -9206,6 +9302,7 @@ function signerSlotProblem(stored, expectSpkiSha256) {
9206
9302
  }
9207
9303
  var KEY_PREFIX_RE, CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
9208
9304
  var init_key_signers = __esm(() => {
9305
+ init_client();
9209
9306
  init_secret_store();
9210
9307
  KEY_PREFIX_RE = /^[A-Za-z0-9_-]{8}$/;
9211
9308
  });
@@ -10851,6 +10948,7 @@ var init_errors2 = __esm(() => {
10851
10948
  "SIGN_LOOKUP_TABLE_UNRESOLVED",
10852
10949
  "SIGN_TRANSACTION_UNDECODABLE",
10853
10950
  "SIGN_BROADCAST_FAILED",
10951
+ "SIGN_CONTROL_CHANGE",
10854
10952
  "VAULT_LABEL_NOT_FOUND",
10855
10953
  "VAULT_LABEL_AMBIGUOUS",
10856
10954
  "VAULT_LABEL_TAKEN",
@@ -11472,13 +11570,14 @@ async function runSequencedLegs(ctx, key, opts) {
11472
11570
  throw failWith("INVALID_RESPONSE", `Candle offered another ${current.legKind} leg after the ${plannedCount} planned ${plannedCount === 1 ? "leg" : "legs"}; nothing was signed.`, 1);
11473
11571
  if (signedKinds.has(current.legKind) || landed.some((done) => done.kind === current.legKind))
11474
11572
  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))
11573
+ if (!await opts.checkLeg(current.legKind, leg))
11476
11574
  throw failWith("INVALID_RESPONSE", `The ${current.legKind} leg does not match what this operation confirmed; nothing was signed.`, 1);
11477
11575
  if (!Number.isFinite(current.expiresAt) || current.expiresAt <= ctx.deps.now())
11478
11576
  throw failWith("QUOTE_EXPIRED", `Operation ${current.operationId}'s window closed before the ${current.legKind} leg was signed.`, 1);
11479
11577
  const signed = await relaySignEvmLeg(ctx, key, opts.wallet, leg);
11480
11578
  signedCount += 1;
11481
11579
  signedKinds.add(current.legKind);
11580
+ opts.onSigned?.(current.legKind, signed.hash);
11482
11581
  if (opts.clientId !== undefined && opts.kind !== undefined)
11483
11582
  await saveOperationHash(ctx, key, opts.clientId, opts.kind, signed.hash, current.operationId);
11484
11583
  const posted = await apiRequest(opts.submitPath, {
@@ -11692,6 +11791,7 @@ var init_trading = __esm(() => {
11692
11791
  init_sha3();
11693
11792
  init_esm();
11694
11793
  init_zod();
11794
+ init_client();
11695
11795
  init_deps();
11696
11796
  init_evm_lite();
11697
11797
  init_key_signers();
@@ -12128,6 +12228,20 @@ function tokenBalanceOf(view) {
12128
12228
  tokenProgram: view.owner
12129
12229
  };
12130
12230
  }
12231
+ function optionalPubkey(data, at) {
12232
+ const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
12233
+ return dv.getUint32(at, true) === 0 ? null : encodePubkey(data.subarray(at + 4, at + 36));
12234
+ }
12235
+ function tokenAuthoritiesOf(view) {
12236
+ if (view === null || tokenBalanceOf(view) === undefined)
12237
+ return;
12238
+ const dv = new DataView(view.data.buffer, view.data.byteOffset, view.data.byteLength);
12239
+ return {
12240
+ delegate: optionalPubkey(view.data, 72),
12241
+ delegatedAmount: dv.getBigUint64(121, true),
12242
+ closeAuthority: optionalPubkey(view.data, 129)
12243
+ };
12244
+ }
12131
12245
  function computeDeltas(snapshots) {
12132
12246
  const sol = [];
12133
12247
  const tokens = [];
@@ -12142,9 +12256,15 @@ function computeDeltas(snapshots) {
12142
12256
  const shape = post ?? pre;
12143
12257
  if (shape === undefined)
12144
12258
  continue;
12259
+ const preAuth = tokenAuthoritiesOf(snapshot.before);
12260
+ const postAuth = tokenAuthoritiesOf(snapshot.after);
12145
12261
  tokens.push({
12146
12262
  account: snapshot.address,
12147
12263
  owner: shape.owner,
12264
+ ...pre ? { ownerBefore: pre.owner } : {},
12265
+ ...post ? { ownerAfter: post.owner } : {},
12266
+ ...preAuth ? { delegateBefore: preAuth.delegate, closeAuthorityBefore: preAuth.closeAuthority } : {},
12267
+ ...postAuth ? { delegateAfter: postAuth.delegate, closeAuthorityAfter: postAuth.closeAuthority } : {},
12148
12268
  mint: shape.mint,
12149
12269
  tokenProgram: shape.tokenProgram,
12150
12270
  before: pre?.amount ?? 0n,
@@ -12153,6 +12273,64 @@ function computeDeltas(snapshots) {
12153
12273
  }
12154
12274
  return { sol, tokens };
12155
12275
  }
12276
+ function controlChanges(snapshots, controlled) {
12277
+ const changes = [];
12278
+ for (const { address, before, after } of snapshots) {
12279
+ if (after === null)
12280
+ continue;
12281
+ if (controlled(address) && before !== null && before.owner === SYSTEM_PROGRAM_ID) {
12282
+ if (after.owner !== before.owner || after.data.length !== before.data.length)
12283
+ changes.push({
12284
+ account: address,
12285
+ controller: address,
12286
+ what: "program owner",
12287
+ from: before.owner,
12288
+ to: after.data.length !== before.data.length ? `${after.owner} with ${after.data.length} data bytes` : after.owner
12289
+ });
12290
+ continue;
12291
+ }
12292
+ const pre = tokenBalanceOf(before);
12293
+ const preAuth = tokenAuthoritiesOf(before);
12294
+ const post = tokenBalanceOf(after);
12295
+ const postAuth = tokenAuthoritiesOf(after);
12296
+ if (pre !== undefined && preAuth !== undefined && controlled(pre.owner)) {
12297
+ const base = { account: address, controller: pre.owner, mint: pre.mint };
12298
+ if (post === undefined || postAuth === undefined) {
12299
+ changes.push({ ...base, what: "owner", from: pre.owner, to: `${after.owner} (no longer a token account)` });
12300
+ continue;
12301
+ }
12302
+ if (post.owner !== pre.owner)
12303
+ changes.push({ ...base, what: "owner", from: pre.owner, to: post.owner });
12304
+ if (postAuth.delegate !== null && (postAuth.delegate !== preAuth.delegate || postAuth.delegatedAmount > preAuth.delegatedAmount))
12305
+ changes.push({
12306
+ ...base,
12307
+ what: "delegate",
12308
+ from: preAuth.delegate,
12309
+ to: `${postAuth.delegate} (may move up to ${postAuth.delegatedAmount} raw)`
12310
+ });
12311
+ if (postAuth.closeAuthority !== null && postAuth.closeAuthority !== preAuth.closeAuthority)
12312
+ changes.push({ ...base, what: "close authority", from: preAuth.closeAuthority, to: postAuth.closeAuthority });
12313
+ continue;
12314
+ }
12315
+ if (pre === undefined && post !== undefined && postAuth !== undefined && controlled(post.owner)) {
12316
+ const base = { account: address, controller: post.owner, mint: post.mint };
12317
+ if (postAuth.delegate !== null && postAuth.delegate !== post.owner)
12318
+ changes.push({
12319
+ ...base,
12320
+ what: "delegate",
12321
+ from: null,
12322
+ to: `${postAuth.delegate} (may move up to ${postAuth.delegatedAmount} raw)`
12323
+ });
12324
+ if (postAuth.closeAuthority !== null && postAuth.closeAuthority !== post.owner)
12325
+ changes.push({ ...base, what: "close authority", from: null, to: postAuth.closeAuthority });
12326
+ }
12327
+ }
12328
+ return changes;
12329
+ }
12330
+ function describeControlChange(change) {
12331
+ const subject = change.mint === undefined ? `wallet ${change.account}` : `token account ${change.account} (mint ${change.mint}, owned by ${change.controller})`;
12332
+ return `${subject}: ${change.what} changes from ${change.from ?? "none"} to ${change.to ?? "none"}`;
12333
+ }
12156
12334
  function programNameOf(programId) {
12157
12335
  switch (programId) {
12158
12336
  case SYSTEM_PROGRAM_ID:
@@ -12173,7 +12351,7 @@ function programNameOf(programId) {
12173
12351
  return programId;
12174
12352
  }
12175
12353
  }
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;
12354
+ 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
12355
  var init_solana_alt = __esm(() => {
12178
12356
  init_esm();
12179
12357
  init_solana_lite();
@@ -12181,6 +12359,14 @@ var init_solana_alt = __esm(() => {
12181
12359
  };
12182
12360
  LookupTableError = class LookupTableError extends Error {
12183
12361
  };
12362
+ TOKEN_CONTROL_RANGES = [
12363
+ [0, 32, "mint"],
12364
+ [32, 64, "authority"],
12365
+ [72, 108, "delegate"],
12366
+ [108, 109, "state"],
12367
+ [121, 129, "delegated amount"],
12368
+ [129, 165, "close authority"]
12369
+ ];
12184
12370
  });
12185
12371
 
12186
12372
  // src/bridge.ts
@@ -12416,6 +12602,724 @@ var init_bridge = __esm(() => {
12416
12602
  BRIDGE_WAIT_MS = 10 * 60 * 1000;
12417
12603
  });
12418
12604
 
12605
+ // src/hood-legs.ts
12606
+ function bytesOf(hex2) {
12607
+ try {
12608
+ return hexToBytes2(hex2);
12609
+ } catch {
12610
+ return;
12611
+ }
12612
+ }
12613
+ function selectorOf(data) {
12614
+ return data.length < 4 ? "" : bytesToHex2(data.subarray(0, 4));
12615
+ }
12616
+ function word2(data, index, base = 4) {
12617
+ const start = base + index * 32;
12618
+ return start + 32 <= data.length ? data.subarray(start, start + 32) : undefined;
12619
+ }
12620
+ function wordUint2(w) {
12621
+ return w === undefined ? undefined : BigInt(bytesToHex2(w));
12622
+ }
12623
+ function wordAddress2(w) {
12624
+ if (w === undefined || w.subarray(0, 12).some((b) => b !== 0))
12625
+ return;
12626
+ return bytesToHex2(w.subarray(12));
12627
+ }
12628
+ function isDecimal(value) {
12629
+ return /^\d+$/.test(value);
12630
+ }
12631
+ function among(address, list) {
12632
+ return list.some((known) => sameEvmAddress(address, known));
12633
+ }
12634
+ function decodeApprove(data) {
12635
+ if (data.length !== 68 || selectorOf(data) !== ERC20_APPROVE_SELECTOR)
12636
+ return;
12637
+ const spender = wordAddress2(word2(data, 0));
12638
+ const amount = wordUint2(word2(data, 1));
12639
+ return spender === undefined || amount === undefined ? undefined : { spender, amount };
12640
+ }
12641
+ function isDynamicType(type) {
12642
+ if (typeof type === "string")
12643
+ return type === "bytes";
12644
+ return "array" in type || type.tuple.some(isDynamicType);
12645
+ }
12646
+ function headSize(type) {
12647
+ return typeof type === "object" && "tuple" in type && !isDynamicType(type) ? type.tuple.reduce((sum, member) => sum + headSize(member), 0) : 32;
12648
+ }
12649
+ function readWord(data, position) {
12650
+ if (!Number.isSafeInteger(position) || position < 0 || position + 32 > data.length)
12651
+ throw new Error("abi: short data");
12652
+ return data.subarray(position, position + 32);
12653
+ }
12654
+ function readNumber(data, position) {
12655
+ const value = BigInt(bytesToHex2(readWord(data, position)));
12656
+ if (value > BigInt(data.length))
12657
+ throw new Error("abi: offset past the data");
12658
+ return Number(value);
12659
+ }
12660
+ function decodeAbiTuple(data, start, types2) {
12661
+ const out = [];
12662
+ let cursor = start;
12663
+ for (const type of types2) {
12664
+ if (isDynamicType(type)) {
12665
+ out.push(decodeAbiDynamic(data, start + readNumber(data, cursor), type));
12666
+ cursor += 32;
12667
+ } else {
12668
+ out.push(decodeAbiStatic(data, cursor, type));
12669
+ cursor += headSize(type);
12670
+ }
12671
+ }
12672
+ return out;
12673
+ }
12674
+ function decodeAbiStatic(data, position, type) {
12675
+ if (typeof type === "object" && "tuple" in type)
12676
+ return decodeAbiTuple(data, position, type.tuple);
12677
+ const w = readWord(data, position);
12678
+ if (type === "address") {
12679
+ const address = wordAddress2(w);
12680
+ if (address === undefined)
12681
+ throw new Error("abi: dirty address");
12682
+ return address;
12683
+ }
12684
+ if (type === "bool")
12685
+ return BigInt(bytesToHex2(w)) === 1n;
12686
+ return BigInt(bytesToHex2(w));
12687
+ }
12688
+ function decodeAbiDynamic(data, position, type) {
12689
+ if (type === "bytes") {
12690
+ const length = readNumber(data, position);
12691
+ if (position + 32 + length > data.length)
12692
+ throw new Error("abi: short bytes");
12693
+ return data.subarray(position + 32, position + 32 + length);
12694
+ }
12695
+ if (typeof type === "object" && "array" in type) {
12696
+ const length = readNumber(data, position);
12697
+ if (length > MAX_ABI_ARRAY)
12698
+ throw new Error("abi: array too long");
12699
+ return decodeAbiTuple(data, position + 32, Array.from({ length }, () => type.array));
12700
+ }
12701
+ return decodeAbiTuple(data, position, type.tuple);
12702
+ }
12703
+ function decodeArgs(data, types2, base = 4) {
12704
+ try {
12705
+ return decodeAbiTuple(data, base, types2);
12706
+ } catch {
12707
+ return;
12708
+ }
12709
+ }
12710
+ function v2CallOk(data, value, terms) {
12711
+ const selector = selectorOf(data);
12712
+ if (selector === V2_SWAP_ETH_FOR_TOKENS_SELECTOR) {
12713
+ const args = decodeArgs(data, ["uint", ADDRESSES, "address", "uint"]);
12714
+ if (!args || terms.inputToken !== undefined)
12715
+ return false;
12716
+ const path = asList(args[1]);
12717
+ 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);
12718
+ }
12719
+ if (selector === V2_SWAP_TOKENS_FOR_ETH_SELECTOR) {
12720
+ const args = decodeArgs(data, ["uint", "uint", ADDRESSES, "address", "uint"]);
12721
+ if (!args || terms.inputToken === undefined || value !== 0n)
12722
+ return false;
12723
+ const path = asList(args[2]);
12724
+ 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);
12725
+ }
12726
+ return false;
12727
+ }
12728
+ function v3SwapOf(call) {
12729
+ const selector = selectorOf(call);
12730
+ if (selector === V3_EXACT_INPUT_SINGLE_SELECTOR) {
12731
+ const args = decodeArgs(call, [V3_SINGLE]);
12732
+ if (!args)
12733
+ return;
12734
+ const fields = asList(args[0]);
12735
+ return {
12736
+ tokenIn: asAddress(fields[0]),
12737
+ tokenOut: asAddress(fields[1]),
12738
+ amountIn: asNumber(fields[4]),
12739
+ minimum: asNumber(fields[5]),
12740
+ recipient: asAddress(fields[3])
12741
+ };
12742
+ }
12743
+ if (selector === V3_EXACT_INPUT_SELECTOR) {
12744
+ const args = decodeArgs(call, [V3_PATH]);
12745
+ if (!args)
12746
+ return;
12747
+ const fields = asList(args[0]);
12748
+ const path = asBytes(fields[0]);
12749
+ if (path.length < 43 || (path.length - 20) % 23 !== 0)
12750
+ return;
12751
+ return {
12752
+ tokenIn: bytesToHex2(path.subarray(0, 20)),
12753
+ tokenOut: bytesToHex2(path.subarray(-20)),
12754
+ amountIn: asNumber(fields[2]),
12755
+ minimum: asNumber(fields[3]),
12756
+ recipient: asAddress(fields[1])
12757
+ };
12758
+ }
12759
+ return;
12760
+ }
12761
+ function v3CallOk(data, terms) {
12762
+ if (selectorOf(data) !== V3_MULTICALL_SELECTOR)
12763
+ return false;
12764
+ const args = decodeArgs(data, ["uint", { array: "bytes" }]);
12765
+ if (!args)
12766
+ return false;
12767
+ const calls = asList(args[1]).map(asBytes);
12768
+ if (calls.length === 0 || calls.length > 4)
12769
+ return false;
12770
+ let swaps = 0;
12771
+ let spent = 0n;
12772
+ let heldByRouter = false;
12773
+ let unwrappedToWallet = false;
12774
+ for (const call of calls) {
12775
+ const selector = selectorOf(call);
12776
+ if (selector === V3_REFUND_ETH_SELECTOR) {
12777
+ if (call.length !== 4)
12778
+ return false;
12779
+ } else if (selector === V3_UNWRAP_WETH9_SELECTOR) {
12780
+ const unwrap = decodeArgs(call, ["uint", "address"]);
12781
+ if (!unwrap || terms.outputToken !== undefined || !heldByRouter || unwrappedToWallet || asNumber(unwrap[0]) < terms.minOutRaw || !sameEvmAddress(asAddress(unwrap[1]), terms.wallet))
12782
+ return false;
12783
+ unwrappedToWallet = true;
12784
+ } else {
12785
+ const swap = v3SwapOf(call);
12786
+ 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))
12787
+ return false;
12788
+ if (!sameEvmAddress(swap.recipient, terms.wallet))
12789
+ heldByRouter = true;
12790
+ swaps++;
12791
+ spent += swap.amountIn;
12792
+ }
12793
+ }
12794
+ return swaps > 0 && spent <= terms.amountRaw && (!heldByRouter || unwrappedToWallet);
12795
+ }
12796
+ function bytesAre(bytes, expected) {
12797
+ return bytes.length === expected.length && expected.every((byte, i) => bytes[i] === byte);
12798
+ }
12799
+ function v4Sides(params) {
12800
+ const key = asList(params[0]);
12801
+ const zeroForOne = params[1] === true;
12802
+ return { input: asAddress(zeroForOne ? key[0] : key[1]), output: asAddress(zeroForOne ? key[1] : key[0]) };
12803
+ }
12804
+ function v4InputIs(currency, terms) {
12805
+ return sameEvmAddress(currency, terms.inputToken ?? NATIVE_CURRENCY);
12806
+ }
12807
+ function v4CallOk(data, terms) {
12808
+ if (selectorOf(data) !== UNIVERSAL_EXECUTE_SELECTOR)
12809
+ return false;
12810
+ const args = decodeArgs(data, ["bytes", { array: "bytes" }, "uint"]);
12811
+ if (!args)
12812
+ return false;
12813
+ const commands = asBytes(args[0]);
12814
+ const inputs = asList(args[1]).map(asBytes);
12815
+ try {
12816
+ if (bytesAre(commands, [UR_V4_SWAP]) && inputs.length === 1)
12817
+ return v4DirectOk(inputs[0], terms);
12818
+ if (bytesAre(commands, [UR_WRAP_ETH, UR_V3_SWAP_EXACT_IN, UR_V4_SWAP]) && inputs.length === 3)
12819
+ return v4BridgedBuyOk(inputs, terms);
12820
+ if (bytesAre(commands, [UR_V4_SWAP, UR_V3_SWAP_EXACT_IN, UR_UNWRAP_WETH]) && inputs.length === 3)
12821
+ return v4BridgedSellOk(inputs, terms);
12822
+ } catch {
12823
+ return false;
12824
+ }
12825
+ return false;
12826
+ }
12827
+ function v4DirectOk(input, terms) {
12828
+ const top = decodeArgs(input, ["bytes", { array: "bytes" }], 0);
12829
+ if (!top || !bytesAre(asBytes(top[0]), [V4_SWAP_EXACT_IN_SINGLE, V4_SETTLE_ALL, V4_TAKE_ALL]))
12830
+ return false;
12831
+ const params = asList(top[1]).map(asBytes);
12832
+ if (params.length !== 3)
12833
+ return false;
12834
+ const swap = decodeArgs(params[0], [V4_SWAP_PARAMS], 0);
12835
+ const settle = decodeArgs(params[1], ["address", "uint"], 0);
12836
+ const take2 = decodeArgs(params[2], ["address", "uint"], 0);
12837
+ if (!swap || !settle || !take2)
12838
+ return false;
12839
+ const fields = asList(swap[0]);
12840
+ const { input: inCurrency, output: outCurrency } = v4Sides(fields);
12841
+ 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);
12842
+ }
12843
+ function v3IntoRouterOk(input, firstToken, lastToken) {
12844
+ const args = decodeArgs(input, ["address", "uint", "uint", "bytes", "bool"], 0);
12845
+ if (!args)
12846
+ return false;
12847
+ const path = asBytes(args[3]);
12848
+ 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);
12849
+ }
12850
+ function v4BridgedBuyOk([wrap, v3, v4], terms) {
12851
+ if (terms.inputToken !== undefined || terms.outputToken === undefined)
12852
+ return false;
12853
+ const wrapped = decodeArgs(wrap, ["address", "uint"], 0);
12854
+ if (!wrapped || !sameEvmAddress(asAddress(wrapped[0]), ADDRESS_THIS) || asNumber(wrapped[1]) > terms.amountRaw)
12855
+ return false;
12856
+ const top = decodeArgs(v4, ["bytes", { array: "bytes" }], 0);
12857
+ if (!top || !bytesAre(asBytes(top[0]), [V4_SETTLE, V4_SWAP_EXACT_IN_SINGLE, V4_TAKE_ALL]))
12858
+ return false;
12859
+ const params = asList(top[1]).map(asBytes);
12860
+ if (params.length !== 3)
12861
+ return false;
12862
+ const settle = decodeArgs(params[0], ["address", "uint", "bool"], 0);
12863
+ const swap = decodeArgs(params[1], [V4_SWAP_PARAMS], 0);
12864
+ const take2 = decodeArgs(params[2], ["address", "uint"], 0);
12865
+ if (!settle || !swap || !take2 || settle[2] !== false)
12866
+ return false;
12867
+ const fields = asList(swap[0]);
12868
+ const { input, output } = v4Sides(fields);
12869
+ 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);
12870
+ }
12871
+ function v4BridgedSellOk([v4, v3, unwrap], terms) {
12872
+ if (terms.inputToken === undefined || terms.outputToken !== undefined)
12873
+ return false;
12874
+ const top = decodeArgs(v4, ["bytes", { array: "bytes" }], 0);
12875
+ if (!top || !bytesAre(asBytes(top[0]), [V4_SETTLE, V4_SWAP_EXACT_IN_SINGLE, V4_TAKE]))
12876
+ return false;
12877
+ const params = asList(top[1]).map(asBytes);
12878
+ if (params.length !== 3)
12879
+ return false;
12880
+ const settle = decodeArgs(params[0], ["address", "uint", "bool"], 0);
12881
+ const swap = decodeArgs(params[1], [V4_SWAP_PARAMS], 0);
12882
+ const take2 = decodeArgs(params[2], ["address", "address", "uint"], 0);
12883
+ if (!settle || !swap || !take2)
12884
+ return false;
12885
+ const { input, output } = v4Sides(asList(swap[0]));
12886
+ 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))
12887
+ return false;
12888
+ if (!v3IntoRouterOk(v3, output, HOOD_WETH_ADDRESS))
12889
+ return false;
12890
+ const unwrapped = decodeArgs(unwrap, ["address", "uint"], 0);
12891
+ return unwrapped !== undefined && asNumber(unwrapped[1]) >= terms.minOutRaw && sameEvmAddress(asAddress(unwrapped[0]), terms.wallet);
12892
+ }
12893
+ function kyberCallOk(data, terms) {
12894
+ const selector = selectorOf(data);
12895
+ let desc;
12896
+ if (selector === KYBER_SWAP_SELECTOR || selector === KYBER_SWAP_GENERIC_SELECTOR) {
12897
+ const args = decodeArgs(data, [KYBER_EXECUTION]);
12898
+ desc = args && asList(asList(args[0])[3]);
12899
+ } else if (selector === KYBER_SWAP_SIMPLE_SELECTOR) {
12900
+ const args = decodeArgs(data, ["address", KYBER_DESC, "bytes", "bytes"]);
12901
+ desc = args && asList(args[1]);
12902
+ }
12903
+ if (!desc)
12904
+ return false;
12905
+ const amount = asNumber(desc[7]);
12906
+ 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;
12907
+ }
12908
+ function dexCallOk(to, data, value, terms) {
12909
+ if (sameEvmAddress(to, HOOD_UNIV2_ROUTER))
12910
+ return v2CallOk(data, value, terms);
12911
+ if (sameEvmAddress(to, HOOD_UNIV3_ROUTER))
12912
+ return v3CallOk(data, terms);
12913
+ if (sameEvmAddress(to, HOOD_V4_UNIVERSAL_ROUTER))
12914
+ return v4CallOk(data, terms);
12915
+ if (sameEvmAddress(to, HOOD_KYBER_ROUTER))
12916
+ return kyberCallOk(data, terms);
12917
+ return false;
12918
+ }
12919
+ async function createCurveLegOk(leg, wallet, rpc) {
12920
+ if (leg.value !== "0" || !sameEvmAddress(leg.to, HOOD_CURVE_FACTORY))
12921
+ return false;
12922
+ const data = bytesOf(leg.data);
12923
+ if (!data || selectorOf(data) !== CREATE_CURVE_SELECTOR)
12924
+ return false;
12925
+ const offset = wordUint2(word2(data, 0));
12926
+ if (offset === undefined || offset % 32n !== 0n || offset > BigInt(data.length))
12927
+ return false;
12928
+ const creator = wordAddress2(word2(data, 5, 4 + Number(offset)));
12929
+ if (creator === undefined || !sameEvmAddress(creator, wallet))
12930
+ return false;
12931
+ const splitter = wordAddress2(word2(data, 3, 4 + Number(offset)));
12932
+ if (splitter === undefined)
12933
+ return false;
12934
+ try {
12935
+ const vouched = await rpc.call({
12936
+ to: HOOD_SPLITTER_FACTORY,
12937
+ data: hexToBytes2(`${SPLITTER_IS_SPLITTER_SELECTOR}${"0".repeat(24)}${splitter.slice(2)}`)
12938
+ });
12939
+ if (wordUint2(word2(vouched, 0, 0)) !== 1n)
12940
+ return false;
12941
+ const dev = wordAddress2(word2(await rpc.call({ to: splitter, data: hexToBytes2(SPLITTER_DEV_SELECTOR) }), 0, 0));
12942
+ return dev !== undefined && sameEvmAddress(dev, wallet);
12943
+ } catch {
12944
+ return false;
12945
+ }
12946
+ }
12947
+ function launchFeeCap(buyRaw) {
12948
+ return (BigInt(buyRaw) * LAUNCH_FEE_MAX_BPS + 9999n) / 10000n;
12949
+ }
12950
+ function feeLegOk(leg, fee) {
12951
+ if (!isDecimal(leg.value))
12952
+ return false;
12953
+ if (fee.asset === "eth") {
12954
+ if (leg.data !== "0x" && leg.data !== "")
12955
+ return false;
12956
+ if (BigInt(leg.value) > fee.cap)
12957
+ return false;
12958
+ return sameEvmAddress(leg.to, fee.treasury);
12959
+ }
12960
+ if (leg.value !== "0" || !sameEvmAddress(leg.to, HOOD_USDG_ADDRESS))
12961
+ return false;
12962
+ const data = bytesOf(leg.data);
12963
+ const decoded = data ? decodeErc20Transfer(data) : undefined;
12964
+ if (!decoded || decoded.amount > fee.cap)
12965
+ return false;
12966
+ return sameEvmAddress(decoded.recipient, fee.treasury);
12967
+ }
12968
+ async function hoodFeeTreasury(rpc) {
12969
+ try {
12970
+ const answer = await rpc.call({ to: HOOD_SPLITTER_FACTORY, data: hexToBytes2(SPLITTER_PLATFORM_VAULT_SELECTOR) });
12971
+ const vault = wordAddress2(word2(answer, 0, 0));
12972
+ return vault !== undefined && BigInt(vault) !== 0n ? vault : undefined;
12973
+ } catch {
12974
+ return;
12975
+ }
12976
+ }
12977
+ function curveCreatedBy(receipt, wallet) {
12978
+ if (!receipt || receipt.status !== 1)
12979
+ return;
12980
+ const created = (receipt.logs ?? []).filter((log) => sameEvmAddress(log.address, HOOD_CURVE_FACTORY) && log.topics[0]?.toLowerCase() === CURVE_CREATED_TOPIC);
12981
+ if (created.length !== 1)
12982
+ return;
12983
+ const topics = created[0].topics;
12984
+ const topicAddress = (topic) => {
12985
+ const bytes = topic === undefined ? undefined : bytesOf(topic);
12986
+ return bytes?.length === 32 ? wordAddress2(bytes) : undefined;
12987
+ };
12988
+ const curve = topicAddress(topics[1]);
12989
+ const creator = topicAddress(topics[3]);
12990
+ if (curve === undefined || BigInt(curve) === 0n || creator === undefined)
12991
+ return;
12992
+ return sameEvmAddress(creator, wallet) ? curve : undefined;
12993
+ }
12994
+ function launchBuyLegOk(leg, buyRaw, curve) {
12995
+ const data = bytesOf(leg.data);
12996
+ return data !== undefined && leg.value === buyRaw && sameEvmAddress(leg.to, curve) && data.length === 36 && selectorOf(data) === CURVE_BUY_SELECTOR;
12997
+ }
12998
+ function hoodTradeLegCheck(terms) {
12999
+ let curve;
13000
+ const registeredCurve = () => {
13001
+ curve ??= terms.curve ? terms.curve().catch(() => {
13002
+ return;
13003
+ }) : Promise.resolve(undefined);
13004
+ return curve;
13005
+ };
13006
+ let treasury;
13007
+ const chainTreasury = () => {
13008
+ treasury ??= terms.treasury ? terms.treasury().catch(() => {
13009
+ return;
13010
+ }) : Promise.resolve(undefined);
13011
+ return treasury;
13012
+ };
13013
+ const spenderOk = async (spender) => {
13014
+ if (terms.venue !== "curve")
13015
+ return among(spender, DEX_SPENDERS);
13016
+ const registered = await registeredCurve();
13017
+ return registered !== undefined && sameEvmAddress(spender, registered);
13018
+ };
13019
+ return async (kind, leg) => {
13020
+ if (!isDecimal(leg.value))
13021
+ return false;
13022
+ const data = bytesOf(leg.data);
13023
+ if (!data)
13024
+ return false;
13025
+ if (kind === "feeTransfer") {
13026
+ if (terms.fee === undefined)
13027
+ return false;
13028
+ const named = await chainTreasury();
13029
+ return named !== undefined && feeLegOk(leg, { ...terms.fee, treasury: named });
13030
+ }
13031
+ if (kind === "approval") {
13032
+ if (terms.inputToken === undefined || leg.value !== "0" || !sameEvmAddress(leg.to, terms.inputToken))
13033
+ return false;
13034
+ const approve = decodeApprove(data);
13035
+ return approve !== undefined && approve.amount <= terms.amountRaw && await spenderOk(approve.spender);
13036
+ }
13037
+ if (kind === "permit2Approval") {
13038
+ if (terms.inputToken === undefined || terms.venue !== "dex" || leg.value !== "0")
13039
+ return false;
13040
+ if (!sameEvmAddress(leg.to, HOOD_PERMIT2) || data.length !== 132)
13041
+ return false;
13042
+ if (selectorOf(data) !== PERMIT2_APPROVE_SELECTOR)
13043
+ return false;
13044
+ const token = wordAddress2(word2(data, 0));
13045
+ const spender = wordAddress2(word2(data, 1));
13046
+ const amount = wordUint2(word2(data, 2));
13047
+ return token !== undefined && sameEvmAddress(token, terms.inputToken) && spender !== undefined && sameEvmAddress(spender, HOOD_V4_UNIVERSAL_ROUTER) && amount !== undefined && amount <= terms.amountRaw;
13048
+ }
13049
+ if (kind !== "trade")
13050
+ return false;
13051
+ const value = BigInt(leg.value);
13052
+ if (terms.inputToken !== undefined ? value !== 0n : value > terms.amountRaw)
13053
+ return false;
13054
+ if (terms.venue === "dex")
13055
+ return dexCallOk(leg.to, data, value, {
13056
+ wallet: terms.wallet,
13057
+ ...terms.inputToken !== undefined ? { inputToken: terms.inputToken } : {},
13058
+ amountRaw: terms.amountRaw,
13059
+ outputToken: terms.outputToken,
13060
+ minOutRaw: terms.minOutRaw
13061
+ });
13062
+ const registered = await registeredCurve();
13063
+ if (registered === undefined || !sameEvmAddress(leg.to, registered))
13064
+ return false;
13065
+ const selector = selectorOf(data);
13066
+ if (terms.inputToken === undefined)
13067
+ return selector === CURVE_BUY_SELECTOR && data.length === 36 && wordUint2(word2(data, 0)) >= terms.minOutRaw;
13068
+ if (data.length !== 68)
13069
+ return false;
13070
+ const amountIn = wordUint2(word2(data, 0));
13071
+ if (amountIn > terms.amountRaw || wordUint2(word2(data, 1)) < terms.minOutRaw)
13072
+ return false;
13073
+ return selector === (sameEvmAddress(terms.inputToken, HOOD_USDG_ADDRESS) ? CURVE_BUY_WITH_QUOTE_SELECTOR : CURVE_SELL_SELECTOR);
13074
+ };
13075
+ }
13076
+ async function registeredCurveFor(rpc, token) {
13077
+ const call = hexToBytes2(`${CURVE_FOR_BASE_TOKEN_SELECTOR}${"0".repeat(24)}${token.toLowerCase().replace(/^0x/, "")}`);
13078
+ for (const registry of HOOD_CURVE_REGISTRIES) {
13079
+ let answer;
13080
+ try {
13081
+ answer = await rpc.call({ to: registry, data: call });
13082
+ } catch {
13083
+ continue;
13084
+ }
13085
+ const curve = wordAddress2(word2(answer, 0, 0));
13086
+ if (curve !== undefined && BigInt(curve) !== 0n)
13087
+ return curve;
13088
+ }
13089
+ return;
13090
+ }
13091
+ 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", SPLITTER_PLATFORM_VAULT_SELECTOR = "0x20f094f8", CURVE_CREATED_TOPIC = "0xed3a740542131b3621499825e40efbcaf096e3b1608de865c08a2109378bb36b", 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";
13092
+ var init_hood_legs = __esm(() => {
13093
+ init_evm_lite();
13094
+ HOOD_CURVE_REGISTRIES = [
13095
+ "0xb789E2711445488407c120563B29dF7c376cC9Ea",
13096
+ "0x75934c16162075583435358f9F042D8D6E42Bd5C",
13097
+ "0xFD54D094ad93349CA316bA309A3C8573E3D6C0cd"
13098
+ ];
13099
+ DEX_SPENDERS = [HOOD_UNIV2_ROUTER, HOOD_UNIV3_ROUTER, HOOD_KYBER_ROUTER, HOOD_PERMIT2];
13100
+ ADDRESSES = { array: "address" };
13101
+ V3_SINGLE = { tuple: ["address", "address", "uint", "address", "uint", "uint", "uint"] };
13102
+ V3_PATH = { tuple: ["bytes", "address", "uint", "uint"] };
13103
+ KYBER_DESC = {
13104
+ tuple: [
13105
+ "address",
13106
+ "address",
13107
+ { array: "address" },
13108
+ { array: "uint" },
13109
+ { array: "address" },
13110
+ { array: "uint" },
13111
+ "address",
13112
+ "uint",
13113
+ "uint",
13114
+ "uint",
13115
+ "bytes"
13116
+ ]
13117
+ };
13118
+ KYBER_EXECUTION = { tuple: ["address", "address", "bytes", KYBER_DESC, "bytes"] };
13119
+ V4_POOL_KEY = { tuple: ["address", "address", "uint", "uint", "address"] };
13120
+ V4_SWAP_PARAMS = { tuple: [V4_POOL_KEY, "bool", "uint", "uint", "uint", "bytes"] };
13121
+ });
13122
+
13123
+ // src/solana-verify.ts
13124
+ function isTokenProgram(program) {
13125
+ return program === TOKEN_PROGRAM_ID || program === TOKEN_2022_PROGRAM_ID;
13126
+ }
13127
+ function systemIndex(data) {
13128
+ if (data.length < 4)
13129
+ return -1;
13130
+ return new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(0, true);
13131
+ }
13132
+ function assetOf(asset) {
13133
+ return asset === NATIVE_MINT ? "SOL" : asset;
13134
+ }
13135
+ function instructionProblem(tx, compiled, wallet, expect) {
13136
+ for (const [i, ix] of tx.message.instructions.entries()) {
13137
+ const program = compiled.keys[ix.programIdIndex];
13138
+ const account = (at) => compiled.keys[ix.accountIndexes[at] ?? -1];
13139
+ const kind = ix.data[0];
13140
+ if (isTokenProgram(program)) {
13141
+ if (kind === TOKEN_IX_APPROVE || kind === TOKEN_IX_APPROVE_CHECKED)
13142
+ return `instruction ${i} approves a delegate over a token account`;
13143
+ if (kind === TOKEN_IX_SET_AUTHORITY)
13144
+ return `instruction ${i} changes a token account's authority`;
13145
+ if (kind === TOKEN_IX_CLOSE_ACCOUNT && account(1) !== wallet)
13146
+ return `instruction ${i} closes a token account to ${account(1) ?? "?"}, not this wallet`;
13147
+ }
13148
+ if (program === SYSTEM_PROGRAM_ID) {
13149
+ const index = systemIndex(ix.data);
13150
+ if ((index === SYSTEM_IX_ASSIGN || index === SYSTEM_IX_ALLOCATE) && account(0) === wallet)
13151
+ return `instruction ${i} reassigns this wallet to another program`;
13152
+ }
13153
+ if (expect.kind !== "transfer")
13154
+ continue;
13155
+ if (program === undefined || !TRANSFER_PROGRAMS.has(program))
13156
+ return `instruction ${i} calls ${program ?? "an unknown program"}, which a transfer never does`;
13157
+ if (program === SYSTEM_PROGRAM_ID && systemIndex(ix.data) !== SYSTEM_IX_TRANSFER)
13158
+ return `instruction ${i} is a System instruction other than Transfer`;
13159
+ if (isTokenProgram(program) && kind !== TOKEN_IX_TRANSFER && kind !== TOKEN_IX_TRANSFER_CHECKED)
13160
+ return `instruction ${i} is a token instruction other than Transfer`;
13161
+ if (program === ASSOCIATED_TOKEN_PROGRAM_ID) {
13162
+ const create = ix.data.length === 0 ? ATA_IX_CREATE : ix.data.length === 1 ? ix.data[0] : -1;
13163
+ if (create !== ATA_IX_CREATE && create !== ATA_IX_CREATE_IDEMPOTENT)
13164
+ return `instruction ${i} is an Associated Token instruction other than Create`;
13165
+ const owner = account(2);
13166
+ if (account(0) !== wallet || owner !== expect.recipient && owner !== wallet)
13167
+ return `instruction ${i} creates a token account for ${owner ?? "?"}, not ${expect.recipient}`;
13168
+ }
13169
+ }
13170
+ return null;
13171
+ }
13172
+ function holdings(views, wallet) {
13173
+ const out = new Map;
13174
+ const add2 = (asset, amount) => out.set(asset, (out.get(asset) ?? 0n) + amount);
13175
+ for (const { address, view } of views) {
13176
+ if (view === null)
13177
+ continue;
13178
+ if (address === wallet) {
13179
+ add2("SOL", view.lamports);
13180
+ continue;
13181
+ }
13182
+ const balance = tokenBalanceOf(view);
13183
+ if (balance === undefined || balance.owner !== wallet)
13184
+ continue;
13185
+ add2("SOL", view.lamports);
13186
+ if (balance.mint !== NATIVE_MINT)
13187
+ add2(balance.mint, balance.amount);
13188
+ }
13189
+ return out;
13190
+ }
13191
+ function walletDeltas(simulation, wallet) {
13192
+ const before = holdings(simulation.snapshots.map((s) => ({ address: s.address, view: s.before })), wallet);
13193
+ const after = holdings(simulation.snapshots.map((s) => ({ address: s.address, view: s.after })), wallet);
13194
+ const deltas = new Map;
13195
+ for (const asset of new Set([...before.keys(), ...after.keys()]))
13196
+ deltas.set(asset, (after.get(asset) ?? 0n) - (before.get(asset) ?? 0n));
13197
+ if (!deltas.has("SOL"))
13198
+ deltas.set("SOL", 0n);
13199
+ return deltas;
13200
+ }
13201
+ function transferProblem(simulation, wallet, expect) {
13202
+ const allowed = (address) => address === wallet || address === expect.recipient;
13203
+ for (const { address, before, after } of simulation.snapshots) {
13204
+ const lamportsBefore = before?.lamports ?? 0n;
13205
+ const lamportsAfter = after?.lamports ?? 0n;
13206
+ const tokenAfter = tokenBalanceOf(after);
13207
+ const tokenBefore = tokenBalanceOf(before);
13208
+ if (lamportsAfter > lamportsBefore && !allowed(address) && !allowed(tokenAfter?.owner))
13209
+ return `${address} would receive ${lamportsAfter - lamportsBefore} lamports, and only ${expect.recipient} may`;
13210
+ const amountBefore = tokenBefore?.amount ?? 0n;
13211
+ const amountAfter = tokenAfter?.amount ?? 0n;
13212
+ if (tokenAfter !== undefined && amountAfter > amountBefore && !allowed(tokenAfter.owner))
13213
+ return `${tokenAfter.owner} would receive ${amountAfter - amountBefore} raw of ${tokenAfter.mint}, and only ${expect.recipient} may`;
13214
+ }
13215
+ const asset = assetOf(expect.asset);
13216
+ const deltas = walletDeltas(simulation, wallet);
13217
+ const sol = deltas.get("SOL") ?? 0n;
13218
+ for (const [mint, delta] of deltas) {
13219
+ if (mint === "SOL" || mint === asset || delta >= 0n)
13220
+ continue;
13221
+ return `the wallet would also lose ${-delta} raw of ${mint}`;
13222
+ }
13223
+ if (asset === "SOL") {
13224
+ if (-sol < expect.amountRaw || -sol > expect.amountRaw + SOLANA_OVERHEAD_LAMPORTS)
13225
+ return `the wallet would lose ${-sol} lamports, not the ${expect.amountRaw} confirmed plus network fees`;
13226
+ const recipient = simulation.snapshots.find((s) => s.address === expect.recipient);
13227
+ const received2 = (recipient?.after?.lamports ?? 0n) - (recipient?.before?.lamports ?? 0n);
13228
+ if (received2 !== expect.amountRaw)
13229
+ return `${expect.recipient} would receive ${received2} lamports, not the ${expect.amountRaw} confirmed`;
13230
+ return null;
13231
+ }
13232
+ const moved = -(deltas.get(asset) ?? 0n);
13233
+ if (moved !== expect.amountRaw)
13234
+ return `the wallet would send ${moved} raw of ${asset}, not the ${expect.amountRaw} confirmed`;
13235
+ if (-sol > SOLANA_OVERHEAD_LAMPORTS)
13236
+ return `the wallet would also lose ${-sol} lamports, more than network fees and rent`;
13237
+ let received = 0n;
13238
+ for (const { before, after } of simulation.snapshots) {
13239
+ const post = tokenBalanceOf(after);
13240
+ if (post === undefined || post.owner !== expect.recipient || post.mint !== asset)
13241
+ continue;
13242
+ received += post.amount - (tokenBalanceOf(before)?.amount ?? 0n);
13243
+ }
13244
+ if (received <= 0n)
13245
+ return `${expect.recipient} would receive none of ${asset}`;
13246
+ return null;
13247
+ }
13248
+ function swapProblem(simulation, wallet, expect) {
13249
+ const spend = assetOf(expect.spend);
13250
+ const receive = expect.receive === undefined ? undefined : assetOf(expect.receive.asset);
13251
+ const deltas = walletDeltas(simulation, wallet);
13252
+ for (const [asset, delta] of deltas) {
13253
+ if (asset === "SOL" || asset === spend || asset === receive || delta >= 0n)
13254
+ continue;
13255
+ return `the wallet would also lose ${-delta} raw of ${asset}`;
13256
+ }
13257
+ const sol = deltas.get("SOL") ?? 0n;
13258
+ const spent = -(deltas.get(spend) ?? 0n);
13259
+ const spendLimit = expect.maxSpendRaw + (spend === "SOL" ? SOLANA_OVERHEAD_LAMPORTS : 0n);
13260
+ if (spent > spendLimit)
13261
+ return `the wallet would spend ${spent} raw of ${spend}, more than the ${expect.maxSpendRaw} confirmed${spend === "SOL" ? " plus network fees" : ""}`;
13262
+ if (spend !== "SOL" && receive !== "SOL" && -sol > SOLANA_OVERHEAD_LAMPORTS)
13263
+ return `the wallet would also lose ${-sol} lamports, more than network fees and rent`;
13264
+ if (expect.receive === undefined || receive === undefined)
13265
+ return null;
13266
+ const received = deltas.get(receive) ?? 0n;
13267
+ const floor = expect.receive.minRaw - (receive === "SOL" ? SOLANA_OVERHEAD_LAMPORTS : 0n);
13268
+ if (received < floor || receive !== "SOL" && received <= 0n)
13269
+ return `the wallet would receive ${received} raw of ${receive}, less than the ${expect.receive.minRaw} confirmed`;
13270
+ return null;
13271
+ }
13272
+ async function solanaSpendProblem(transactionBase64, wallet, rpc, expect) {
13273
+ let tx;
13274
+ try {
13275
+ tx = decodeTransaction(decodeStrictBase64(transactionBase64));
13276
+ } catch (error) {
13277
+ if (error instanceof TransactionDecodeError)
13278
+ return `the transaction does not decode: ${error.message}`;
13279
+ throw error;
13280
+ }
13281
+ if (tx.message.staticKeys[0] !== wallet)
13282
+ return `the fee payer is ${tx.message.staticKeys[0]}, not this wallet`;
13283
+ let compiled;
13284
+ try {
13285
+ compiled = await resolveCompiledKeys(tx.message, rpc);
13286
+ } catch (error) {
13287
+ if (error instanceof LookupTableError)
13288
+ return `a lookup table did not resolve: ${error.message}`;
13289
+ throw error;
13290
+ }
13291
+ const refused = instructionProblem(tx, compiled, wallet, expect);
13292
+ if (refused)
13293
+ return refused;
13294
+ let simulation;
13295
+ try {
13296
+ simulation = await simulateWithSnapshots(rpc, transactionBase64, compiled);
13297
+ } catch (error) {
13298
+ if (isRateLimited(error))
13299
+ throw error;
13300
+ return `the simulation could not be run: ${error instanceof Error ? error.message : String(error)}`;
13301
+ }
13302
+ if (simulation.result.err !== null && simulation.result.err !== undefined)
13303
+ return `the simulation failed: ${JSON.stringify(simulation.result.err)}`;
13304
+ const control = controlChanges(simulation.snapshots, (address) => address === wallet);
13305
+ if (control[0] !== undefined)
13306
+ return describeControlChange(control[0]);
13307
+ return expect.kind === "transfer" ? transferProblem(simulation, wallet, expect) : swapProblem(simulation, wallet, expect);
13308
+ }
13309
+ 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;
13310
+ var init_solana_verify = __esm(() => {
13311
+ init_solana_alt();
13312
+ init_solana_lite();
13313
+ TRANSFER_PROGRAMS = new Set([
13314
+ SYSTEM_PROGRAM_ID,
13315
+ TOKEN_PROGRAM_ID,
13316
+ TOKEN_2022_PROGRAM_ID,
13317
+ ASSOCIATED_TOKEN_PROGRAM_ID,
13318
+ COMPUTE_BUDGET_PROGRAM_ID,
13319
+ MEMO_PROGRAM_ID
13320
+ ]);
13321
+ });
13322
+
12419
13323
  // src/commands/swap.ts
12420
13324
  import { randomUUID } from "node:crypto";
12421
13325
  function tradingFailure(ctx, error, id) {
@@ -12680,15 +13584,15 @@ async function swap(args, ctx) {
12680
13584
  const outDecimals = await decimalsFor(ctx, to, solana);
12681
13585
  let amountRaw;
12682
13586
  if (percent !== undefined) {
12683
- const reader = await solana();
13587
+ const reader2 = await solana();
12684
13588
  let balance;
12685
13589
  if (from === "SOL")
12686
- balance = await tradingRead(ctx, reader, () => reader.rpc.getBalance(wallet.address));
13590
+ balance = await tradingRead(ctx, reader2, () => reader2.rpc.getBalance(wallet.address));
12687
13591
  else {
12688
13592
  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)
13593
+ const accounts = (await tradingRead(ctx, reader2, () => Promise.all([
13594
+ reader2.rpc.getTokenAccountsByOwner(wallet.address, TOKEN_PROGRAM_ID),
13595
+ reader2.rpc.getTokenAccountsByOwner(wallet.address, TOKEN_2022_PROGRAM_ID)
12692
13596
  ]))).flat();
12693
13597
  balance = accounts.filter((account) => account.mint === mint).reduce((sum, account) => sum + BigInt(account.amountRaw), 0n);
12694
13598
  }
@@ -12771,6 +13675,21 @@ async function swap(args, ctx) {
12771
13675
  throw new TradingError("INVALID_RESPONSE", "A TEE swap must contain exactly one same-chain transaction.");
12772
13676
  if (!transaction)
12773
13677
  throw new TradingError("INVALID_RESPONSE", "Missing transaction.");
13678
+ const feeRaw = BigInt(data.fee.feeRaw);
13679
+ const feeInInput = kind === "trade" && Boolean(fromBase);
13680
+ const solanaAssetOf = (asset) => asset === "SOL" ? "SOL" : BASES[asset]?.mint ?? asset;
13681
+ const reader = await solana();
13682
+ const problem = await tradingRead(ctx, reader, () => solanaSpendProblem(transaction, wallet.address, reader.rpc, {
13683
+ kind: "swap",
13684
+ spend: solanaAssetOf(from),
13685
+ maxSpendRaw: BigInt(amountRaw) + (feeInInput ? feeRaw : 0n),
13686
+ receive: {
13687
+ asset: solanaAssetOf(to),
13688
+ minRaw: BigInt(minimumRaw)
13689
+ }
13690
+ }));
13691
+ if (problem)
13692
+ throw new TradingError("INVALID_RESPONSE", `The transaction Candle built does not match the confirmed quote (${safeText(problem)}); nothing was signed.`);
12774
13693
  const signed = await relaySign(ctx, key, payerWallet.wallet, transaction);
12775
13694
  const result = kind === "swap" ? await request(ctx, key, "/api/v1/agent/swap/submit", {
12776
13695
  clientTradeId: id,
@@ -12980,6 +13899,19 @@ async function hoodSwap(ctx, args) {
12980
13899
  throw chainMismatch(`TEE wallet ${wallet.id}`, wallet.chain === "evm" ? "hood" : "solana", "hood");
12981
13900
  const recorded = kind === "trade" ? token : HOOD_USDG_ADDRESS;
12982
13901
  const notices = [];
13902
+ const feeRaw = BigInt(data.fee.feeRaw);
13903
+ const feeAsset = artifacts.quoteAsset === "usdg" ? "usdg" : "eth";
13904
+ const checkLeg = hoodTradeLegCheck({
13905
+ wallet: wallet.address,
13906
+ inputToken: from.asset === "ETH" ? undefined : from.asset === "USDG" ? HOOD_USDG_ADDRESS : from.asset,
13907
+ amountRaw: BigInt(amountRaw),
13908
+ outputToken: to.asset === "ETH" ? undefined : to.asset === "USDG" ? HOOD_USDG_ADDRESS : to.asset,
13909
+ minOutRaw: BigInt(data.minOutRaw),
13910
+ venue: kind === "trade" && artifacts.venue === "curve" ? "curve" : "dex",
13911
+ ...kind === "trade" && feeRaw > 0n ? { fee: { cap: feeRaw, asset: feeAsset } } : {},
13912
+ treasury: () => hoodFeeTreasury(rpc()),
13913
+ curve: () => registeredCurveFor(rpc(), token)
13914
+ });
12983
13915
  const run = await runSequencedLegs(ctx, key, {
12984
13916
  wallet,
12985
13917
  first: sequenced,
@@ -12989,6 +13921,7 @@ async function hoodSwap(ctx, args) {
12989
13921
  clientId: id,
12990
13922
  kind,
12991
13923
  allowedLegs: HOOD_TRADE_LEGS,
13924
+ checkLeg,
12992
13925
  onLanded: async (_leg) => {
12993
13926
  const notice = await recordTradedToken(ctx, wallet.address, toChecksumAddress(recorded));
12994
13927
  if (notice)
@@ -13141,6 +14074,13 @@ async function bridgeSwap(ctx, args) {
13141
14074
  const problem = await tradingRead(ctx, reader, () => relaySolanaDepositProblem(transaction, wallet.address, reader.rpc));
13142
14075
  if (problem)
13143
14076
  throw refused(problem);
14077
+ const spent = await tradingRead(ctx, reader, () => solanaSpendProblem(transaction, wallet.address, reader.rpc, {
14078
+ kind: "swap",
14079
+ spend: from === "SOL" ? "SOL" : BASES[from]?.mint ?? from,
14080
+ maxSpendRaw: BigInt(amountRaw)
14081
+ }));
14082
+ if (spent)
14083
+ throw refused(spent);
13144
14084
  }
13145
14085
  const outDecimals = BRIDGE_ASSETS[to].decimals;
13146
14086
  const minimumRaw = (BigInt(data.expectedOutRaw) * BigInt(1e4 - args.slippage) / 10000n).toString();
@@ -13253,10 +14193,12 @@ var init_swap = __esm(() => {
13253
14193
  init_args();
13254
14194
  init_bridge();
13255
14195
  init_evm_lite();
14196
+ init_hood_legs();
13256
14197
  init_profiles();
13257
14198
  init_render();
13258
14199
  init_solana_endpoint();
13259
14200
  init_solana_lite();
14201
+ init_solana_verify();
13260
14202
  init_trading();
13261
14203
  settlementSchema = exports_external.object({
13262
14204
  state: exports_external.enum(["settled", "pending", "failed", "uncertain"]),
@@ -13596,6 +14538,7 @@ var KEYS_PATH = "/api/v1/agent/keys", ACCESS_LEVELS, NO_DEVICE_TOKEN;
13596
14538
  var init_keys = __esm(() => {
13597
14539
  init_agent_key_access();
13598
14540
  init_args();
14541
+ init_client();
13599
14542
  init_deps();
13600
14543
  init_profiles();
13601
14544
  init_render();
@@ -16293,6 +17236,7 @@ async function runRoleCheck(ctx, rpc, addresses) {
16293
17236
  }
16294
17237
  var SENTENCE_PREFIX = "You are about to accept ", CONFIRM_WORD = "confirm";
16295
17238
  var init_promote_support = __esm(() => {
17239
+ init_client();
16296
17240
  init_keys();
16297
17241
  init_deps();
16298
17242
  init_profiles();
@@ -16315,11 +17259,11 @@ function parseCloseArtifact(body) {
16315
17259
  return;
16316
17260
  return { transaction, accountKeys };
16317
17261
  }
16318
- function isTokenProgram(owner) {
17262
+ function isTokenProgram2(owner) {
16319
17263
  return owner === TOKEN_PROGRAM_ID || owner === TOKEN_2022_PROGRAM_ID;
16320
17264
  }
16321
17265
  function isMintAccount(view) {
16322
- if (view === null || !isTokenProgram(view.owner))
17266
+ if (view === null || !isTokenProgram2(view.owner))
16323
17267
  return false;
16324
17268
  if (view.data.length === MINT_BASE_SIZE)
16325
17269
  return true;
@@ -16383,8 +17327,8 @@ async function verifyCloseArtifact(input) {
16383
17327
  case COMPUTE_BUDGET_PROGRAM_ID:
16384
17328
  break;
16385
17329
  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)
17330
+ const kind = ix.data.length === 0 ? ATA_IX_CREATE2 : ix.data.length === 1 ? ix.data[0] : -1;
17331
+ if (kind !== ATA_IX_CREATE2 && kind !== ATA_IX_CREATE_IDEMPOTENT2)
16388
17332
  return refuse2(`instruction ${i} is an Associated Token instruction other than Create, which a close never needs`);
16389
17333
  if (account(0) !== tee || !allowedOwner(account(2) ?? ""))
16390
17334
  return refuse2(`instruction ${i} creates a token account for ${account(2) ?? "?"}, not the TEE wallet or its vault`);
@@ -16392,7 +17336,7 @@ async function verifyCloseArtifact(input) {
16392
17336
  }
16393
17337
  case TOKEN_PROGRAM_ID:
16394
17338
  case TOKEN_2022_PROGRAM_ID: {
16395
- if (ix.data[0] !== TOKEN_IX_CLOSE_ACCOUNT || ix.data.length !== 1)
17339
+ if (ix.data[0] !== TOKEN_IX_CLOSE_ACCOUNT2 || ix.data.length !== 1)
16396
17340
  return refuse2(`instruction ${i} is a token instruction other than CloseAccount, which a close never issues at the top level`);
16397
17341
  if (account(1) !== tee || account(2) !== tee)
16398
17342
  return refuse2(`instruction ${i} closes a token account to ${account(1) ?? "?"}, not the TEE wallet`);
@@ -16446,9 +17390,9 @@ async function verifyCloseArtifact(input) {
16446
17390
  if (before !== null) {
16447
17391
  const controlled = tokenBalanceOf(before);
16448
17392
  if (controlled !== undefined && allowedOwner(controlled.owner)) {
16449
- if (after.data.length < TOKEN_ACCOUNT_SIZE2)
17393
+ if (after.data.length < TOKEN_ACCOUNT_SIZE)
16450
17394
  return refuse2(`token account ${address} would no longer decode as a token account`);
16451
- for (const [start, end, what] of CONTROL_RANGES) {
17395
+ for (const [start, end, what] of TOKEN_CONTROL_RANGES) {
16452
17396
  if (!bytesEqual2(before.data.subarray(start, end), after.data.subarray(start, end)))
16453
17397
  return refuse2(`token account ${address} would change its ${what}, which a close never does`);
16454
17398
  }
@@ -16488,7 +17432,7 @@ async function verifyCloseArtifact(input) {
16488
17432
  continue;
16489
17433
  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
17434
  }
16491
- if (isTokenProgram(view.owner)) {
17435
+ if (isTokenProgram2(view.owner)) {
16492
17436
  if (key === nftMint)
16493
17437
  continue;
16494
17438
  const balance = tokenBalanceOf(view);
@@ -16548,7 +17492,7 @@ function describeClose(input) {
16548
17492
  lines.push("note the simulation is evidence, not a guarantee: a program can behave differently once signed");
16549
17493
  return lines;
16550
17494
  }
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;
17495
+ 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
17496
  var init_lp_close = __esm(() => {
16553
17497
  init_solana_alt();
16554
17498
  init_solana_lite();
@@ -16560,14 +17504,6 @@ var init_lp_close = __esm(() => {
16560
17504
  COMPUTE_BUDGET_PROGRAM_ID,
16561
17505
  SYSTEM_PROGRAM_ID
16562
17506
  ]);
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
17507
  });
16572
17508
 
16573
17509
  // src/sweep-pending.ts
@@ -27581,14 +28517,14 @@ function availabilityText(envelope, facts) {
27581
28517
  return availability.state === "available" ? availabilityLabel(availability) : `${availabilityLabel(availability)}: ${availability.reason}`;
27582
28518
  }
27583
28519
  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));
28520
+ const among2 = opts.among;
28521
+ const envelopes = among2 === undefined ? allEnvelopes : allEnvelopes.filter((envelope) => among2.envelopeIds.includes(envelope.id));
27586
28522
  const hasOtherFactor = allEnvelopes.some((envelope) => !isPassphraseEnvelope(envelope));
27587
28523
  let flag = requested;
27588
- if (among !== undefined && flag !== undefined && flag !== "passphrase") {
28524
+ if (among2 !== undefined && flag !== undefined && flag !== "passphrase") {
27589
28525
  const namesExcluded = envelopes.every((envelope) => !matchesFlag(envelope, flag)) && allEnvelopes.some((envelope) => matchesFlag(envelope, flag));
27590
28526
  if (namesExcluded) {
27591
- ctx.deps.stderr.write(`--factor ${flag} is not used here: ${among.excludedBecause}
28527
+ ctx.deps.stderr.write(`--factor ${flag} is not used here: ${among2.excludedBecause}
27592
28528
  `);
27593
28529
  flag = undefined;
27594
28530
  }
@@ -27615,8 +28551,8 @@ async function chooseFactor(ctx, vaultId, allEnvelopes, facts, requested, opts =
27615
28551
  }
27616
28552
  if (!hasOtherFactor)
27617
28553
  return { kind: "passphrase" };
27618
- if (among !== undefined)
27619
- return { kind: "passphrase", onlyBecause: among.because(unusable()) };
28554
+ if (among2 !== undefined)
28555
+ return { kind: "passphrase", onlyBecause: among2.because(unusable()) };
27620
28556
  const detail = unusable().map((entry) => `${entry.id} ${entry.word}: ${entry.availability}`).join("; ");
27621
28557
  return {
27622
28558
  kind: "passphrase",
@@ -28072,7 +29008,9 @@ async function completeLinkedWalletRead(ctx, apiKey) {
28072
29008
  };
28073
29009
  }
28074
29010
  var MAX_PAGES = 100, PAGE_LIMIT = 100;
28075
- var init_linked_wallets = () => {};
29011
+ var init_linked_wallets = __esm(() => {
29012
+ init_client();
29013
+ });
28076
29014
 
28077
29015
  // src/vault/reconcile-grant.ts
28078
29016
  async function reconcileGrant(ctx, entry, opts = {}) {
@@ -28254,6 +29192,7 @@ function sameWalletAddress(a, b) {
28254
29192
  return a === b;
28255
29193
  }
28256
29194
  var init_reconcile_grant = __esm(() => {
29195
+ init_client();
28257
29196
  init_deps();
28258
29197
  init_errors2();
28259
29198
  init_format();
@@ -31222,6 +32161,7 @@ async function runImportFlow(params) {
31222
32161
  }
31223
32162
  var TEE_PROFILE = "ember-tee";
31224
32163
  var init_wallet_import_flow = __esm(() => {
32164
+ init_client();
31225
32165
  init_secret_store();
31226
32166
  init_wallet_import();
31227
32167
  });
@@ -31402,12 +32342,12 @@ async function readEvmHoldings(rpc, address) {
31402
32342
  ]);
31403
32343
  return { eth, usdg, weth };
31404
32344
  }
31405
- function holdingsLines(address, holdings, observedAt) {
32345
+ function holdingsLines(address, holdings2, observedAt) {
31406
32346
  return [
31407
32347
  `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)`,
32348
+ ` ETH ${formatUnits(holdings2.eth, NATIVE_DECIMALS)} (${holdings2.eth} wei)`,
32349
+ ` USDG ${formatUnits(holdings2.usdg, HOOD_USDG_DECIMALS)} (${holdings2.usdg} raw)`,
32350
+ ` WETH ${formatUnits(holdings2.weth, NATIVE_DECIMALS)} (${holdings2.weth} raw)`,
31411
32351
  " Other tokens are not listed: an EVM RPC has no token-account listing."
31412
32352
  ];
31413
32353
  }
@@ -31528,6 +32468,7 @@ function assertWalletLockFree(entry, lock, now) {
31528
32468
  }
31529
32469
  var init_evm_tee = __esm(() => {
31530
32470
  init_bridge();
32471
+ init_client();
31531
32472
  init_deps();
31532
32473
  init_evm_lite();
31533
32474
  init_errors2();
@@ -32347,6 +33288,7 @@ var TEE_PROFILES, LINKED_WALLETS_PAGE_LIMIT = 100, LINKED_WALLETS_PAGE_CAP = 25,
32347
33288
  var init_wallets = __esm(() => {
32348
33289
  init_esm();
32349
33290
  init_args();
33291
+ init_client();
32350
33292
  init_deps();
32351
33293
  init_key_signers();
32352
33294
  init_profiles();
@@ -33218,6 +34160,7 @@ var MAX_TOKEN_TRANSFER_GAS = 1000000n;
33218
34160
  var init_tee_evm = __esm(() => {
33219
34161
  init_args();
33220
34162
  init_bridge();
34163
+ init_client();
33221
34164
  init_deps();
33222
34165
  init_evm_lite();
33223
34166
  init_profiles();
@@ -33433,6 +34376,7 @@ async function teeStatusEvm(ctx, parsed, address) {
33433
34376
  }
33434
34377
  var GAS_LOW_RESERVE_MULTIPLE = 2n;
33435
34378
  var init_tee_status_evm = __esm(() => {
34379
+ init_client();
33436
34380
  init_deps();
33437
34381
  init_evm_lite();
33438
34382
  init_render();
@@ -35294,6 +36238,7 @@ var init_tee = __esm(() => {
35294
36238
  init_esm();
35295
36239
  init_args();
35296
36240
  init_bridge();
36241
+ init_client();
35297
36242
  init_deps();
35298
36243
  init_lp_close();
35299
36244
  init_profiles();
@@ -35805,6 +36750,7 @@ async function teeRebinds(args, ctx) {
35805
36750
  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
36751
  var init_tee_rebind = __esm(() => {
35807
36752
  init_args();
36753
+ init_client();
35808
36754
  init_deps();
35809
36755
  init_key_signers();
35810
36756
  init_profiles();
@@ -36056,6 +37002,7 @@ function writeChanged(ctx, result, command) {
36056
37002
  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
37003
  var init_keys_embedded_wallet = __esm(() => {
36058
37004
  init_args();
37005
+ init_client();
36059
37006
  init_deps();
36060
37007
  init_profiles();
36061
37008
  init_render();
@@ -61108,13 +62055,52 @@ function assertTransportSecurity(apiUrl, env) {
61108
62055
  return;
61109
62056
  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
62057
  }
62058
+ function keyedRedirectGuard(doFetch) {
62059
+ return async (url, init) => {
62060
+ if (!init?.headers || !new Headers(init.headers).has("x-api-key"))
62061
+ return doFetch(url, init);
62062
+ let current = url;
62063
+ let next = { ...init, redirect: "manual" };
62064
+ for (let hops = 0;; hops++) {
62065
+ const response = await doFetch(current, next);
62066
+ const headers = response.headers;
62067
+ const location = REDIRECT_STATUSES2.has(response.status) ? headers?.get?.("location") : null;
62068
+ if (!location)
62069
+ return response;
62070
+ const from = new URL(current);
62071
+ const target = new URL(location, from);
62072
+ if (target.origin !== from.origin)
62073
+ throw new CrossOriginRedirectError2(from.origin, target.origin);
62074
+ if (hops >= MAX_REDIRECTS2)
62075
+ throw new Error(`More than ${MAX_REDIRECTS2} redirects from ${url}`);
62076
+ const method = (next.method ?? "GET").toUpperCase();
62077
+ if (response.status === 303 && method !== "GET" && method !== "HEAD" || (response.status === 301 || response.status === 302) && method === "POST") {
62078
+ next = { ...next, method: "GET", body: undefined };
62079
+ }
62080
+ current = target.href;
62081
+ }
62082
+ };
62083
+ }
61111
62084
  function resolveConfig(env = process.env) {
61112
62085
  const apiUrl = env.CANDLE_API_URL?.trim() || DEFAULT_API_URL2;
61113
62086
  assertTransportSecurity(apiUrl, env);
61114
62087
  const apiKey = env.CANDLE_AGENT_API_KEY?.trim() || env.CANDLE_API_KEY?.trim();
61115
62088
  return apiKey ? { apiUrl, apiKey } : { apiUrl };
61116
62089
  }
61117
- var DEFAULT_API_URL2 = "https://api.alpha.candle.tv";
62090
+ var DEFAULT_API_URL2 = "https://api.alpha.candle.tv", REDIRECT_STATUSES2, MAX_REDIRECTS2 = 5, CrossOriginRedirectError2;
62091
+ var init_client2 = __esm(() => {
62092
+ REDIRECT_STATUSES2 = new Set([301, 302, 303, 307, 308]);
62093
+ CrossOriginRedirectError2 = class CrossOriginRedirectError2 extends Error {
62094
+ from;
62095
+ to;
62096
+ constructor(from, to) {
62097
+ super(`The API at ${from} redirected to ${to}, another origin. Not followed, so the API key was not sent there.`);
62098
+ this.from = from;
62099
+ this.to = to;
62100
+ this.name = "CrossOriginRedirectError";
62101
+ }
62102
+ };
62103
+ });
61118
62104
 
61119
62105
  // ../mcp/src/convert.ts
61120
62106
  function defaultQuoteId(chain2) {
@@ -61162,7 +62148,7 @@ var init_convert = __esm(() => {
61162
62148
  });
61163
62149
 
61164
62150
  // ../mcp/src/version.ts
61165
- var SERVER_VERSION = "0.10.16";
62151
+ var SERVER_VERSION = "0.10.18";
61166
62152
 
61167
62153
  // ../mcp/src/update-notice.ts
61168
62154
  function newer(a, b) {
@@ -62097,6 +63083,12 @@ function checkActionIntent2(action, intent) {
62097
63083
  const trigger = leg.t?.trigger;
62098
63084
  if (leg.a !== first.a || leg.b !== !buy || leg.r !== true || decimal2(leg.s) !== decimal2(p.size) || trigger?.tpsl !== kind || trigger.isMarket !== true || decimal2(trigger.triggerPx) !== decimal2(p[field]))
62099
63085
  return "trigger side, size, reduce-only flag or price does not match the request";
63086
+ const px = decimal2(leg.p) ?? 0n;
63087
+ const triggerPx = decimal2(trigger.triggerPx) ?? 0n;
63088
+ const bound = BigInt(HYPERLIQUID_TPSL_SLIPPAGE_BPS2 + HYPERLIQUID_MID_DRIFT_BPS2);
63089
+ const within = leg.b === true ? px * 10000n <= triggerPx * (10000n + bound) : px * 10000n >= triggerPx * (10000n - bound);
63090
+ if (triggerPx <= 0n || !within)
63091
+ return "trigger order price is outside its slippage of the trigger price";
62100
63092
  }
62101
63093
  } else {
62102
63094
  if (list.length !== 1)
@@ -62136,6 +63128,65 @@ async function hyperliquidCloseOrder2(fetcher, network2, address, params) {
62136
63128
  const size = asked < held ? String(params.size) : String(szi).replace(/^-/, "");
62137
63129
  return { asset, isBuy: signed < 0n, size };
62138
63130
  }
63131
+ async function hyperliquidOrderMarket2(fetcher, network2, address, method, params) {
63132
+ const read = async (body) => {
63133
+ const res = await fetcher(HYPERLIQUID_INFO_URLS2[network2], {
63134
+ method: "POST",
63135
+ headers: { "content-type": "application/json" },
63136
+ body: JSON.stringify(body)
63137
+ });
63138
+ if (!res.ok)
63139
+ throw new Error("Cannot read the market from Hyperliquid");
63140
+ return JSON.parse(await res.text());
63141
+ };
63142
+ let coin = String(params.coin ?? "").toUpperCase();
63143
+ let order;
63144
+ if (method === "modify") {
63145
+ const status = await read({ type: "orderStatus", user: address, oid: params.cloid });
63146
+ const resting = status?.order?.order ?? null;
63147
+ if (!resting || typeof resting.coin !== "string" || decimal2(resting.limitPx) === null)
63148
+ throw new Error("No resting order with this cloid on Hyperliquid");
63149
+ coin = resting.coin.toUpperCase();
63150
+ order = { isBuy: resting.side === "B", limitPx: String(resting.limitPx) };
63151
+ }
63152
+ const [meta2, contexts] = await read({ type: "metaAndAssetCtxs" });
63153
+ const asset = meta2.universe.findIndex((m) => m.name.toUpperCase() === coin);
63154
+ if (asset < 0 || asset >= 1e4)
63155
+ throw new Error("No main-universe market for this coin on Hyperliquid");
63156
+ const context = contexts[asset];
63157
+ const midPx = context?.midPx ?? context?.markPx;
63158
+ if ((decimal2(midPx) ?? 0n) <= 0n)
63159
+ throw new Error("No Hyperliquid price for this coin");
63160
+ return { asset, midPx: String(midPx), ...order ? { order } : {} };
63161
+ }
63162
+ function checkOrderMarket2(action, intent, market) {
63163
+ const list = action.type === "modify" ? [action.order] : action.orders;
63164
+ if (!Array.isArray(list) || list.length === 0)
63165
+ return "missing requested order";
63166
+ if (list.some((o) => !o || o.a !== market.asset))
63167
+ return "order asset is not the requested coin's on Hyperliquid";
63168
+ const first = list[0];
63169
+ const p = intent.params;
63170
+ if (intent.method === "open" && p.price == null) {
63171
+ const requested = p.slippageBps;
63172
+ const slippage2 = typeof requested === "number" && Number.isInteger(requested) && requested > 0 ? requested : HYPERLIQUID_DEFAULT_SLIPPAGE_BPS2;
63173
+ const bound = BigInt(slippage2 + HYPERLIQUID_MID_DRIFT_BPS2);
63174
+ const px = decimal2(first.p) ?? 0n;
63175
+ const mid = decimal2(market.midPx) ?? 0n;
63176
+ const within = first.b === true ? px * 10000n <= mid * (10000n + bound) : px * 10000n >= mid * (10000n - bound);
63177
+ if (mid <= 0n || !within)
63178
+ return "market order price is outside its slippage of Hyperliquid's mid";
63179
+ }
63180
+ if (intent.method === "modify") {
63181
+ if (!market.order)
63182
+ return "no resting order on Hyperliquid to check the modify against";
63183
+ if (first.b !== market.order.isBuy)
63184
+ return "modify changes the resting order's side";
63185
+ if (p.price == null && decimal2(first.p) !== decimal2(market.order.limitPx))
63186
+ return "modify changes the resting order's price";
63187
+ }
63188
+ return null;
63189
+ }
62139
63190
  function verifyPerpsBuild2(build, opts) {
62140
63191
  try {
62141
63192
  const network2 = opts.network ?? "mainnet";
@@ -62163,6 +63214,13 @@ function verifyPerpsBuild2(build, opts) {
62163
63214
  if (!order || order.a !== opts.closeOrder.asset || order.b !== opts.closeOrder.isBuy || decimal2(order.s) !== decimal2(opts.closeOrder.size) || order.r !== true)
62164
63215
  return { ok: false, reason: "close side, asset or size does not match the requested position" };
62165
63216
  }
63217
+ if (opts.market) {
63218
+ if (opts.intent?.method !== "open" && opts.intent?.method !== "modify")
63219
+ return { ok: false, reason: "a market check needs an open or modify intent" };
63220
+ const marketRefusal = checkOrderMarket2(action, opts.intent, opts.market);
63221
+ if (marketRefusal)
63222
+ return { ok: false, reason: marketRefusal };
63223
+ }
62166
63224
  if (typed.primary_type === "Agent") {
62167
63225
  const type = action.type;
62168
63226
  if (typeof type !== "string" || !HYPERLIQUID_ALLOWED_ACTION_TYPES2.includes(type)) {
@@ -62220,7 +63278,7 @@ function hyperliquidExchangeBody2(action, nonce, signature) {
62220
63278
  function hyperliquidRelayBody2(typedData) {
62221
63279
  return { method: "eth_signTypedData_v4", params: { typed_data: typedData } };
62222
63280
  }
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;
63281
+ 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, HYPERLIQUID_TPSL_SLIPPAGE_BPS2 = 500;
62224
63282
  var init_hyperliquid = __esm(() => {
62225
63283
  init_sha3();
62226
63284
  HYPERLIQUID_ALLOWED_ACTION_TYPES2 = [
@@ -62234,6 +63292,10 @@ var init_hyperliquid = __esm(() => {
62234
63292
  mainnet: "https://api.hyperliquid.xyz/exchange",
62235
63293
  testnet: "https://api.hyperliquid-testnet.xyz/exchange"
62236
63294
  };
63295
+ HYPERLIQUID_INFO_URLS2 = {
63296
+ mainnet: "https://api.hyperliquid.xyz/info",
63297
+ testnet: "https://api.hyperliquid-testnet.xyz/info"
63298
+ };
62237
63299
  UINT64_MAX2 = (1n << 64n) - 1n;
62238
63300
  INT64_MIN2 = -(1n << 63n);
62239
63301
  EVM_ADDRESS_RE2 = /^0x[0-9a-fA-F]{40}$/;
@@ -62344,6 +63406,18 @@ async function executePerps(tool, args, cfg, env, fetch2) {
62344
63406
  if (!closeCheck.ok)
62345
63407
  return fail3("PERPS_BUILD_REFUSED", `Refused to sign this build: ${closeCheck.reason}. Nothing was signed.`);
62346
63408
  }
63409
+ if (method === "open") {
63410
+ const market = await hyperliquidOrderMarket2(fetch2, network2, row.address, method, fields);
63411
+ const marketCheck = verifyPerpsBuild2(build, {
63412
+ builder,
63413
+ network: network2,
63414
+ address: row.address,
63415
+ intent: { method, params: fields },
63416
+ market
63417
+ });
63418
+ if (!marketCheck.ok)
63419
+ return fail3("PERPS_BUILD_REFUSED", `Refused to sign this build: ${marketCheck.reason}. Nothing was signed.`);
63420
+ }
62347
63421
  if (!willSubmit || signerPem === null)
62348
63422
  return { text: JSON.stringify({ ...build, signed: false, submitted: false }) };
62349
63423
  if (!appId || !row.privyWalletId)
@@ -62705,7 +63779,7 @@ var init_plans = __esm(() => {
62705
63779
  createLinkedWallets: "Create linked wallets",
62706
63780
  importLinkedWallets: "Import linked wallets",
62707
63781
  limitOrders: "Limit orders",
62708
- quant: "Quant on Telegram"
63782
+ quant: "Quant deep analyses (Max)"
62709
63783
  };
62710
63784
  PLAN_LABELS2 = { free: "Free", believer: "Believer", pro: "Pro", max: "Max" };
62711
63785
  });
@@ -63080,7 +64154,7 @@ function registerToolSubset(server, options) {
63080
64154
  const allowed = new Set(options.tools);
63081
64155
  const getConfig = options.getConfig;
63082
64156
  const env = options.env ?? {};
63083
- const doFetch = options.fetch ?? ((url, init) => fetch(url, init));
64157
+ const doFetch = keyedRedirectGuard(options.fetch ?? ((url, init) => fetch(url, init)));
63084
64158
  const register = (name2, definition, handler) => {
63085
64159
  if (!allowed.has(name2))
63086
64160
  return;
@@ -63241,7 +64315,7 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
63241
64315
  }, async (args) => callAndRelay("candle_get_fees", args, getConfig(), doFetch));
63242
64316
  register("candle_claim_fees", {
63243
64317
  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.",
64318
+ 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
64319
  inputSchema: claimFeesShape
63246
64320
  }, async (args) => callAndRelay("candle_claim_fees", args, getConfig(), doFetch));
63247
64321
  register("candle_get_wallets", {
@@ -63407,7 +64481,7 @@ async function registerEligibleProfileTool(server, env = process.env, doFetch =
63407
64481
  let eligible = false;
63408
64482
  if (cfg.apiKey && wanted.length > 0) {
63409
64483
  try {
63410
- const res = await doFetch(`${cfg.apiUrl}/api/v1/agent/keys/self/public-profile`, {
64484
+ const res = await keyedRedirectGuard(doFetch)(`${cfg.apiUrl}/api/v1/agent/keys/self/public-profile`, {
63411
64485
  method: "GET",
63412
64486
  headers: { "x-api-key": cfg.apiKey },
63413
64487
  signal: AbortSignal.timeout(15000)
@@ -63429,9 +64503,10 @@ async function registerEligibleProfileTool(server, env = process.env, doFetch =
63429
64503
  }
63430
64504
  return eligible;
63431
64505
  }
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;
64506
+ 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
64507
  var init_tools = __esm(() => {
63434
64508
  init_zod();
64509
+ init_client2();
63435
64510
  init_convert();
63436
64511
  init_image();
63437
64512
  init_market_disclosure();
@@ -63440,7 +64515,7 @@ var init_tools = __esm(() => {
63440
64515
  init_plans();
63441
64516
  init_update_notice();
63442
64517
  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.";
64518
+ 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
64519
  localProfileTools = new WeakMap;
63445
64520
  stakingWriteShape = {
63446
64521
  payer: exports_external.enum(["main", "linked"]).optional().describe("main is the embedded wallet; linked names a linked or TEE wallet"),
@@ -63739,6 +64814,7 @@ Full reference, error catalogue and end-to-end recipes: https://docs.candle.tv
63739
64814
  });
63740
64815
 
63741
64816
  // src/index.ts
64817
+ init_client();
63742
64818
  import { spawn as spawn3 } from "node:child_process";
63743
64819
  import { realpathSync } from "node:fs";
63744
64820
  import { chmod as chmod8, readFile as readFile10, realpath as realpath2, rename as rename6, unlink, writeFile as writeFile7 } from "node:fs/promises";
@@ -63747,6 +64823,7 @@ import { pathToFileURL } from "node:url";
63747
64823
 
63748
64824
  // src/commands/agent-post.ts
63749
64825
  init_args();
64826
+ init_client();
63750
64827
  init_render();
63751
64828
  init_trading();
63752
64829
  init_swap();
@@ -63842,6 +64919,7 @@ ${USAGE}`);
63842
64919
 
63843
64920
  // src/commands/agent-profile.ts
63844
64921
  init_args();
64922
+ init_client();
63845
64923
  init_render();
63846
64924
  init_trading();
63847
64925
  init_swap();
@@ -63907,6 +64985,7 @@ import { homedir as homedir3 } from "node:os";
63907
64985
  import { join as join8 } from "node:path";
63908
64986
 
63909
64987
  // src/account.ts
64988
+ init_client();
63910
64989
  async function fetchAccount(deps, apiUrl, apiKey) {
63911
64990
  const identity = await apiRequest("/api/v1/agent/wallets/embedded", {
63912
64991
  auth: "key",
@@ -63927,6 +65006,7 @@ init_agent_key_access();
63927
65006
  init_args();
63928
65007
 
63929
65008
  // src/checks.ts
65009
+ init_client();
63930
65010
  init_render();
63931
65011
  async function runLiveCheck(params) {
63932
65012
  const { deps, apiUrl, path, auth, credential, check, passDetail } = params;
@@ -63941,13 +65021,14 @@ async function runLiveCheck(params) {
63941
65021
  }
63942
65022
 
63943
65023
  // src/commands/auth.ts
65024
+ init_client();
63944
65025
  init_deps();
63945
65026
  init_profiles();
63946
65027
  init_render();
63947
65028
  init_secret_store();
63948
65029
 
63949
65030
  // src/version.ts
63950
- var CLI_VERSION = "0.11.28";
65031
+ var CLI_VERSION = "0.11.30";
63951
65032
 
63952
65033
  // src/commands/auth.ts
63953
65034
  init_keys_embedded_wallet();
@@ -64487,7 +65568,7 @@ var HELP = {
64487
65568
  stake: {
64488
65569
  group: "Trade",
64489
65570
  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.",
65571
+ 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
65572
  usage: [
64492
65573
  "candle stake [--wallet <wallet>]",
64493
65574
  "candle stake add <amount>",
@@ -64779,7 +65860,7 @@ var HELP = {
64779
65860
  fees: {
64780
65861
  group: "Account",
64781
65862
  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.",
65863
+ 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
65864
  usage: ["candle fees [--cursor <cursor>] [--limit <1-50>]", "candle fees claim <mint> [--dry-run]"],
64784
65865
  rows: [
64785
65866
  {
@@ -65295,10 +66376,10 @@ var HELP = {
65295
66376
  examples: ["candle help", "candle help vault", "candle help completion"]
65296
66377
  }
65297
66378
  };
65298
- function topicFor(word2) {
65299
- if (Object.hasOwn(HELP, word2))
65300
- return HELP[word2];
65301
- return Object.values(HELP).find((topic) => topic.display === word2);
66379
+ function topicFor(word3) {
66380
+ if (Object.hasOwn(HELP, word3))
66381
+ return HELP[word3];
66382
+ return Object.values(HELP).find((topic) => topic.display === word3);
65302
66383
  }
65303
66384
  function displayName(canonical) {
65304
66385
  return HELP[canonical]?.display ?? canonical;
@@ -65332,14 +66413,14 @@ function wrap(text, width) {
65332
66413
  return [text];
65333
66414
  const lines = [];
65334
66415
  let line = "";
65335
- for (const word2 of text.split(" ")) {
66416
+ for (const word3 of text.split(" ")) {
65336
66417
  if (line === "")
65337
- line = word2;
65338
- else if (`${line} ${word2}`.length <= width)
65339
- line = `${line} ${word2}`;
66418
+ line = word3;
66419
+ else if (`${line} ${word3}`.length <= width)
66420
+ line = `${line} ${word3}`;
65340
66421
  else {
65341
66422
  lines.push(line);
65342
- line = word2;
66423
+ line = word3;
65343
66424
  }
65344
66425
  }
65345
66426
  if (line !== "")
@@ -65376,14 +66457,14 @@ function renderTopLevel() {
65376
66457
  " candle help <command> a command's subcommands, flags and examples"
65377
66458
  ];
65378
66459
  const entries = Object.entries(HELP);
65379
- const longest = Math.max(...entries.map(([word2]) => displayName(word2).length));
66460
+ const longest = Math.max(...entries.map(([word3]) => displayName(word3).length));
65380
66461
  const column = 2 + longest + 2;
65381
66462
  for (const group of GROUPS) {
65382
66463
  out.push("", group);
65383
- for (const [word2, topic] of entries) {
66464
+ for (const [word3, topic] of entries) {
65384
66465
  if (topic.group !== group)
65385
66466
  continue;
65386
- out.push(` ${displayName(word2).padEnd(column - 2)}${topic.summary}`);
66467
+ out.push(` ${displayName(word3).padEnd(column - 2)}${topic.summary}`);
65387
66468
  }
65388
66469
  }
65389
66470
  out.push("", "Global flags", ...renderRows(GLOBAL_FLAGS, 28));
@@ -65393,11 +66474,11 @@ function renderTopLevel() {
65393
66474
  `)}
65394
66475
  `;
65395
66476
  }
65396
- function renderTopic(word2) {
65397
- const topic = topicFor(word2);
66477
+ function renderTopic(word3) {
66478
+ const topic = topicFor(word3);
65398
66479
  if (!topic)
65399
66480
  return;
65400
- const canonical = Object.keys(HELP).find((key) => HELP[key] === topic) ?? word2;
66481
+ const canonical = Object.keys(HELP).find((key) => HELP[key] === topic) ?? word3;
65401
66482
  const display = displayName(canonical);
65402
66483
  const out = [wrap(`candle ${display}: ${topic.description}`, WIDTH).join(`
65403
66484
  `)];
@@ -65465,8 +66546,8 @@ function zshScript() {
65465
66546
  " _candle_flags=()",
65466
66547
  ' case "${words[2]}" in'
65467
66548
  ];
65468
- for (const { word: word2, subcommands, flags } of perWord()) {
65469
- lines.push(` ${word2})`);
66549
+ for (const { word: word3, subcommands, flags } of perWord()) {
66550
+ lines.push(` ${word3})`);
65470
66551
  if (subcommands.length > 0)
65471
66552
  lines.push(` _candle_subs=(${subcommands.join(" ")})`);
65472
66553
  if (flags.length > 0)
@@ -65495,8 +66576,8 @@ function bashScript() {
65495
66576
  " fi",
65496
66577
  ' case "${COMP_WORDS[1]}" in'
65497
66578
  ];
65498
- for (const { word: word2, subcommands, flags } of perWord()) {
65499
- lines.push(` ${word2})`);
66579
+ for (const { word: word3, subcommands, flags } of perWord()) {
66580
+ lines.push(` ${word3})`);
65500
66581
  if (subcommands.length > 0)
65501
66582
  lines.push(` candle_subs="${subcommands.join(" ")}"`);
65502
66583
  if (flags.length > 0)
@@ -65516,8 +66597,8 @@ function fishScript() {
65516
66597
  ""
65517
66598
  ];
65518
66599
  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)}'`);
66600
+ for (const word3 of topic.display ? [canonical, topic.display] : [canonical]) {
66601
+ lines.push(`complete -c candle -n '__fish_use_subcommand' -a '${word3}' -d '${safe(topic.summary)}'`);
65521
66602
  }
65522
66603
  }
65523
66604
  lines.push("");
@@ -65530,18 +66611,18 @@ function fishScript() {
65530
66611
  }
65531
66612
  }
65532
66613
  }
65533
- for (const { word: word2, subcommands, flags } of perWord()) {
66614
+ for (const { word: word3, subcommands, flags } of perWord()) {
65534
66615
  if (subcommands.length === 0 && flags.length === 0)
65535
66616
  continue;
65536
66617
  lines.push("");
65537
66618
  for (const sub of subcommands) {
65538
- lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${word2}' -a '${sub}'`);
66619
+ lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${word3}' -a '${sub}'`);
65539
66620
  }
65540
66621
  for (const flag of flags) {
65541
66622
  if (flag.startsWith("--")) {
65542
- lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${word2}' -l '${flag.slice(2)}'`);
66623
+ lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${word3}' -l '${flag.slice(2)}'`);
65543
66624
  } else
65544
- lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${word2}' -s '${flag.slice(1)}'`);
66625
+ lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${word3}' -s '${flag.slice(1)}'`);
65545
66626
  }
65546
66627
  }
65547
66628
  lines.push("");
@@ -65580,6 +66661,7 @@ async function completion(args, ctx) {
65580
66661
  // src/commands/doctor.ts
65581
66662
  init_agent_key_access();
65582
66663
  init_args();
66664
+ init_client();
65583
66665
  init_deps();
65584
66666
 
65585
66667
  // src/keychain.ts
@@ -65773,6 +66855,7 @@ init_store();
65773
66855
 
65774
66856
  // src/commands/key-signer.ts
65775
66857
  init_args();
66858
+ init_client();
65776
66859
  init_deps();
65777
66860
  init_key_signers();
65778
66861
  import { generateKeyPairSync as generateKeyPairSync2 } from "node:crypto";
@@ -65781,6 +66864,7 @@ init_render();
65781
66864
  init_secret_store();
65782
66865
 
65783
66866
  // src/vault/promote-to-key.ts
66867
+ init_client();
65784
66868
  init_tee_rebind();
65785
66869
  init_deps();
65786
66870
  init_key_signers();
@@ -70773,14 +71857,14 @@ async function feesClaim(args, ctx) {
70773
71857
 
70774
71858
  // src/commands/help.ts
70775
71859
  async function help(args, ctx) {
70776
- const word2 = args[0];
70777
- if (word2 === undefined) {
71860
+ const word3 = args[0];
71861
+ if (word3 === undefined) {
70778
71862
  ctx.deps.stdout.write(renderTopLevel());
70779
71863
  return 0;
70780
71864
  }
70781
- const topic = renderTopic(word2);
71865
+ const topic = renderTopic(word3);
70782
71866
  if (topic === undefined) {
70783
- ctx.deps.stderr.write(`Unknown command: ${word2}
71867
+ ctx.deps.stderr.write(`Unknown command: ${word3}
70784
71868
  `);
70785
71869
  ctx.deps.stderr.write(renderTopLevel());
70786
71870
  return 1;
@@ -70791,6 +71875,7 @@ async function help(args, ctx) {
70791
71875
 
70792
71876
  // src/commands/image.ts
70793
71877
  init_args();
71878
+ init_client();
70794
71879
  init_deps();
70795
71880
  init_render();
70796
71881
  init_trading();
@@ -70910,6 +71995,7 @@ init_keys();
70910
71995
  // src/commands/keys-access.ts
70911
71996
  init_agent_key_access();
70912
71997
  init_args();
71998
+ init_client();
70913
71999
  init_deps();
70914
72000
  init_profiles();
70915
72001
  init_render();
@@ -71236,6 +72322,7 @@ init_keys_embedded_wallet();
71236
72322
 
71237
72323
  // src/commands/keys-wallets.ts
71238
72324
  init_args();
72325
+ init_client();
71239
72326
  init_deps();
71240
72327
  init_profiles();
71241
72328
  init_render();
@@ -71418,6 +72505,7 @@ async function keysWallets(args, ctx) {
71418
72505
  // src/commands/launch.ts
71419
72506
  init_args();
71420
72507
  init_evm_lite();
72508
+ init_hood_legs();
71421
72509
  init_render();
71422
72510
  init_solana_endpoint();
71423
72511
  init_solana_lite();
@@ -71432,7 +72520,7 @@ var QUOTE_CHAIN = {
71432
72520
  eth: "hood",
71433
72521
  usdg: "hood"
71434
72522
  };
71435
- var HOOD_LAUNCH_LEGS = ["createCurve", "feeTransfer"];
72523
+ var HOOD_LAUNCH_LEGS = ["createCurve"];
71436
72524
  var HOOD_LAUNCH_BUY_LEGS = ["createCurve", "feeTransfer", "trade"];
71437
72525
  function launchBuy(flags, chain2) {
71438
72526
  const amount = flags["--buy"];
@@ -71677,6 +72765,9 @@ async function hoodLaunch(ctx, key, args) {
71677
72765
  curveAddress: curve.address,
71678
72766
  operationId: first.operationId
71679
72767
  };
72768
+ const feeCap = buy && first.plannedLegCount === 3 ? launchFeeCap(buy.raw) : undefined;
72769
+ if (feeCap !== undefined)
72770
+ 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
72771
  ctx.deps.stderr.write(buyNotice(buy, "hood"));
71681
72772
  if (!await confirmQuote(ctx, quote, args.yes)) {
71682
72773
  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 +72786,9 @@ async function hoodLaunch(ctx, key, args) {
71695
72786
  }
71696
72787
  const confirmedPlan = buy ? first.plannedLegCount === 3 ? ["createCurve", "feeTransfer", "trade"] : ["createCurve", "trade"] : undefined;
71697
72788
  let position = 0;
72789
+ let createCurveHash;
72790
+ let quotedCurve;
72791
+ const evm = lazyEvmRpc(ctx, flags["--rpc-url"]);
71698
72792
  const run2 = await runSequencedLegs(ctx, key, {
71699
72793
  wallet,
71700
72794
  first,
@@ -71705,10 +72799,33 @@ async function hoodLaunch(ctx, key, args) {
71705
72799
  kind: "launch",
71706
72800
  primaryLeg: "createCurve",
71707
72801
  allowedLegs,
71708
- checkLeg: (kind, leg2) => {
72802
+ onSigned: (kind, hash) => {
72803
+ if (kind === "createCurve")
72804
+ createCurveHash = hash;
72805
+ },
72806
+ checkLeg: async (kind, leg2) => {
72807
+ const buyCurve = () => {
72808
+ if (createCurveHash === undefined)
72809
+ return Promise.resolve(undefined);
72810
+ quotedCurve ??= launchedCurve(ctx, evm(), createCurveHash, wallet.address).then((created2) => created2 !== undefined && sameEvmAddress(created2, curve.address) ? created2 : undefined);
72811
+ return quotedCurve;
72812
+ };
71709
72813
  if (confirmedPlan && confirmedPlan[position++] !== kind)
71710
72814
  return false;
71711
- return kind === "createCurve" ? leg2.value === "0" : kind !== "trade" || buy !== undefined && leg2.value === buy.raw && sameEvmAddress(leg2.to, curve.address);
72815
+ if (kind === "createCurve")
72816
+ return await createCurveLegOk(leg2, wallet.address, evm());
72817
+ if (kind === "feeTransfer") {
72818
+ if (feeCap === undefined)
72819
+ return false;
72820
+ if (buy !== undefined && await buyCurve() === undefined)
72821
+ return false;
72822
+ const treasury = await hoodFeeTreasury(evm());
72823
+ return treasury !== undefined && feeLegOk(leg2, { cap: feeCap, asset: "eth", treasury });
72824
+ }
72825
+ if (kind !== "trade" || buy === undefined)
72826
+ return false;
72827
+ const created = await buyCurve();
72828
+ return created !== undefined && launchBuyLegOk(leg2, buy.raw, created);
71712
72829
  },
71713
72830
  onLanded: async () => {}
71714
72831
  });
@@ -71727,14 +72844,27 @@ async function hoodLaunch(ctx, key, args) {
71727
72844
  ...args.used
71728
72845
  });
71729
72846
  }
72847
+ var RECEIPT_ATTEMPTS = 5;
72848
+ var RECEIPT_RETRY_MS = 1000;
72849
+ async function launchedCurve(ctx, rpc, hash, wallet) {
72850
+ for (let attempt = 1;; attempt++) {
72851
+ let receipt = null;
72852
+ try {
72853
+ receipt = await rpc.getTransactionReceipt(hash);
72854
+ } catch {}
72855
+ if (receipt !== null)
72856
+ return curveCreatedBy(receipt, wallet);
72857
+ if (attempt >= RECEIPT_ATTEMPTS)
72858
+ return;
72859
+ await ctx.deps.sleep(RECEIPT_RETRY_MS);
72860
+ }
72861
+ }
71730
72862
  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";
72863
+ return count < 2 || !buys ? "" : "; the later legs are priced by Candle when each becomes next";
71734
72864
  }
71735
72865
  function hoodLaunchLegNames(count, buys) {
71736
72866
  if (!buys)
71737
- return count === 2 ? ["create curve", "fee"] : ["create curve"];
72867
+ return ["create curve"];
71738
72868
  return count === 3 ? ["create curve", "fee", "first buy"] : ["create curve", "first buy"];
71739
72869
  }
71740
72870
  async function recordLaunchedToken(ctx, wallet, final) {
@@ -71759,6 +72889,7 @@ async function resumeHoodLaunch(ctx, key, id, operationId, prior) {
71759
72889
 
71760
72890
  // src/commands/lp.ts
71761
72891
  init_args();
72892
+ init_client();
71762
72893
  init_render();
71763
72894
  init_solana_endpoint();
71764
72895
  init_solana_lite();
@@ -72301,6 +73432,7 @@ async function mcp(args, ctx) {
72301
73432
  // src/commands/perps.ts
72302
73433
  init_args();
72303
73434
  init_bridge();
73435
+ init_client();
72304
73436
  init_evm_lite();
72305
73437
  import { randomUUID as randomUUID4, sign as sign3 } from "node:crypto";
72306
73438
 
@@ -72317,6 +73449,10 @@ var HYPERLIQUID_EXCHANGE_URLS = {
72317
73449
  mainnet: "https://api.hyperliquid.xyz/exchange",
72318
73450
  testnet: "https://api.hyperliquid-testnet.xyz/exchange"
72319
73451
  };
73452
+ var HYPERLIQUID_INFO_URLS = {
73453
+ mainnet: "https://api.hyperliquid.xyz/info",
73454
+ testnet: "https://api.hyperliquid-testnet.xyz/info"
73455
+ };
72320
73456
  var CANDLE_HYPERLIQUID_BUILDER_ADDRESS = null;
72321
73457
  var HYPERLIQUID_MAX_BUILDER_FEE_TENTHS_BPS = 100;
72322
73458
  var HYPERLIQUID_MAX_BUILDER_FEE_RATE = "0.1%";
@@ -72583,6 +73719,12 @@ function checkActionIntent(action, intent) {
72583
73719
  const trigger = leg.t?.trigger;
72584
73720
  if (leg.a !== first.a || leg.b !== !buy || leg.r !== true || decimal(leg.s) !== decimal(p.size) || trigger?.tpsl !== kind || trigger.isMarket !== true || decimal(trigger.triggerPx) !== decimal(p[field]))
72585
73721
  return "trigger side, size, reduce-only flag or price does not match the request";
73722
+ const px = decimal(leg.p) ?? 0n;
73723
+ const triggerPx = decimal(trigger.triggerPx) ?? 0n;
73724
+ const bound = BigInt(HYPERLIQUID_TPSL_SLIPPAGE_BPS + HYPERLIQUID_MID_DRIFT_BPS);
73725
+ const within = leg.b === true ? px * 10000n <= triggerPx * (10000n + bound) : px * 10000n >= triggerPx * (10000n - bound);
73726
+ if (triggerPx <= 0n || !within)
73727
+ return "trigger order price is outside its slippage of the trigger price";
72586
73728
  }
72587
73729
  } else {
72588
73730
  if (list.length !== 1)
@@ -72622,6 +73764,68 @@ async function hyperliquidCloseOrder(fetcher, network, address, params) {
72622
73764
  const size = asked < held ? String(params.size) : String(szi).replace(/^-/, "");
72623
73765
  return { asset, isBuy: signed < 0n, size };
72624
73766
  }
73767
+ var HYPERLIQUID_DEFAULT_SLIPPAGE_BPS = 100;
73768
+ var HYPERLIQUID_MID_DRIFT_BPS = 100;
73769
+ var HYPERLIQUID_TPSL_SLIPPAGE_BPS = 500;
73770
+ async function hyperliquidOrderMarket(fetcher, network, address, method, params) {
73771
+ const read = async (body) => {
73772
+ const res = await fetcher(HYPERLIQUID_INFO_URLS[network], {
73773
+ method: "POST",
73774
+ headers: { "content-type": "application/json" },
73775
+ body: JSON.stringify(body)
73776
+ });
73777
+ if (!res.ok)
73778
+ throw new Error("Cannot read the market from Hyperliquid");
73779
+ return JSON.parse(await res.text());
73780
+ };
73781
+ let coin = String(params.coin ?? "").toUpperCase();
73782
+ let order;
73783
+ if (method === "modify") {
73784
+ const status = await read({ type: "orderStatus", user: address, oid: params.cloid });
73785
+ const resting = status?.order?.order ?? null;
73786
+ if (!resting || typeof resting.coin !== "string" || decimal(resting.limitPx) === null)
73787
+ throw new Error("No resting order with this cloid on Hyperliquid");
73788
+ coin = resting.coin.toUpperCase();
73789
+ order = { isBuy: resting.side === "B", limitPx: String(resting.limitPx) };
73790
+ }
73791
+ const [meta, contexts] = await read({ type: "metaAndAssetCtxs" });
73792
+ const asset = meta.universe.findIndex((m) => m.name.toUpperCase() === coin);
73793
+ if (asset < 0 || asset >= 1e4)
73794
+ throw new Error("No main-universe market for this coin on Hyperliquid");
73795
+ const context = contexts[asset];
73796
+ const midPx = context?.midPx ?? context?.markPx;
73797
+ if ((decimal(midPx) ?? 0n) <= 0n)
73798
+ throw new Error("No Hyperliquid price for this coin");
73799
+ return { asset, midPx: String(midPx), ...order ? { order } : {} };
73800
+ }
73801
+ function checkOrderMarket(action, intent, market) {
73802
+ const list = action.type === "modify" ? [action.order] : action.orders;
73803
+ if (!Array.isArray(list) || list.length === 0)
73804
+ return "missing requested order";
73805
+ if (list.some((o) => !o || o.a !== market.asset))
73806
+ return "order asset is not the requested coin's on Hyperliquid";
73807
+ const first = list[0];
73808
+ const p = intent.params;
73809
+ if (intent.method === "open" && p.price == null) {
73810
+ const requested = p.slippageBps;
73811
+ const slippage = typeof requested === "number" && Number.isInteger(requested) && requested > 0 ? requested : HYPERLIQUID_DEFAULT_SLIPPAGE_BPS;
73812
+ const bound = BigInt(slippage + HYPERLIQUID_MID_DRIFT_BPS);
73813
+ const px = decimal(first.p) ?? 0n;
73814
+ const mid = decimal(market.midPx) ?? 0n;
73815
+ const within = first.b === true ? px * 10000n <= mid * (10000n + bound) : px * 10000n >= mid * (10000n - bound);
73816
+ if (mid <= 0n || !within)
73817
+ return "market order price is outside its slippage of Hyperliquid's mid";
73818
+ }
73819
+ if (intent.method === "modify") {
73820
+ if (!market.order)
73821
+ return "no resting order on Hyperliquid to check the modify against";
73822
+ if (first.b !== market.order.isBuy)
73823
+ return "modify changes the resting order's side";
73824
+ if (p.price == null && decimal(first.p) !== decimal(market.order.limitPx))
73825
+ return "modify changes the resting order's price";
73826
+ }
73827
+ return null;
73828
+ }
72625
73829
  function verifyPerpsBuild(build, opts) {
72626
73830
  try {
72627
73831
  const network = opts.network ?? "mainnet";
@@ -72649,6 +73853,13 @@ function verifyPerpsBuild(build, opts) {
72649
73853
  if (!order || order.a !== opts.closeOrder.asset || order.b !== opts.closeOrder.isBuy || decimal(order.s) !== decimal(opts.closeOrder.size) || order.r !== true)
72650
73854
  return { ok: false, reason: "close side, asset or size does not match the requested position" };
72651
73855
  }
73856
+ if (opts.market) {
73857
+ if (opts.intent?.method !== "open" && opts.intent?.method !== "modify")
73858
+ return { ok: false, reason: "a market check needs an open or modify intent" };
73859
+ const marketRefusal = checkOrderMarket(action, opts.intent, opts.market);
73860
+ if (marketRefusal)
73861
+ return { ok: false, reason: marketRefusal };
73862
+ }
72652
73863
  if (typed.primary_type === "Agent") {
72653
73864
  const type = action.type;
72654
73865
  if (typeof type !== "string" || !HYPERLIQUID_ALLOWED_ACTION_TYPES.includes(type)) {
@@ -72709,6 +73920,7 @@ function hyperliquidRelayBody(typedData) {
72709
73920
 
72710
73921
  // src/commands/perps.ts
72711
73922
  init_render();
73923
+ init_solana_verify();
72712
73924
  init_trading();
72713
73925
  init_swap();
72714
73926
  var PERPS_SCOPE = "perps:write";
@@ -72855,6 +74067,18 @@ async function runPerpsWrite(ctx, opts) {
72855
74067
  if (!closeCheck.ok)
72856
74068
  throw new TradingError("PERPS_BUILD_REFUSED", `Refused to sign this build: ${closeCheck.reason}. Nothing was signed.`);
72857
74069
  }
74070
+ if (method === "open") {
74071
+ const market = await hyperliquidOrderMarket(ctx.deps.fetch, net, wallet.address, method, opts.body);
74072
+ const marketCheck = verifyPerpsBuild(build, {
74073
+ builder,
74074
+ network: net,
74075
+ address: wallet.address,
74076
+ intent: { method, params: opts.body },
74077
+ market
74078
+ });
74079
+ if (!marketCheck.ok)
74080
+ throw new TradingError("PERPS_BUILD_REFUSED", `Refused to sign this build: ${marketCheck.reason}. Nothing was signed.`);
74081
+ }
72858
74082
  if (!opts.submit)
72859
74083
  return printTradingResult(ctx, { ...build, signed: false, submitted: false });
72860
74084
  if (!await confirm(ctx, build, wallet.address, opts.yes)) {
@@ -73247,6 +74471,13 @@ async function perpsDeposit(args, ctx) {
73247
74471
  const solanaProblem = await tradingRead(ctx, reader, () => relaySolanaDepositProblem(transaction, wallet.address, reader.rpc));
73248
74472
  if (solanaProblem)
73249
74473
  throw refused(solanaProblem);
74474
+ const spent = await tradingRead(ctx, reader, () => solanaSpendProblem(transaction, wallet.address, reader.rpc, {
74475
+ kind: "swap",
74476
+ spend: asset === "SOL" ? "SOL" : BASES[asset]?.mint ?? asset,
74477
+ maxSpendRaw: BigInt(amountRaw)
74478
+ }));
74479
+ if (spent)
74480
+ throw refused(spent);
73250
74481
  }
73251
74482
  const decimals = Number(build.outDecimals);
73252
74483
  const minimum = decimalAmount(String(build.minimumOutRaw), decimals);
@@ -73312,6 +74543,7 @@ async function perpsDeposit(args, ctx) {
73312
74543
 
73313
74544
  // src/commands/plans.ts
73314
74545
  init_args();
74546
+ init_client();
73315
74547
 
73316
74548
  // src/plans.ts
73317
74549
  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 +74559,7 @@ var PLAN_CAPABILITY_LABELS = {
73327
74559
  createLinkedWallets: "Create linked wallets",
73328
74560
  importLinkedWallets: "Import linked wallets",
73329
74561
  limitOrders: "Limit orders",
73330
- quant: "Quant on Telegram"
74562
+ quant: "Quant deep analyses (Max)"
73331
74563
  };
73332
74564
  var PLAN_LABELS = { free: "Free", believer: "Believer", pro: "Pro", max: "Max" };
73333
74565
  function planLabel(plan) {
@@ -73817,6 +75049,7 @@ async function runPlugin(name2, rawArgs, ctx) {
73817
75049
 
73818
75050
  // src/commands/pnl.ts
73819
75051
  init_args();
75052
+ init_client();
73820
75053
  init_deps();
73821
75054
  init_profiles();
73822
75055
  init_render();
@@ -74264,6 +75497,7 @@ ${renderTable(headers, rows.map((row) => row.map(terminalText)))}
74264
75497
 
74265
75498
  // src/commands/portfolio.ts
74266
75499
  init_args();
75500
+ init_client();
74267
75501
  init_deps();
74268
75502
  init_evm_lite();
74269
75503
  init_profiles();
@@ -74870,7 +76104,7 @@ function hoodHoldings(read, prices, tokensPart) {
74870
76104
  ...token.symbol ? { symbol: token.symbol } : {}
74871
76105
  });
74872
76106
  }
74873
- const holdings = raw.map(({ symbol, ...h }) => {
76107
+ const holdings2 = raw.map(({ symbol, ...h }) => {
74874
76108
  const price = prices[hoodPriceKey(h.mint)];
74875
76109
  const priceUsd = price?.priceUsd ?? null;
74876
76110
  const amount = formatAmount(h.amountRaw, h.decimals);
@@ -74887,7 +76121,7 @@ function hoodHoldings(read, prices, tokensPart) {
74887
76121
  };
74888
76122
  });
74889
76123
  return {
74890
- holdings,
76124
+ holdings: holdings2,
74891
76125
  unread: [...read.wei === null ? ["eth"] : [], ...read.tokens === null ? [tokensPart] : []],
74892
76126
  ...read.truncated ? { truncated: true } : {}
74893
76127
  };
@@ -75024,6 +76258,7 @@ ${renderTable(["GROUP", "WALLET", "POSITION", "POOL", "HOLDINGS", "UNCLAIMED FEE
75024
76258
 
75025
76259
  // src/commands/profile.ts
75026
76260
  init_args();
76261
+ init_client();
75027
76262
  init_profiles();
75028
76263
  init_render();
75029
76264
  init_solana_endpoint();
@@ -75298,6 +76533,7 @@ async function profileRemove(args, ctx) {
75298
76533
 
75299
76534
  // src/commands/setup.ts
75300
76535
  init_args();
76536
+ init_client();
75301
76537
  init_deps();
75302
76538
  init_profiles();
75303
76539
  init_render();
@@ -75495,6 +76731,10 @@ function formatAmount2(raw, decimals) {
75495
76731
  function formatSol2(lamports) {
75496
76732
  return `${formatAmount2(lamports, 9)} SOL`;
75497
76733
  }
76734
+ function signerControlChanges(signers, simulation) {
76735
+ const addresses = new Set(signers.map((entry) => entry.address));
76736
+ return controlChanges(simulation.snapshots, (address) => addresses.has(address));
76737
+ }
75498
76738
  function displayLines(input) {
75499
76739
  const { tx, compiled, signers, simulation, mints } = input;
75500
76740
  const lines = [];
@@ -75507,6 +76747,7 @@ function displayLines(input) {
75507
76747
  for (const program of programs)
75508
76748
  lines.push(`program ${programNameOf(program)}`);
75509
76749
  const deltas = computeDeltas(simulation.snapshots);
76750
+ const changes = signerControlChanges(signers, simulation);
75510
76751
  for (const entry of signers) {
75511
76752
  const sol = deltas.sol.find((delta) => delta.address === entry.address);
75512
76753
  if (sol) {
@@ -75514,7 +76755,7 @@ function displayLines(input) {
75514
76755
  } else {
75515
76756
  lines.push(`${entry.label.padEnd(11)} SOL unchanged (not a writable account of this transaction)`);
75516
76757
  }
75517
- for (const token of deltas.tokens.filter((delta) => delta.owner === entry.address)) {
76758
+ for (const token of deltas.tokens.filter((delta) => delta.ownerBefore === entry.address || delta.ownerAfter === entry.address)) {
75518
76759
  const info = mints.get(token.mint);
75519
76760
  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
76761
  }
@@ -75533,10 +76774,14 @@ function displayLines(input) {
75533
76774
  const info = mints.get(token.mint);
75534
76775
  others.push(`${token.owner} receives +${formatAmount2(token.after - token.before, info?.decimals)} of ${token.mint} (account ${token.account})`);
75535
76776
  }
75536
- if (others.length === 0)
76777
+ if (others.length === 0 && changes.length === 0)
75537
76778
  lines.push("others no other account gains a balance in the simulation");
76779
+ if (others.length === 0 && changes.length > 0)
76780
+ lines.push("others no balance moves elsewhere, but control of an account changes hands (below)");
75538
76781
  for (const line of others)
75539
76782
  lines.push(`receives ${line}`);
76783
+ for (const change of changes)
76784
+ lines.push(`CONTROL ${describeControlChange(change)}`);
75540
76785
  for (const [mint, info] of mints)
75541
76786
  for (const risk of info.risks)
75542
76787
  lines.push(`warning ${mint}: ${risk}`);
@@ -75650,6 +76895,14 @@ ${simulation.result.logs.map((line) => ` ${line}`).join(`
75650
76895
  deps.stderr.write(` ${line}
75651
76896
  `);
75652
76897
  }
76898
+ const changes = signerControlChanges(signers, simulation);
76899
+ if (changes.length > 0) {
76900
+ 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.
76901
+ ${changes.map((change) => ` ${describeControlChange(change)}`).join(`
76902
+ `)}`, {
76903
+ 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."
76904
+ });
76905
+ }
75653
76906
  if (!yes)
75654
76907
  await opened.confirm(`sign with ${signers.map((entry) => entry.label).join(", ")}`);
75655
76908
  const signed = new Map;
@@ -76027,6 +77280,7 @@ init_tee_rebind();
76027
77280
 
76028
77281
  // src/commands/tee-rename.ts
76029
77282
  init_args();
77283
+ init_client();
76030
77284
  init_deps();
76031
77285
  init_profiles();
76032
77286
  init_render();
@@ -76668,6 +77922,7 @@ init_args();
76668
77922
  init_evm_lite();
76669
77923
  init_profiles();
76670
77924
  init_render();
77925
+ init_solana_verify();
76671
77926
  init_trading();
76672
77927
  init_swap();
76673
77928
  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 +78037,12 @@ async function transfer(args, ctx) {
76782
78037
  const wallet = payer.wallet;
76783
78038
  const destination = await resolveDestination(ctx, key, wallet, to);
76784
78039
  const label = asset ?? flags["--mint"];
78040
+ const solana = lazySolanaClient(ctx, flags["--rpc-url"]);
76785
78041
  let amountRaw;
76786
78042
  if (isMax)
76787
78043
  amountRaw = "max";
76788
78044
  else {
76789
- const decimals = asset ? BASES[asset]?.decimals ?? 9 : await decimalsFor(ctx, flags["--mint"], lazySolanaClient(ctx, flags["--rpc-url"]));
78045
+ const decimals = asset ? BASES[asset]?.decimals ?? 9 : await decimalsFor(ctx, flags["--mint"], solana);
76790
78046
  amountRaw = rawAmount(flags["--amount"], decimals);
76791
78047
  }
76792
78048
  const amountText2 = isMax ? `the full spendable balance of ${label}` : `${flags["--amount"]} ${label}`;
@@ -76809,8 +78065,22 @@ async function transfer(args, ctx) {
76809
78065
  throw new TradingError("INVALID_RESPONSE", "Candle did not return one unsigned transfer transaction; nothing was signed.");
76810
78066
  if (typeof built.payerAddress === "string" && built.payerAddress !== wallet.address)
76811
78067
  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"}` : ""}.
78068
+ const builtAmount = typeof built.amountRaw === "string" && /^[1-9]\d*$/.test(built.amountRaw) ? built.amountRaw : null;
78069
+ if (builtAmount === null || !isMax && builtAmount !== amountRaw)
78070
+ throw new TradingError("INVALID_RESPONSE", "The transfer build does not name the confirmed amount; nothing was signed.");
78071
+ const amountShown = asset ? `${decimalAmount(builtAmount, BASES[asset]?.decimals ?? 0)} ${asset}` : `${builtAmount} raw units of ${label}`;
78072
+ ctx.deps.stderr.write(`Built transfer ${transferId}: ${amountShown}${typeof built.destinationKind === "string" ? ` to ${built.destinationKind === "vault" ? "the vault" : "a linked wallet"}` : ""}.
78073
+ `);
78074
+ const reader = await solana();
78075
+ const problem = await tradingRead(ctx, reader, () => solanaSpendProblem(unsigned[0], wallet.address, reader.rpc, {
78076
+ kind: "transfer",
78077
+ asset: asset === undefined ? flags["--mint"] : asset === "SOL" ? "SOL" : BASES[asset]?.mint ?? asset,
78078
+ amountRaw: BigInt(builtAmount),
78079
+ recipient: destination.address
78080
+ }));
78081
+ if (problem)
78082
+ 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.`);
78083
+ 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
78084
  `);
76815
78085
  const signed = await relaySign(ctx, key, wallet, unsigned[0]);
76816
78086
  const result = await request(ctx, key, "/api/v1/agent/transfer/submit", {
@@ -77870,8 +79140,8 @@ function assertFloorCarried(copy, live) {
77870
79140
  details: { step: "floor", missing: ids }
77871
79141
  });
77872
79142
  }
77873
- function verifiedWithLine(word2, envelopeId, how, floorClause) {
77874
- return ` verified with ${word2} ${envelopeId} (${how}); ${floorClause}`;
79143
+ function verifiedWithLine(word3, envelopeId, how, floorClause) {
79144
+ return ` verified with ${word3} ${envelopeId} (${how}); ${floorClause}`;
77875
79145
  }
77876
79146
  function carriedClause(passphraseIds) {
77877
79147
  return `passphrase ${passphraseIds.join(", ")} carried byte for byte`;
@@ -79372,7 +80642,7 @@ Anyone who reads them can move every derived key's funds.
79372
80642
  deps.stdout.write(`
79373
80643
  `);
79374
80644
  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(" ");
80645
+ const row = words.slice(i, i + 4).map((word3, offset) => `${String(i + offset + 1).padStart(2, " ")}. ${word3.padEnd(9, " ")}`).join(" ");
79376
80646
  deps.stdout.write(` ${row.trimEnd()}
79377
80647
  `);
79378
80648
  }
@@ -79793,8 +81063,8 @@ async function collectOwn(ctx) {
79793
81063
  // src/commands/vault-factor-dispatch.ts
79794
81064
  init_vault_support();
79795
81065
  async function vaultFactor(args, ctx) {
79796
- const [word2, ...rest] = args;
79797
- switch (word2) {
81066
+ const [word3, ...rest] = args;
81067
+ switch (word3) {
79798
81068
  case "list":
79799
81069
  return vaultFactorList(rest, ctx);
79800
81070
  case "add":
@@ -79804,7 +81074,7 @@ async function vaultFactor(args, ctx) {
79804
81074
  case undefined:
79805
81075
  return usage(ctx, "Usage: candle vault factor <list | add passphrase | add security-key | add touch-id | add passkey | remove <id>>");
79806
81076
  default:
79807
- return usage(ctx, `Unknown subcommand: vault factor ${word2}. Try: list, add, remove`);
81077
+ return usage(ctx, `Unknown subcommand: vault factor ${word3}. Try: list, add, remove`);
79808
81078
  }
79809
81079
  }
79810
81080
 
@@ -81001,12 +82271,12 @@ function matches2(entry, filter) {
81001
82271
  // src/commands/vault-phrase-dispatch.ts
81002
82272
  init_vault_support();
81003
82273
  async function vaultPhrase(args, ctx) {
81004
- const [word2, ...rest] = args;
81005
- if (word2 === "show")
82274
+ const [word3, ...rest] = args;
82275
+ if (word3 === "show")
81006
82276
  return vaultPhraseShow(rest, ctx);
81007
- if (word2 === undefined)
82277
+ if (word3 === undefined)
81008
82278
  return usage(ctx, "Usage: candle vault phrase show");
81009
- return usage(ctx, `Unknown subcommand: vault phrase ${word2}. The only one is: show`);
82279
+ return usage(ctx, `Unknown subcommand: vault phrase ${word3}. The only one is: show`);
81010
82280
  }
81011
82281
 
81012
82282
  // src/commands/vault-promote.ts
@@ -81019,6 +82289,7 @@ init_solana_endpoint();
81019
82289
  init_solana_lite();
81020
82290
 
81021
82291
  // src/vault/account-room.ts
82292
+ init_client();
81022
82293
  init_deps();
81023
82294
  init_errors2();
81024
82295
  async function readAccountRoom(ctx) {
@@ -81316,8 +82587,8 @@ async function promoteEvmInPlace(input, subjectLabel, sweepTo) {
81316
82587
  ...toKey.keySigner !== undefined ? { signer: toKey.keySigner.fingerprint } : {}
81317
82588
  });
81318
82589
  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())) {
82590
+ const holdings2 = await readEvmHoldings(client.rpc, first.subject.address);
82591
+ for (const line of holdingsLines(first.subject.address, holdings2, new Date(ctx.deps.now()).toISOString())) {
81321
82592
  ctx.deps.stdout.write(`${line}
81322
82593
  `);
81323
82594
  }
@@ -81411,7 +82682,7 @@ async function promoteEvmInPlace(input, subjectLabel, sweepTo) {
81411
82682
  lifecycle: updated?.tee?.lifecycle ?? null,
81412
82683
  linkedWalletId: updated?.linkedWalletId ?? null,
81413
82684
  controlledBy: controlledByJson(controlledBy),
81414
- holdings: { eth: holdings.eth.toString(), usdg: holdings.usdg.toString(), weth: holdings.weth.toString() },
82685
+ holdings: { eth: holdings2.eth.toString(), usdg: holdings2.usdg.toString(), weth: holdings2.weth.toString() },
81415
82686
  scanStart: { block: scanStart.block, recorded: scanStart.recorded },
81416
82687
  vaultVersion: reopened.file.version,
81417
82688
  ...rebound?.json ?? {}
@@ -84353,6 +85624,7 @@ init_promote_support();
84353
85624
  init_store();
84354
85625
 
84355
85626
  // src/vault/tee-transfer.ts
85627
+ init_client();
84356
85628
  init_errors2();
84357
85629
  function assertNoPendingSweep(entry) {
84358
85630
  const pending = entry.tee?.sweepPending?.length ?? 0;
@@ -84694,6 +85966,7 @@ init_wallets();
84694
85966
 
84695
85967
  // src/commands/wallets-allow-launch.ts
84696
85968
  init_args();
85969
+ init_client();
84697
85970
  init_deps();
84698
85971
  init_profiles();
84699
85972
  init_render();
@@ -85012,6 +86285,7 @@ async function walletsCloseEmpty(args, ctx) {
85012
86285
 
85013
86286
  // src/commands/wallets-trust.ts
85014
86287
  init_args();
86288
+ init_client();
85015
86289
  init_deps();
85016
86290
  init_profiles();
85017
86291
  init_render();
@@ -85269,6 +86543,7 @@ init_render();
85269
86543
  init_secret_store();
85270
86544
 
85271
86545
  // src/update-notice.ts
86546
+ init_client();
85272
86547
  init_release();
85273
86548
  var DAY_MS = 24 * 60 * 60 * 1000;
85274
86549
  async function maybeWriteUpdateNotice(deps, opts = {}) {
@@ -85469,12 +86744,12 @@ var COMMANDS = {
85469
86744
  };
85470
86745
  var ROUTED_COMMANDS = new Set(Object.keys(COMMANDS));
85471
86746
  var ALIASES = { wallet: "wallets" };
85472
- function canonicalCommand(word2) {
85473
- return word2 !== undefined && Object.hasOwn(ALIASES, word2) ? ALIASES[word2] : word2;
86747
+ function canonicalCommand(word3) {
86748
+ return word3 !== undefined && Object.hasOwn(ALIASES, word3) ? ALIASES[word3] : word3;
85474
86749
  }
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;
86750
+ var ROUTED_SUBCOMMANDS = Object.fromEntries(Object.entries(COMMANDS).filter(([, route]) => route.subcommands !== undefined).map(([word3, route]) => [word3, Object.keys(route.subcommands ?? {})]));
86751
+ function routeFor(word3) {
86752
+ return word3 !== undefined && Object.hasOwn(COMMANDS, word3) ? COMMANDS[word3] : undefined;
85478
86753
  }
85479
86754
  function subHandlerFor(route, sub) {
85480
86755
  const subcommands = route?.subcommands;
@@ -85516,8 +86791,8 @@ async function run2(argv, deps) {
85516
86791
  return 1;
85517
86792
  }
85518
86793
  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 });
86794
+ const word3 = "error" in extractedForNotice ? undefined : canonicalCommand(extractedForNotice.rest[0] === "candle" ? extractedForNotice.rest[1] : extractedForNotice.rest[0]);
86795
+ await maybeWriteUpdateNotice(deps, { command: word3 });
85521
86796
  return code;
85522
86797
  }
85523
86798
  async function runCommand(argv, deps) {
@@ -85528,7 +86803,7 @@ async function runCommand(argv, deps) {
85528
86803
  return 2;
85529
86804
  }
85530
86805
  const { rest, flags } = extracted;
85531
- const plugin = pluginInvocation(argv, deps.env, (word3) => ROUTED_COMMANDS.has(canonicalCommand(word3) ?? ""));
86806
+ const plugin = pluginInvocation(argv, deps.env, (word4) => ROUTED_COMMANDS.has(canonicalCommand(word4) ?? ""));
85532
86807
  if (plugin !== undefined) {
85533
86808
  const leading = extractGlobalFlags(argv.slice(0, argv.length - plugin.args.length - 1));
85534
86809
  const pluginFlags = "error" in leading ? flags : leading.flags;
@@ -85609,9 +86884,9 @@ async function runCommand(argv, deps) {
85609
86884
  vaultFactor: flags.vaultFactor,
85610
86885
  vaultDevice: flags.vaultDevice
85611
86886
  };
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) {
86887
+ const word3 = cmd ?? "";
86888
+ const actsAsIdentity = word3 !== "mcp" || mcpActsAsIdentity(tokens.slice(1));
86889
+ if (ROUTED_COMMANDS.has(word3) && !NEVER_GUARDED.has(word3) && routesToCommand(cmd, sub) && actsAsIdentity) {
85615
86890
  const verdict = await verifyProfileAccount(ctx, config2);
85616
86891
  if (!verdict.ok) {
85617
86892
  writeLocalFailure(deps, { code: "ACCOUNT_MISMATCH", message: verdict.message, suggestion: verdict.suggestion }, flags.json);
@@ -85645,11 +86920,11 @@ function splitFix(message) {
85645
86920
  const fixAt = message.indexOf(" Run: ");
85646
86921
  return fixAt === -1 ? { message } : { message: message.slice(0, fixAt), suggestion: message.slice(fixAt + 1) };
85647
86922
  }
85648
- function unknownCommand(deps, token, word2) {
86923
+ function unknownCommand(deps, token, word3) {
85649
86924
  if (token !== undefined)
85650
86925
  deps.stderr.write(`Unknown command: ${token}
85651
86926
  `);
85652
- deps.stderr.write((word2 === undefined ? undefined : renderTopic(word2)) ?? renderTopLevel());
86927
+ deps.stderr.write((word3 === undefined ? undefined : renderTopic(word3)) ?? renderTopLevel());
85653
86928
  return 1;
85654
86929
  }
85655
86930
  async function migrateProfiles(deps) {
@@ -85670,10 +86945,13 @@ async function migrateProfiles(deps) {
85670
86945
  await deps.writeConfig({ profiles: config2.profiles, activeProfile: config2.activeProfile });
85671
86946
  return config2;
85672
86947
  }
85673
- function realOpenBrowser(url) {
86948
+ function realOpenBrowser(serverUrl) {
86949
+ const url = browserUrl(serverUrl);
86950
+ if (url === null)
86951
+ return;
85674
86952
  try {
85675
86953
  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 });
86954
+ 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
86955
  child.on("error", () => {});
85678
86956
  child.unref();
85679
86957
  } catch {}