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