@candledottv/cli 0.11.28 → 0.11.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/index.js +1312 -127
- 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;
|
|
@@ -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", {
|
|
@@ -63407,7 +64431,7 @@ async function registerEligibleProfileTool(server, env = process.env, doFetch =
|
|
|
63407
64431
|
let eligible = false;
|
|
63408
64432
|
if (cfg.apiKey && wanted.length > 0) {
|
|
63409
64433
|
try {
|
|
63410
|
-
const res = await doFetch(`${cfg.apiUrl}/api/v1/agent/keys/self/public-profile`, {
|
|
64434
|
+
const res = await keyedRedirectGuard(doFetch)(`${cfg.apiUrl}/api/v1/agent/keys/self/public-profile`, {
|
|
63411
64435
|
method: "GET",
|
|
63412
64436
|
headers: { "x-api-key": cfg.apiKey },
|
|
63413
64437
|
signal: AbortSignal.timeout(15000)
|
|
@@ -63429,9 +64453,10 @@ async function registerEligibleProfileTool(server, env = process.env, doFetch =
|
|
|
63429
64453
|
}
|
|
63430
64454
|
return eligible;
|
|
63431
64455
|
}
|
|
63432
|
-
var VAMP_CANDIDATES_DESCRIPTION, CUSTOM_CURVE_RULE, localProfileTools, STAKING_DISCLOSURE = "No instant unstake. Read pool.withdrawalTimelockSeconds for the wait. Solana rewards accrue to the holding wallet.
|
|
64456
|
+
var VAMP_CANDIDATES_DESCRIPTION, CUSTOM_CURVE_RULE, localProfileTools, STAKING_DISCLOSURE = "No instant unstake. Read pool.withdrawalTimelockSeconds for the wait. Solana rewards accrue to the holding wallet. sCNDL in any eligible account wallet counts toward Pro and earns the account's Hood staking reward, except a wallet another account also claims, wallets past the 200-wallet cap, and an account with no EVM wallet. CNDL in a pending withdrawal counts toward nothing for Pro.", stakingWriteShape, TOOL_NAMES, PROFILE_EDIT_TOOL_NAMES, isProfileEditTool = (name2) => PROFILE_EDIT_TOOL_NAMES.includes(name2), OPERATION_NOT_FOUND_RULE, launchTokenShape, getMarketShape, tokenForensicsShape, FEED_LIMIT_MESSAGE = "limit must be a whole number from 1 to 200", feedLimit, getFeedShape, reportActivityShape, getAgentProfileShape, listAgentsShape, createPostShape, deletePostShape, listAgentPostsShape, getOperationShape, vampCandidatesShape, getFeesShape, claimFeesShape, resolveTokenShape, profileWalletsShape, profilePnlShape, profileTradesShape, setProfileWalletsShape, swapShape, tradeShape, _rawBuyAmount, seedableLaunchShape, seedShape, launchAndSeedShape, localImageShape, localLaunchTokenShape, localLaunchAndSeedShape, IMAGE_PATH_DISCLOSURE, transferShape, sweepShape;
|
|
63433
64457
|
var init_tools = __esm(() => {
|
|
63434
64458
|
init_zod();
|
|
64459
|
+
init_client2();
|
|
63435
64460
|
init_convert();
|
|
63436
64461
|
init_image();
|
|
63437
64462
|
init_market_disclosure();
|
|
@@ -63440,7 +64465,7 @@ var init_tools = __esm(() => {
|
|
|
63440
64465
|
init_plans();
|
|
63441
64466
|
init_update_notice();
|
|
63442
64467
|
VAMP_CANDIDATES_DESCRIPTION = "Lists Solana tokens that are close to graduating or graduated in the last 6 hours whose ticker other tokens " + "are copying, most-copied in the last 24 hours first. Each entry carries a launch draft (name, ticker, image URL) " + "for `candle_launch_token` or `candle_launch_and_seed`; draft notes identify fields you must supply or adjust. " + "Upload your own image through the agent image upload when the image is unavailable. Use `dryRun: true` first. " + "Counts exclude the candidate itself and cover both Solana and Hood launches at the venues returned in " + "`coverage`, not Candle's own. `coverage.since` is the returned Solana coverage date; Hood history may differ.";
|
|
63443
|
-
CUSTOM_CURVE_RULE = 'mode "exp-custom" with curve { virtualQuote, threshold } launches a Solana curve with your own V (the virtual ' + "quote it starts with) and G (the quote it raises before graduating), in whole SOL or CNDL as decimal strings, " + "accepted when V is 1 to 100 SOL and G 1 to 250 SOL (66,000 to 6,600,000 and 66,000 to 16,500,000 CNDL), when " + "every price up to 20 x G stays under the ceiling (V + 20G)^2 < V x X at this stakerAllocationBps, when neither " + "is finer than the quote's decimals, and when the dev buy is at most 10% of G; a curve past a limit is " + "CURVE_OUT_OF_RANGE, and at the ceiling it carries maxThresholdRaw, the largest G that fits. " + "Dry-run it first (dryRun: true): the dry run spends nothing and returns curveTerms, the market cap at start and " + "at graduation and the supply sold by graduation. Only where the API has
|
|
64468
|
+
CUSTOM_CURVE_RULE = 'mode "exp-custom" with curve { virtualQuote, threshold } launches a Solana curve with your own V (the virtual ' + "quote it starts with) and G (the quote it raises before graduating), in whole SOL or CNDL as decimal strings, " + "accepted when V is 1 to 100 SOL and G 1 to 250 SOL (66,000 to 6,600,000 and 66,000 to 16,500,000 CNDL), when " + "every price up to 20 x G stays under the ceiling (V + 20G)^2 < V x X at this stakerAllocationBps, when neither " + "is finer than the quote's decimals, and when the dev buy is at most 10% of G; a curve past a limit is " + "CURVE_OUT_OF_RANGE, and at the ceiling it carries maxThresholdRaw, the largest G that fits. " + "Dry-run it first (dryRun: true): the dry run spends nothing and returns curveTerms, the market cap at start and " + "at graduation and the supply sold by graduation. Only where the API has EXP_CUSTOM_CURVES on; " + "elsewhere exp-custom is refused as an unknown mode.";
|
|
63444
64469
|
localProfileTools = new WeakMap;
|
|
63445
64470
|
stakingWriteShape = {
|
|
63446
64471
|
payer: exports_external.enum(["main", "linked"]).optional().describe("main is the embedded wallet; linked names a linked or TEE wallet"),
|
|
@@ -63739,6 +64764,7 @@ Full reference, error catalogue and end-to-end recipes: https://docs.candle.tv
|
|
|
63739
64764
|
});
|
|
63740
64765
|
|
|
63741
64766
|
// src/index.ts
|
|
64767
|
+
init_client();
|
|
63742
64768
|
import { spawn as spawn3 } from "node:child_process";
|
|
63743
64769
|
import { realpathSync } from "node:fs";
|
|
63744
64770
|
import { chmod as chmod8, readFile as readFile10, realpath as realpath2, rename as rename6, unlink, writeFile as writeFile7 } from "node:fs/promises";
|
|
@@ -63747,6 +64773,7 @@ import { pathToFileURL } from "node:url";
|
|
|
63747
64773
|
|
|
63748
64774
|
// src/commands/agent-post.ts
|
|
63749
64775
|
init_args();
|
|
64776
|
+
init_client();
|
|
63750
64777
|
init_render();
|
|
63751
64778
|
init_trading();
|
|
63752
64779
|
init_swap();
|
|
@@ -63842,6 +64869,7 @@ ${USAGE}`);
|
|
|
63842
64869
|
|
|
63843
64870
|
// src/commands/agent-profile.ts
|
|
63844
64871
|
init_args();
|
|
64872
|
+
init_client();
|
|
63845
64873
|
init_render();
|
|
63846
64874
|
init_trading();
|
|
63847
64875
|
init_swap();
|
|
@@ -63907,6 +64935,7 @@ import { homedir as homedir3 } from "node:os";
|
|
|
63907
64935
|
import { join as join8 } from "node:path";
|
|
63908
64936
|
|
|
63909
64937
|
// src/account.ts
|
|
64938
|
+
init_client();
|
|
63910
64939
|
async function fetchAccount(deps, apiUrl, apiKey) {
|
|
63911
64940
|
const identity = await apiRequest("/api/v1/agent/wallets/embedded", {
|
|
63912
64941
|
auth: "key",
|
|
@@ -63927,6 +64956,7 @@ init_agent_key_access();
|
|
|
63927
64956
|
init_args();
|
|
63928
64957
|
|
|
63929
64958
|
// src/checks.ts
|
|
64959
|
+
init_client();
|
|
63930
64960
|
init_render();
|
|
63931
64961
|
async function runLiveCheck(params) {
|
|
63932
64962
|
const { deps, apiUrl, path, auth, credential, check, passDetail } = params;
|
|
@@ -63941,13 +64971,14 @@ async function runLiveCheck(params) {
|
|
|
63941
64971
|
}
|
|
63942
64972
|
|
|
63943
64973
|
// src/commands/auth.ts
|
|
64974
|
+
init_client();
|
|
63944
64975
|
init_deps();
|
|
63945
64976
|
init_profiles();
|
|
63946
64977
|
init_render();
|
|
63947
64978
|
init_secret_store();
|
|
63948
64979
|
|
|
63949
64980
|
// src/version.ts
|
|
63950
|
-
var CLI_VERSION = "0.11.
|
|
64981
|
+
var CLI_VERSION = "0.11.29";
|
|
63951
64982
|
|
|
63952
64983
|
// src/commands/auth.ts
|
|
63953
64984
|
init_keys_embedded_wallet();
|
|
@@ -64487,7 +65518,7 @@ var HELP = {
|
|
|
64487
65518
|
stake: {
|
|
64488
65519
|
group: "Trade",
|
|
64489
65520
|
summary: "Stake CNDL, start and finish withdrawals, claim Solana staking rewards",
|
|
64490
|
-
description: "Solana only. MOVES REAL FUNDS from the embedded wallet (default) or a named TEE wallet whose relay signer is on this machine. add takes decimal CNDL; remove takes decimal sCNDL. No instant unstake: finish waits for the pool's withdrawalTimelockSeconds, shown by candle stake. Solana rewards accrue to the holding wallet;
|
|
65521
|
+
description: "Solana only. MOVES REAL FUNDS from the embedded wallet (default) or a named TEE wallet whose relay signer is on this machine. add takes decimal CNDL; remove takes decimal sCNDL. No instant unstake: finish waits for the pool's withdrawalTimelockSeconds, shown by candle stake. Solana rewards accrue to the holding wallet; sCNDL in any eligible account wallet counts toward Pro and earns the account's Hood reward, except a wallet another account also claims, wallets past the 200-wallet cap, and an account with no EVM wallet. Pending withdrawals count toward nothing for Pro. Writes require swap:write and an enabled agent staking rail. Reuse --client-stake-id on retry: a rerun posts the saved signed transaction to submit again without re-signing. The server reads the chain for that signature first, so it lands once. The signed transaction is kept in the CLI operations directory for that retry. Ordinary linked wallets use SDK builds and external signing.",
|
|
64491
65522
|
usage: [
|
|
64492
65523
|
"candle stake [--wallet <wallet>]",
|
|
64493
65524
|
"candle stake add <amount>",
|
|
@@ -64779,7 +65810,7 @@ var HELP = {
|
|
|
64779
65810
|
fees: {
|
|
64780
65811
|
group: "Account",
|
|
64781
65812
|
summary: "Creator fees your launched tokens can pay, and the claim",
|
|
64782
|
-
description: "Lists the tokens launched by this account's wallets and what each could pay now, from direct chain reads, with the reason a token cannot be claimed. `fees claim` collects one token's creator fees and pays them to the wallet fixed at launch: there is no destination option, Candle signs with the embedded wallet, and that wallet pays the network fee. The key needs launch:write to claim and account:read to list.
|
|
65813
|
+
description: "Lists the tokens launched by this account's wallets and what each could pay now, from direct chain reads, with the reason a token cannot be claimed. `fees claim` collects one token's creator fees and pays them to the wallet fixed at launch: there is no destination option, Candle signs with the embedded wallet, and that wallet pays the network fee. The key needs launch:write to claim and account:read to list. A Phantom-signup account's Solana fees are credited to the external wallet, which Candle cannot sign for, so its agent cannot claim them.",
|
|
64783
65814
|
usage: ["candle fees [--cursor <cursor>] [--limit <1-50>]", "candle fees claim <mint> [--dry-run]"],
|
|
64784
65815
|
rows: [
|
|
64785
65816
|
{
|
|
@@ -65295,10 +66326,10 @@ var HELP = {
|
|
|
65295
66326
|
examples: ["candle help", "candle help vault", "candle help completion"]
|
|
65296
66327
|
}
|
|
65297
66328
|
};
|
|
65298
|
-
function topicFor(
|
|
65299
|
-
if (Object.hasOwn(HELP,
|
|
65300
|
-
return HELP[
|
|
65301
|
-
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);
|
|
65302
66333
|
}
|
|
65303
66334
|
function displayName(canonical) {
|
|
65304
66335
|
return HELP[canonical]?.display ?? canonical;
|
|
@@ -65332,14 +66363,14 @@ function wrap(text, width) {
|
|
|
65332
66363
|
return [text];
|
|
65333
66364
|
const lines = [];
|
|
65334
66365
|
let line = "";
|
|
65335
|
-
for (const
|
|
66366
|
+
for (const word3 of text.split(" ")) {
|
|
65336
66367
|
if (line === "")
|
|
65337
|
-
line =
|
|
65338
|
-
else if (`${line} ${
|
|
65339
|
-
line = `${line} ${
|
|
66368
|
+
line = word3;
|
|
66369
|
+
else if (`${line} ${word3}`.length <= width)
|
|
66370
|
+
line = `${line} ${word3}`;
|
|
65340
66371
|
else {
|
|
65341
66372
|
lines.push(line);
|
|
65342
|
-
line =
|
|
66373
|
+
line = word3;
|
|
65343
66374
|
}
|
|
65344
66375
|
}
|
|
65345
66376
|
if (line !== "")
|
|
@@ -65376,14 +66407,14 @@ function renderTopLevel() {
|
|
|
65376
66407
|
" candle help <command> a command's subcommands, flags and examples"
|
|
65377
66408
|
];
|
|
65378
66409
|
const entries = Object.entries(HELP);
|
|
65379
|
-
const longest = Math.max(...entries.map(([
|
|
66410
|
+
const longest = Math.max(...entries.map(([word3]) => displayName(word3).length));
|
|
65380
66411
|
const column = 2 + longest + 2;
|
|
65381
66412
|
for (const group of GROUPS) {
|
|
65382
66413
|
out.push("", group);
|
|
65383
|
-
for (const [
|
|
66414
|
+
for (const [word3, topic] of entries) {
|
|
65384
66415
|
if (topic.group !== group)
|
|
65385
66416
|
continue;
|
|
65386
|
-
out.push(` ${displayName(
|
|
66417
|
+
out.push(` ${displayName(word3).padEnd(column - 2)}${topic.summary}`);
|
|
65387
66418
|
}
|
|
65388
66419
|
}
|
|
65389
66420
|
out.push("", "Global flags", ...renderRows(GLOBAL_FLAGS, 28));
|
|
@@ -65393,11 +66424,11 @@ function renderTopLevel() {
|
|
|
65393
66424
|
`)}
|
|
65394
66425
|
`;
|
|
65395
66426
|
}
|
|
65396
|
-
function renderTopic(
|
|
65397
|
-
const topic = topicFor(
|
|
66427
|
+
function renderTopic(word3) {
|
|
66428
|
+
const topic = topicFor(word3);
|
|
65398
66429
|
if (!topic)
|
|
65399
66430
|
return;
|
|
65400
|
-
const canonical = Object.keys(HELP).find((key) => HELP[key] === topic) ??
|
|
66431
|
+
const canonical = Object.keys(HELP).find((key) => HELP[key] === topic) ?? word3;
|
|
65401
66432
|
const display = displayName(canonical);
|
|
65402
66433
|
const out = [wrap(`candle ${display}: ${topic.description}`, WIDTH).join(`
|
|
65403
66434
|
`)];
|
|
@@ -65465,8 +66496,8 @@ function zshScript() {
|
|
|
65465
66496
|
" _candle_flags=()",
|
|
65466
66497
|
' case "${words[2]}" in'
|
|
65467
66498
|
];
|
|
65468
|
-
for (const { word:
|
|
65469
|
-
lines.push(` ${
|
|
66499
|
+
for (const { word: word3, subcommands, flags } of perWord()) {
|
|
66500
|
+
lines.push(` ${word3})`);
|
|
65470
66501
|
if (subcommands.length > 0)
|
|
65471
66502
|
lines.push(` _candle_subs=(${subcommands.join(" ")})`);
|
|
65472
66503
|
if (flags.length > 0)
|
|
@@ -65495,8 +66526,8 @@ function bashScript() {
|
|
|
65495
66526
|
" fi",
|
|
65496
66527
|
' case "${COMP_WORDS[1]}" in'
|
|
65497
66528
|
];
|
|
65498
|
-
for (const { word:
|
|
65499
|
-
lines.push(` ${
|
|
66529
|
+
for (const { word: word3, subcommands, flags } of perWord()) {
|
|
66530
|
+
lines.push(` ${word3})`);
|
|
65500
66531
|
if (subcommands.length > 0)
|
|
65501
66532
|
lines.push(` candle_subs="${subcommands.join(" ")}"`);
|
|
65502
66533
|
if (flags.length > 0)
|
|
@@ -65516,8 +66547,8 @@ function fishScript() {
|
|
|
65516
66547
|
""
|
|
65517
66548
|
];
|
|
65518
66549
|
for (const [canonical, topic] of Object.entries(HELP)) {
|
|
65519
|
-
for (const
|
|
65520
|
-
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)}'`);
|
|
65521
66552
|
}
|
|
65522
66553
|
}
|
|
65523
66554
|
lines.push("");
|
|
@@ -65530,18 +66561,18 @@ function fishScript() {
|
|
|
65530
66561
|
}
|
|
65531
66562
|
}
|
|
65532
66563
|
}
|
|
65533
|
-
for (const { word:
|
|
66564
|
+
for (const { word: word3, subcommands, flags } of perWord()) {
|
|
65534
66565
|
if (subcommands.length === 0 && flags.length === 0)
|
|
65535
66566
|
continue;
|
|
65536
66567
|
lines.push("");
|
|
65537
66568
|
for (const sub of subcommands) {
|
|
65538
|
-
lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${
|
|
66569
|
+
lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${word3}' -a '${sub}'`);
|
|
65539
66570
|
}
|
|
65540
66571
|
for (const flag of flags) {
|
|
65541
66572
|
if (flag.startsWith("--")) {
|
|
65542
|
-
lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${
|
|
66573
|
+
lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${word3}' -l '${flag.slice(2)}'`);
|
|
65543
66574
|
} else
|
|
65544
|
-
lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${
|
|
66575
|
+
lines.push(`complete -c candle -n '__fish_seen_subcommand_from ${word3}' -s '${flag.slice(1)}'`);
|
|
65545
66576
|
}
|
|
65546
66577
|
}
|
|
65547
66578
|
lines.push("");
|
|
@@ -65580,6 +66611,7 @@ async function completion(args, ctx) {
|
|
|
65580
66611
|
// src/commands/doctor.ts
|
|
65581
66612
|
init_agent_key_access();
|
|
65582
66613
|
init_args();
|
|
66614
|
+
init_client();
|
|
65583
66615
|
init_deps();
|
|
65584
66616
|
|
|
65585
66617
|
// src/keychain.ts
|
|
@@ -65773,6 +66805,7 @@ init_store();
|
|
|
65773
66805
|
|
|
65774
66806
|
// src/commands/key-signer.ts
|
|
65775
66807
|
init_args();
|
|
66808
|
+
init_client();
|
|
65776
66809
|
init_deps();
|
|
65777
66810
|
init_key_signers();
|
|
65778
66811
|
import { generateKeyPairSync as generateKeyPairSync2 } from "node:crypto";
|
|
@@ -65781,6 +66814,7 @@ init_render();
|
|
|
65781
66814
|
init_secret_store();
|
|
65782
66815
|
|
|
65783
66816
|
// src/vault/promote-to-key.ts
|
|
66817
|
+
init_client();
|
|
65784
66818
|
init_tee_rebind();
|
|
65785
66819
|
init_deps();
|
|
65786
66820
|
init_key_signers();
|
|
@@ -70773,14 +71807,14 @@ async function feesClaim(args, ctx) {
|
|
|
70773
71807
|
|
|
70774
71808
|
// src/commands/help.ts
|
|
70775
71809
|
async function help(args, ctx) {
|
|
70776
|
-
const
|
|
70777
|
-
if (
|
|
71810
|
+
const word3 = args[0];
|
|
71811
|
+
if (word3 === undefined) {
|
|
70778
71812
|
ctx.deps.stdout.write(renderTopLevel());
|
|
70779
71813
|
return 0;
|
|
70780
71814
|
}
|
|
70781
|
-
const topic = renderTopic(
|
|
71815
|
+
const topic = renderTopic(word3);
|
|
70782
71816
|
if (topic === undefined) {
|
|
70783
|
-
ctx.deps.stderr.write(`Unknown command: ${
|
|
71817
|
+
ctx.deps.stderr.write(`Unknown command: ${word3}
|
|
70784
71818
|
`);
|
|
70785
71819
|
ctx.deps.stderr.write(renderTopLevel());
|
|
70786
71820
|
return 1;
|
|
@@ -70791,6 +71825,7 @@ async function help(args, ctx) {
|
|
|
70791
71825
|
|
|
70792
71826
|
// src/commands/image.ts
|
|
70793
71827
|
init_args();
|
|
71828
|
+
init_client();
|
|
70794
71829
|
init_deps();
|
|
70795
71830
|
init_render();
|
|
70796
71831
|
init_trading();
|
|
@@ -70910,6 +71945,7 @@ init_keys();
|
|
|
70910
71945
|
// src/commands/keys-access.ts
|
|
70911
71946
|
init_agent_key_access();
|
|
70912
71947
|
init_args();
|
|
71948
|
+
init_client();
|
|
70913
71949
|
init_deps();
|
|
70914
71950
|
init_profiles();
|
|
70915
71951
|
init_render();
|
|
@@ -71236,6 +72272,7 @@ init_keys_embedded_wallet();
|
|
|
71236
72272
|
|
|
71237
72273
|
// src/commands/keys-wallets.ts
|
|
71238
72274
|
init_args();
|
|
72275
|
+
init_client();
|
|
71239
72276
|
init_deps();
|
|
71240
72277
|
init_profiles();
|
|
71241
72278
|
init_render();
|
|
@@ -71418,6 +72455,7 @@ async function keysWallets(args, ctx) {
|
|
|
71418
72455
|
// src/commands/launch.ts
|
|
71419
72456
|
init_args();
|
|
71420
72457
|
init_evm_lite();
|
|
72458
|
+
init_hood_legs();
|
|
71421
72459
|
init_render();
|
|
71422
72460
|
init_solana_endpoint();
|
|
71423
72461
|
init_solana_lite();
|
|
@@ -71432,7 +72470,7 @@ var QUOTE_CHAIN = {
|
|
|
71432
72470
|
eth: "hood",
|
|
71433
72471
|
usdg: "hood"
|
|
71434
72472
|
};
|
|
71435
|
-
var HOOD_LAUNCH_LEGS = ["createCurve"
|
|
72473
|
+
var HOOD_LAUNCH_LEGS = ["createCurve"];
|
|
71436
72474
|
var HOOD_LAUNCH_BUY_LEGS = ["createCurve", "feeTransfer", "trade"];
|
|
71437
72475
|
function launchBuy(flags, chain2) {
|
|
71438
72476
|
const amount = flags["--buy"];
|
|
@@ -71677,6 +72715,9 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
71677
72715
|
curveAddress: curve.address,
|
|
71678
72716
|
operationId: first.operationId
|
|
71679
72717
|
};
|
|
72718
|
+
const feeCap = buy && first.plannedLegCount === 3 ? launchFeeCap(buy.raw) : undefined;
|
|
72719
|
+
if (feeCap !== undefined)
|
|
72720
|
+
quote.candleFee = `at most ${formatUnits(feeCap, 18)} ETH (the tier fee on the first buy, never above ${Number(LAUNCH_FEE_MAX_BPS) / 100}% of it)`;
|
|
71680
72721
|
ctx.deps.stderr.write(buyNotice(buy, "hood"));
|
|
71681
72722
|
if (!await confirmQuote(ctx, quote, args.yes)) {
|
|
71682
72723
|
ctx.deps.stderr.write(`Nothing was signed. Operation ${first.operationId} holds this wallet until ${new Date(first.expiresAt).toISOString()}; a new Hood operation from it is refused as WALLET_BUSY until then.
|
|
@@ -71695,6 +72736,7 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
71695
72736
|
}
|
|
71696
72737
|
const confirmedPlan = buy ? first.plannedLegCount === 3 ? ["createCurve", "feeTransfer", "trade"] : ["createCurve", "trade"] : undefined;
|
|
71697
72738
|
let position = 0;
|
|
72739
|
+
const evm = lazyEvmRpc(ctx, flags["--rpc-url"]);
|
|
71698
72740
|
const run2 = await runSequencedLegs(ctx, key, {
|
|
71699
72741
|
wallet,
|
|
71700
72742
|
first,
|
|
@@ -71705,10 +72747,14 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
71705
72747
|
kind: "launch",
|
|
71706
72748
|
primaryLeg: "createCurve",
|
|
71707
72749
|
allowedLegs,
|
|
71708
|
-
checkLeg: (kind, leg2) => {
|
|
72750
|
+
checkLeg: async (kind, leg2) => {
|
|
71709
72751
|
if (confirmedPlan && confirmedPlan[position++] !== kind)
|
|
71710
72752
|
return false;
|
|
71711
|
-
|
|
72753
|
+
if (kind === "createCurve")
|
|
72754
|
+
return await createCurveLegOk(leg2, wallet.address, evm());
|
|
72755
|
+
if (kind === "feeTransfer")
|
|
72756
|
+
return feeCap !== undefined && feeLegOk(leg2, { cap: feeCap, asset: "eth" });
|
|
72757
|
+
return kind === "trade" && buy !== undefined && leg2.value === buy.raw && sameEvmAddress(leg2.to, curve.address);
|
|
71712
72758
|
},
|
|
71713
72759
|
onLanded: async () => {}
|
|
71714
72760
|
});
|
|
@@ -71728,13 +72774,11 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
71728
72774
|
});
|
|
71729
72775
|
}
|
|
71730
72776
|
function hoodLaterLegsGas(count, buys) {
|
|
71731
|
-
|
|
71732
|
-
return "";
|
|
71733
|
-
return buys ? "; the later legs are priced by Candle when each becomes next" : "; the fee leg is priced by Candle when it becomes next";
|
|
72777
|
+
return count < 2 || !buys ? "" : "; the later legs are priced by Candle when each becomes next";
|
|
71734
72778
|
}
|
|
71735
72779
|
function hoodLaunchLegNames(count, buys) {
|
|
71736
72780
|
if (!buys)
|
|
71737
|
-
return
|
|
72781
|
+
return ["create curve"];
|
|
71738
72782
|
return count === 3 ? ["create curve", "fee", "first buy"] : ["create curve", "first buy"];
|
|
71739
72783
|
}
|
|
71740
72784
|
async function recordLaunchedToken(ctx, wallet, final) {
|
|
@@ -71759,6 +72803,7 @@ async function resumeHoodLaunch(ctx, key, id, operationId, prior) {
|
|
|
71759
72803
|
|
|
71760
72804
|
// src/commands/lp.ts
|
|
71761
72805
|
init_args();
|
|
72806
|
+
init_client();
|
|
71762
72807
|
init_render();
|
|
71763
72808
|
init_solana_endpoint();
|
|
71764
72809
|
init_solana_lite();
|
|
@@ -72301,6 +73346,7 @@ async function mcp(args, ctx) {
|
|
|
72301
73346
|
// src/commands/perps.ts
|
|
72302
73347
|
init_args();
|
|
72303
73348
|
init_bridge();
|
|
73349
|
+
init_client();
|
|
72304
73350
|
init_evm_lite();
|
|
72305
73351
|
import { randomUUID as randomUUID4, sign as sign3 } from "node:crypto";
|
|
72306
73352
|
|
|
@@ -72317,6 +73363,10 @@ var HYPERLIQUID_EXCHANGE_URLS = {
|
|
|
72317
73363
|
mainnet: "https://api.hyperliquid.xyz/exchange",
|
|
72318
73364
|
testnet: "https://api.hyperliquid-testnet.xyz/exchange"
|
|
72319
73365
|
};
|
|
73366
|
+
var HYPERLIQUID_INFO_URLS = {
|
|
73367
|
+
mainnet: "https://api.hyperliquid.xyz/info",
|
|
73368
|
+
testnet: "https://api.hyperliquid-testnet.xyz/info"
|
|
73369
|
+
};
|
|
72320
73370
|
var CANDLE_HYPERLIQUID_BUILDER_ADDRESS = null;
|
|
72321
73371
|
var HYPERLIQUID_MAX_BUILDER_FEE_TENTHS_BPS = 100;
|
|
72322
73372
|
var HYPERLIQUID_MAX_BUILDER_FEE_RATE = "0.1%";
|
|
@@ -72622,6 +73672,67 @@ async function hyperliquidCloseOrder(fetcher, network, address, params) {
|
|
|
72622
73672
|
const size = asked < held ? String(params.size) : String(szi).replace(/^-/, "");
|
|
72623
73673
|
return { asset, isBuy: signed < 0n, size };
|
|
72624
73674
|
}
|
|
73675
|
+
var HYPERLIQUID_DEFAULT_SLIPPAGE_BPS = 100;
|
|
73676
|
+
var HYPERLIQUID_MID_DRIFT_BPS = 100;
|
|
73677
|
+
async function hyperliquidOrderMarket(fetcher, network, address, method, params) {
|
|
73678
|
+
const read = async (body) => {
|
|
73679
|
+
const res = await fetcher(HYPERLIQUID_INFO_URLS[network], {
|
|
73680
|
+
method: "POST",
|
|
73681
|
+
headers: { "content-type": "application/json" },
|
|
73682
|
+
body: JSON.stringify(body)
|
|
73683
|
+
});
|
|
73684
|
+
if (!res.ok)
|
|
73685
|
+
throw new Error("Cannot read the market from Hyperliquid");
|
|
73686
|
+
return JSON.parse(await res.text());
|
|
73687
|
+
};
|
|
73688
|
+
let coin = String(params.coin ?? "").toUpperCase();
|
|
73689
|
+
let order;
|
|
73690
|
+
if (method === "modify") {
|
|
73691
|
+
const status = await read({ type: "orderStatus", user: address, oid: params.cloid });
|
|
73692
|
+
const resting = status?.order?.order ?? null;
|
|
73693
|
+
if (!resting || typeof resting.coin !== "string" || decimal(resting.limitPx) === null)
|
|
73694
|
+
throw new Error("No resting order with this cloid on Hyperliquid");
|
|
73695
|
+
coin = resting.coin.toUpperCase();
|
|
73696
|
+
order = { isBuy: resting.side === "B", limitPx: String(resting.limitPx) };
|
|
73697
|
+
}
|
|
73698
|
+
const [meta, contexts] = await read({ type: "metaAndAssetCtxs" });
|
|
73699
|
+
const asset = meta.universe.findIndex((m) => m.name.toUpperCase() === coin);
|
|
73700
|
+
if (asset < 0 || asset >= 1e4)
|
|
73701
|
+
throw new Error("No main-universe market for this coin on Hyperliquid");
|
|
73702
|
+
const context = contexts[asset];
|
|
73703
|
+
const midPx = context?.midPx ?? context?.markPx;
|
|
73704
|
+
if ((decimal(midPx) ?? 0n) <= 0n)
|
|
73705
|
+
throw new Error("No Hyperliquid price for this coin");
|
|
73706
|
+
return { asset, midPx: String(midPx), ...order ? { order } : {} };
|
|
73707
|
+
}
|
|
73708
|
+
function checkOrderMarket(action, intent, market) {
|
|
73709
|
+
const list = action.type === "modify" ? [action.order] : action.orders;
|
|
73710
|
+
if (!Array.isArray(list) || list.length === 0)
|
|
73711
|
+
return "missing requested order";
|
|
73712
|
+
if (list.some((o) => !o || o.a !== market.asset))
|
|
73713
|
+
return "order asset is not the requested coin's on Hyperliquid";
|
|
73714
|
+
const first = list[0];
|
|
73715
|
+
const p = intent.params;
|
|
73716
|
+
if (intent.method === "open" && p.price == null) {
|
|
73717
|
+
const requested = p.slippageBps;
|
|
73718
|
+
const slippage = typeof requested === "number" && Number.isInteger(requested) && requested > 0 ? requested : HYPERLIQUID_DEFAULT_SLIPPAGE_BPS;
|
|
73719
|
+
const bound = BigInt(slippage + HYPERLIQUID_MID_DRIFT_BPS);
|
|
73720
|
+
const px = decimal(first.p) ?? 0n;
|
|
73721
|
+
const mid = decimal(market.midPx) ?? 0n;
|
|
73722
|
+
const within = first.b === true ? px * 10000n <= mid * (10000n + bound) : px * 10000n >= mid * (10000n - bound);
|
|
73723
|
+
if (mid <= 0n || !within)
|
|
73724
|
+
return "market order price is outside its slippage of Hyperliquid's mid";
|
|
73725
|
+
}
|
|
73726
|
+
if (intent.method === "modify") {
|
|
73727
|
+
if (!market.order)
|
|
73728
|
+
return "no resting order on Hyperliquid to check the modify against";
|
|
73729
|
+
if (first.b !== market.order.isBuy)
|
|
73730
|
+
return "modify changes the resting order's side";
|
|
73731
|
+
if (p.price == null && decimal(first.p) !== decimal(market.order.limitPx))
|
|
73732
|
+
return "modify changes the resting order's price";
|
|
73733
|
+
}
|
|
73734
|
+
return null;
|
|
73735
|
+
}
|
|
72625
73736
|
function verifyPerpsBuild(build, opts) {
|
|
72626
73737
|
try {
|
|
72627
73738
|
const network = opts.network ?? "mainnet";
|
|
@@ -72649,6 +73760,13 @@ function verifyPerpsBuild(build, opts) {
|
|
|
72649
73760
|
if (!order || order.a !== opts.closeOrder.asset || order.b !== opts.closeOrder.isBuy || decimal(order.s) !== decimal(opts.closeOrder.size) || order.r !== true)
|
|
72650
73761
|
return { ok: false, reason: "close side, asset or size does not match the requested position" };
|
|
72651
73762
|
}
|
|
73763
|
+
if (opts.market) {
|
|
73764
|
+
if (opts.intent?.method !== "open" && opts.intent?.method !== "modify")
|
|
73765
|
+
return { ok: false, reason: "a market check needs an open or modify intent" };
|
|
73766
|
+
const marketRefusal = checkOrderMarket(action, opts.intent, opts.market);
|
|
73767
|
+
if (marketRefusal)
|
|
73768
|
+
return { ok: false, reason: marketRefusal };
|
|
73769
|
+
}
|
|
72652
73770
|
if (typed.primary_type === "Agent") {
|
|
72653
73771
|
const type = action.type;
|
|
72654
73772
|
if (typeof type !== "string" || !HYPERLIQUID_ALLOWED_ACTION_TYPES.includes(type)) {
|
|
@@ -72709,6 +73827,7 @@ function hyperliquidRelayBody(typedData) {
|
|
|
72709
73827
|
|
|
72710
73828
|
// src/commands/perps.ts
|
|
72711
73829
|
init_render();
|
|
73830
|
+
init_solana_verify();
|
|
72712
73831
|
init_trading();
|
|
72713
73832
|
init_swap();
|
|
72714
73833
|
var PERPS_SCOPE = "perps:write";
|
|
@@ -72855,6 +73974,18 @@ async function runPerpsWrite(ctx, opts) {
|
|
|
72855
73974
|
if (!closeCheck.ok)
|
|
72856
73975
|
throw new TradingError("PERPS_BUILD_REFUSED", `Refused to sign this build: ${closeCheck.reason}. Nothing was signed.`);
|
|
72857
73976
|
}
|
|
73977
|
+
if (method === "open") {
|
|
73978
|
+
const market = await hyperliquidOrderMarket(ctx.deps.fetch, net, wallet.address, method, opts.body);
|
|
73979
|
+
const marketCheck = verifyPerpsBuild(build, {
|
|
73980
|
+
builder,
|
|
73981
|
+
network: net,
|
|
73982
|
+
address: wallet.address,
|
|
73983
|
+
intent: { method, params: opts.body },
|
|
73984
|
+
market
|
|
73985
|
+
});
|
|
73986
|
+
if (!marketCheck.ok)
|
|
73987
|
+
throw new TradingError("PERPS_BUILD_REFUSED", `Refused to sign this build: ${marketCheck.reason}. Nothing was signed.`);
|
|
73988
|
+
}
|
|
72858
73989
|
if (!opts.submit)
|
|
72859
73990
|
return printTradingResult(ctx, { ...build, signed: false, submitted: false });
|
|
72860
73991
|
if (!await confirm(ctx, build, wallet.address, opts.yes)) {
|
|
@@ -73247,6 +74378,13 @@ async function perpsDeposit(args, ctx) {
|
|
|
73247
74378
|
const solanaProblem = await tradingRead(ctx, reader, () => relaySolanaDepositProblem(transaction, wallet.address, reader.rpc));
|
|
73248
74379
|
if (solanaProblem)
|
|
73249
74380
|
throw refused(solanaProblem);
|
|
74381
|
+
const spent = await tradingRead(ctx, reader, () => solanaSpendProblem(transaction, wallet.address, reader.rpc, {
|
|
74382
|
+
kind: "swap",
|
|
74383
|
+
spend: asset === "SOL" ? "SOL" : BASES[asset]?.mint ?? asset,
|
|
74384
|
+
maxSpendRaw: BigInt(amountRaw)
|
|
74385
|
+
}));
|
|
74386
|
+
if (spent)
|
|
74387
|
+
throw refused(spent);
|
|
73250
74388
|
}
|
|
73251
74389
|
const decimals = Number(build.outDecimals);
|
|
73252
74390
|
const minimum = decimalAmount(String(build.minimumOutRaw), decimals);
|
|
@@ -73312,6 +74450,7 @@ async function perpsDeposit(args, ctx) {
|
|
|
73312
74450
|
|
|
73313
74451
|
// src/commands/plans.ts
|
|
73314
74452
|
init_args();
|
|
74453
|
+
init_client();
|
|
73315
74454
|
|
|
73316
74455
|
// src/plans.ts
|
|
73317
74456
|
var PLAN_CAPABILITY_NOTE = "A capability marked yes is what the plan allows. It is subject to the deployment's own switches (perps and own-wallet bridges each have one) and to the wallet, scopes and setup the feature needs. Every plan can launch a token from its embedded wallet, with an optional dev buy (same transaction on Solana; best-effort follow-up on Hood), through the headless launch; the two launch rows are additional routes, not the only ones.";
|
|
@@ -73327,7 +74466,7 @@ var PLAN_CAPABILITY_LABELS = {
|
|
|
73327
74466
|
createLinkedWallets: "Create linked wallets",
|
|
73328
74467
|
importLinkedWallets: "Import linked wallets",
|
|
73329
74468
|
limitOrders: "Limit orders",
|
|
73330
|
-
quant: "Quant
|
|
74469
|
+
quant: "Quant deep analyses (Max)"
|
|
73331
74470
|
};
|
|
73332
74471
|
var PLAN_LABELS = { free: "Free", believer: "Believer", pro: "Pro", max: "Max" };
|
|
73333
74472
|
function planLabel(plan) {
|
|
@@ -73817,6 +74956,7 @@ async function runPlugin(name2, rawArgs, ctx) {
|
|
|
73817
74956
|
|
|
73818
74957
|
// src/commands/pnl.ts
|
|
73819
74958
|
init_args();
|
|
74959
|
+
init_client();
|
|
73820
74960
|
init_deps();
|
|
73821
74961
|
init_profiles();
|
|
73822
74962
|
init_render();
|
|
@@ -74264,6 +75404,7 @@ ${renderTable(headers, rows.map((row) => row.map(terminalText)))}
|
|
|
74264
75404
|
|
|
74265
75405
|
// src/commands/portfolio.ts
|
|
74266
75406
|
init_args();
|
|
75407
|
+
init_client();
|
|
74267
75408
|
init_deps();
|
|
74268
75409
|
init_evm_lite();
|
|
74269
75410
|
init_profiles();
|
|
@@ -74870,7 +76011,7 @@ function hoodHoldings(read, prices, tokensPart) {
|
|
|
74870
76011
|
...token.symbol ? { symbol: token.symbol } : {}
|
|
74871
76012
|
});
|
|
74872
76013
|
}
|
|
74873
|
-
const
|
|
76014
|
+
const holdings2 = raw.map(({ symbol, ...h }) => {
|
|
74874
76015
|
const price = prices[hoodPriceKey(h.mint)];
|
|
74875
76016
|
const priceUsd = price?.priceUsd ?? null;
|
|
74876
76017
|
const amount = formatAmount(h.amountRaw, h.decimals);
|
|
@@ -74887,7 +76028,7 @@ function hoodHoldings(read, prices, tokensPart) {
|
|
|
74887
76028
|
};
|
|
74888
76029
|
});
|
|
74889
76030
|
return {
|
|
74890
|
-
holdings,
|
|
76031
|
+
holdings: holdings2,
|
|
74891
76032
|
unread: [...read.wei === null ? ["eth"] : [], ...read.tokens === null ? [tokensPart] : []],
|
|
74892
76033
|
...read.truncated ? { truncated: true } : {}
|
|
74893
76034
|
};
|
|
@@ -75024,6 +76165,7 @@ ${renderTable(["GROUP", "WALLET", "POSITION", "POOL", "HOLDINGS", "UNCLAIMED FEE
|
|
|
75024
76165
|
|
|
75025
76166
|
// src/commands/profile.ts
|
|
75026
76167
|
init_args();
|
|
76168
|
+
init_client();
|
|
75027
76169
|
init_profiles();
|
|
75028
76170
|
init_render();
|
|
75029
76171
|
init_solana_endpoint();
|
|
@@ -75298,6 +76440,7 @@ async function profileRemove(args, ctx) {
|
|
|
75298
76440
|
|
|
75299
76441
|
// src/commands/setup.ts
|
|
75300
76442
|
init_args();
|
|
76443
|
+
init_client();
|
|
75301
76444
|
init_deps();
|
|
75302
76445
|
init_profiles();
|
|
75303
76446
|
init_render();
|
|
@@ -75495,6 +76638,10 @@ function formatAmount2(raw, decimals) {
|
|
|
75495
76638
|
function formatSol2(lamports) {
|
|
75496
76639
|
return `${formatAmount2(lamports, 9)} SOL`;
|
|
75497
76640
|
}
|
|
76641
|
+
function signerControlChanges(signers, simulation) {
|
|
76642
|
+
const addresses = new Set(signers.map((entry) => entry.address));
|
|
76643
|
+
return controlChanges(simulation.snapshots, (address) => addresses.has(address));
|
|
76644
|
+
}
|
|
75498
76645
|
function displayLines(input) {
|
|
75499
76646
|
const { tx, compiled, signers, simulation, mints } = input;
|
|
75500
76647
|
const lines = [];
|
|
@@ -75507,6 +76654,7 @@ function displayLines(input) {
|
|
|
75507
76654
|
for (const program of programs)
|
|
75508
76655
|
lines.push(`program ${programNameOf(program)}`);
|
|
75509
76656
|
const deltas = computeDeltas(simulation.snapshots);
|
|
76657
|
+
const changes = signerControlChanges(signers, simulation);
|
|
75510
76658
|
for (const entry of signers) {
|
|
75511
76659
|
const sol = deltas.sol.find((delta) => delta.address === entry.address);
|
|
75512
76660
|
if (sol) {
|
|
@@ -75514,7 +76662,7 @@ function displayLines(input) {
|
|
|
75514
76662
|
} else {
|
|
75515
76663
|
lines.push(`${entry.label.padEnd(11)} SOL unchanged (not a writable account of this transaction)`);
|
|
75516
76664
|
}
|
|
75517
|
-
for (const token of deltas.tokens.filter((delta) => delta.
|
|
76665
|
+
for (const token of deltas.tokens.filter((delta) => delta.ownerBefore === entry.address || delta.ownerAfter === entry.address)) {
|
|
75518
76666
|
const info = mints.get(token.mint);
|
|
75519
76667
|
lines.push(` ${token.mint}: ${formatAmount2(token.before, info?.decimals)} -> ${formatAmount2(token.after, info?.decimals)} (${token.after >= token.before ? "+" : ""}${formatAmount2(token.after - token.before, info?.decimals)}${info?.decimals === undefined ? "" : `, ${info.decimals} dp`}) in ${token.account}`);
|
|
75520
76668
|
}
|
|
@@ -75533,10 +76681,14 @@ function displayLines(input) {
|
|
|
75533
76681
|
const info = mints.get(token.mint);
|
|
75534
76682
|
others.push(`${token.owner} receives +${formatAmount2(token.after - token.before, info?.decimals)} of ${token.mint} (account ${token.account})`);
|
|
75535
76683
|
}
|
|
75536
|
-
if (others.length === 0)
|
|
76684
|
+
if (others.length === 0 && changes.length === 0)
|
|
75537
76685
|
lines.push("others no other account gains a balance in the simulation");
|
|
76686
|
+
if (others.length === 0 && changes.length > 0)
|
|
76687
|
+
lines.push("others no balance moves elsewhere, but control of an account changes hands (below)");
|
|
75538
76688
|
for (const line of others)
|
|
75539
76689
|
lines.push(`receives ${line}`);
|
|
76690
|
+
for (const change of changes)
|
|
76691
|
+
lines.push(`CONTROL ${describeControlChange(change)}`);
|
|
75540
76692
|
for (const [mint, info] of mints)
|
|
75541
76693
|
for (const risk of info.risks)
|
|
75542
76694
|
lines.push(`warning ${mint}: ${risk}`);
|
|
@@ -75650,6 +76802,14 @@ ${simulation.result.logs.map((line) => ` ${line}`).join(`
|
|
|
75650
76802
|
deps.stderr.write(` ${line}
|
|
75651
76803
|
`);
|
|
75652
76804
|
}
|
|
76805
|
+
const changes = signerControlChanges(signers, simulation);
|
|
76806
|
+
if (changes.length > 0) {
|
|
76807
|
+
throw new VaultError("SIGN_CONTROL_CHANGE", `The simulation hands control of a signer's account to someone else. Nothing was signed; there is no override.
|
|
76808
|
+
${changes.map((change) => ` ${describeControlChange(change)}`).join(`
|
|
76809
|
+
`)}`, {
|
|
76810
|
+
suggestion: "A transfer, swap or close never needs this. If you meant to change an authority, do it from a wallet you can inspect directly."
|
|
76811
|
+
});
|
|
76812
|
+
}
|
|
75653
76813
|
if (!yes)
|
|
75654
76814
|
await opened.confirm(`sign with ${signers.map((entry) => entry.label).join(", ")}`);
|
|
75655
76815
|
const signed = new Map;
|
|
@@ -76027,6 +77187,7 @@ init_tee_rebind();
|
|
|
76027
77187
|
|
|
76028
77188
|
// src/commands/tee-rename.ts
|
|
76029
77189
|
init_args();
|
|
77190
|
+
init_client();
|
|
76030
77191
|
init_deps();
|
|
76031
77192
|
init_profiles();
|
|
76032
77193
|
init_render();
|
|
@@ -76668,6 +77829,7 @@ init_args();
|
|
|
76668
77829
|
init_evm_lite();
|
|
76669
77830
|
init_profiles();
|
|
76670
77831
|
init_render();
|
|
77832
|
+
init_solana_verify();
|
|
76671
77833
|
init_trading();
|
|
76672
77834
|
init_swap();
|
|
76673
77835
|
var USAGE6 = "Usage: candle transfer --to <address|wallet name|vault> --asset <SOL|USDC|CNDL|ETH|USDG>|--mint <mint>|--token <0x...> --amount <decimal|max> [--wallet <name>] [--rpc-url <url>] [--yes] [--json]";
|
|
@@ -76782,11 +77944,12 @@ async function transfer(args, ctx) {
|
|
|
76782
77944
|
const wallet = payer.wallet;
|
|
76783
77945
|
const destination = await resolveDestination(ctx, key, wallet, to);
|
|
76784
77946
|
const label = asset ?? flags["--mint"];
|
|
77947
|
+
const solana = lazySolanaClient(ctx, flags["--rpc-url"]);
|
|
76785
77948
|
let amountRaw;
|
|
76786
77949
|
if (isMax)
|
|
76787
77950
|
amountRaw = "max";
|
|
76788
77951
|
else {
|
|
76789
|
-
const decimals = asset ? BASES[asset]?.decimals ?? 9 : await decimalsFor(ctx, flags["--mint"],
|
|
77952
|
+
const decimals = asset ? BASES[asset]?.decimals ?? 9 : await decimalsFor(ctx, flags["--mint"], solana);
|
|
76790
77953
|
amountRaw = rawAmount(flags["--amount"], decimals);
|
|
76791
77954
|
}
|
|
76792
77955
|
const amountText2 = isMax ? `the full spendable balance of ${label}` : `${flags["--amount"]} ${label}`;
|
|
@@ -76809,8 +77972,22 @@ async function transfer(args, ctx) {
|
|
|
76809
77972
|
throw new TradingError("INVALID_RESPONSE", "Candle did not return one unsigned transfer transaction; nothing was signed.");
|
|
76810
77973
|
if (typeof built.payerAddress === "string" && built.payerAddress !== wallet.address)
|
|
76811
77974
|
throw new TradingError("INVALID_RESPONSE", "The transfer build does not name the requested payer; nothing was signed.");
|
|
76812
|
-
const builtAmount = typeof built.amountRaw === "string" ? built.amountRaw :
|
|
76813
|
-
|
|
77975
|
+
const builtAmount = typeof built.amountRaw === "string" && /^[1-9]\d*$/.test(built.amountRaw) ? built.amountRaw : null;
|
|
77976
|
+
if (builtAmount === null || !isMax && builtAmount !== amountRaw)
|
|
77977
|
+
throw new TradingError("INVALID_RESPONSE", "The transfer build does not name the confirmed amount; nothing was signed.");
|
|
77978
|
+
const amountShown = asset ? `${decimalAmount(builtAmount, BASES[asset]?.decimals ?? 0)} ${asset}` : `${builtAmount} raw units of ${label}`;
|
|
77979
|
+
ctx.deps.stderr.write(`Built transfer ${transferId}: ${amountShown}${typeof built.destinationKind === "string" ? ` to ${built.destinationKind === "vault" ? "the vault" : "a linked wallet"}` : ""}.
|
|
77980
|
+
`);
|
|
77981
|
+
const reader = await solana();
|
|
77982
|
+
const problem = await tradingRead(ctx, reader, () => solanaSpendProblem(unsigned[0], wallet.address, reader.rpc, {
|
|
77983
|
+
kind: "transfer",
|
|
77984
|
+
asset: asset === undefined ? flags["--mint"] : asset === "SOL" ? "SOL" : BASES[asset]?.mint ?? asset,
|
|
77985
|
+
amountRaw: BigInt(builtAmount),
|
|
77986
|
+
recipient: destination.address
|
|
77987
|
+
}));
|
|
77988
|
+
if (problem)
|
|
77989
|
+
throw new TradingError("INVALID_RESPONSE", `The transaction Candle built does not move ${safeText(amountShown)} to ${destination.address} and nothing else (${safeText(problem)}); nothing was signed.`);
|
|
77990
|
+
ctx.deps.stderr.write(`Checked locally: it moves ${safeText(amountShown)} from ${wallet.address} to ${destination.address} and nothing else (simulated; a program can still behave differently once signed).
|
|
76814
77991
|
`);
|
|
76815
77992
|
const signed = await relaySign(ctx, key, wallet, unsigned[0]);
|
|
76816
77993
|
const result = await request(ctx, key, "/api/v1/agent/transfer/submit", {
|
|
@@ -77870,8 +79047,8 @@ function assertFloorCarried(copy, live) {
|
|
|
77870
79047
|
details: { step: "floor", missing: ids }
|
|
77871
79048
|
});
|
|
77872
79049
|
}
|
|
77873
|
-
function verifiedWithLine(
|
|
77874
|
-
return ` verified with ${
|
|
79050
|
+
function verifiedWithLine(word3, envelopeId, how, floorClause) {
|
|
79051
|
+
return ` verified with ${word3} ${envelopeId} (${how}); ${floorClause}`;
|
|
77875
79052
|
}
|
|
77876
79053
|
function carriedClause(passphraseIds) {
|
|
77877
79054
|
return `passphrase ${passphraseIds.join(", ")} carried byte for byte`;
|
|
@@ -79372,7 +80549,7 @@ Anyone who reads them can move every derived key's funds.
|
|
|
79372
80549
|
deps.stdout.write(`
|
|
79373
80550
|
`);
|
|
79374
80551
|
for (let i = 0;i < words.length; i += 4) {
|
|
79375
|
-
const row = words.slice(i, i + 4).map((
|
|
80552
|
+
const row = words.slice(i, i + 4).map((word3, offset) => `${String(i + offset + 1).padStart(2, " ")}. ${word3.padEnd(9, " ")}`).join(" ");
|
|
79376
80553
|
deps.stdout.write(` ${row.trimEnd()}
|
|
79377
80554
|
`);
|
|
79378
80555
|
}
|
|
@@ -79793,8 +80970,8 @@ async function collectOwn(ctx) {
|
|
|
79793
80970
|
// src/commands/vault-factor-dispatch.ts
|
|
79794
80971
|
init_vault_support();
|
|
79795
80972
|
async function vaultFactor(args, ctx) {
|
|
79796
|
-
const [
|
|
79797
|
-
switch (
|
|
80973
|
+
const [word3, ...rest] = args;
|
|
80974
|
+
switch (word3) {
|
|
79798
80975
|
case "list":
|
|
79799
80976
|
return vaultFactorList(rest, ctx);
|
|
79800
80977
|
case "add":
|
|
@@ -79804,7 +80981,7 @@ async function vaultFactor(args, ctx) {
|
|
|
79804
80981
|
case undefined:
|
|
79805
80982
|
return usage(ctx, "Usage: candle vault factor <list | add passphrase | add security-key | add touch-id | add passkey | remove <id>>");
|
|
79806
80983
|
default:
|
|
79807
|
-
return usage(ctx, `Unknown subcommand: vault factor ${
|
|
80984
|
+
return usage(ctx, `Unknown subcommand: vault factor ${word3}. Try: list, add, remove`);
|
|
79808
80985
|
}
|
|
79809
80986
|
}
|
|
79810
80987
|
|
|
@@ -81001,12 +82178,12 @@ function matches2(entry, filter) {
|
|
|
81001
82178
|
// src/commands/vault-phrase-dispatch.ts
|
|
81002
82179
|
init_vault_support();
|
|
81003
82180
|
async function vaultPhrase(args, ctx) {
|
|
81004
|
-
const [
|
|
81005
|
-
if (
|
|
82181
|
+
const [word3, ...rest] = args;
|
|
82182
|
+
if (word3 === "show")
|
|
81006
82183
|
return vaultPhraseShow(rest, ctx);
|
|
81007
|
-
if (
|
|
82184
|
+
if (word3 === undefined)
|
|
81008
82185
|
return usage(ctx, "Usage: candle vault phrase show");
|
|
81009
|
-
return usage(ctx, `Unknown subcommand: vault phrase ${
|
|
82186
|
+
return usage(ctx, `Unknown subcommand: vault phrase ${word3}. The only one is: show`);
|
|
81010
82187
|
}
|
|
81011
82188
|
|
|
81012
82189
|
// src/commands/vault-promote.ts
|
|
@@ -81019,6 +82196,7 @@ init_solana_endpoint();
|
|
|
81019
82196
|
init_solana_lite();
|
|
81020
82197
|
|
|
81021
82198
|
// src/vault/account-room.ts
|
|
82199
|
+
init_client();
|
|
81022
82200
|
init_deps();
|
|
81023
82201
|
init_errors2();
|
|
81024
82202
|
async function readAccountRoom(ctx) {
|
|
@@ -81316,8 +82494,8 @@ async function promoteEvmInPlace(input, subjectLabel, sweepTo) {
|
|
|
81316
82494
|
...toKey.keySigner !== undefined ? { signer: toKey.keySigner.fingerprint } : {}
|
|
81317
82495
|
});
|
|
81318
82496
|
const height = await promoteHeight(ctx, client);
|
|
81319
|
-
const
|
|
81320
|
-
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())) {
|
|
81321
82499
|
ctx.deps.stdout.write(`${line}
|
|
81322
82500
|
`);
|
|
81323
82501
|
}
|
|
@@ -81411,7 +82589,7 @@ async function promoteEvmInPlace(input, subjectLabel, sweepTo) {
|
|
|
81411
82589
|
lifecycle: updated?.tee?.lifecycle ?? null,
|
|
81412
82590
|
linkedWalletId: updated?.linkedWalletId ?? null,
|
|
81413
82591
|
controlledBy: controlledByJson(controlledBy),
|
|
81414
|
-
holdings: { eth:
|
|
82592
|
+
holdings: { eth: holdings2.eth.toString(), usdg: holdings2.usdg.toString(), weth: holdings2.weth.toString() },
|
|
81415
82593
|
scanStart: { block: scanStart.block, recorded: scanStart.recorded },
|
|
81416
82594
|
vaultVersion: reopened.file.version,
|
|
81417
82595
|
...rebound?.json ?? {}
|
|
@@ -84353,6 +85531,7 @@ init_promote_support();
|
|
|
84353
85531
|
init_store();
|
|
84354
85532
|
|
|
84355
85533
|
// src/vault/tee-transfer.ts
|
|
85534
|
+
init_client();
|
|
84356
85535
|
init_errors2();
|
|
84357
85536
|
function assertNoPendingSweep(entry) {
|
|
84358
85537
|
const pending = entry.tee?.sweepPending?.length ?? 0;
|
|
@@ -84694,6 +85873,7 @@ init_wallets();
|
|
|
84694
85873
|
|
|
84695
85874
|
// src/commands/wallets-allow-launch.ts
|
|
84696
85875
|
init_args();
|
|
85876
|
+
init_client();
|
|
84697
85877
|
init_deps();
|
|
84698
85878
|
init_profiles();
|
|
84699
85879
|
init_render();
|
|
@@ -85012,6 +86192,7 @@ async function walletsCloseEmpty(args, ctx) {
|
|
|
85012
86192
|
|
|
85013
86193
|
// src/commands/wallets-trust.ts
|
|
85014
86194
|
init_args();
|
|
86195
|
+
init_client();
|
|
85015
86196
|
init_deps();
|
|
85016
86197
|
init_profiles();
|
|
85017
86198
|
init_render();
|
|
@@ -85269,6 +86450,7 @@ init_render();
|
|
|
85269
86450
|
init_secret_store();
|
|
85270
86451
|
|
|
85271
86452
|
// src/update-notice.ts
|
|
86453
|
+
init_client();
|
|
85272
86454
|
init_release();
|
|
85273
86455
|
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
85274
86456
|
async function maybeWriteUpdateNotice(deps, opts = {}) {
|
|
@@ -85469,12 +86651,12 @@ var COMMANDS = {
|
|
|
85469
86651
|
};
|
|
85470
86652
|
var ROUTED_COMMANDS = new Set(Object.keys(COMMANDS));
|
|
85471
86653
|
var ALIASES = { wallet: "wallets" };
|
|
85472
|
-
function canonicalCommand(
|
|
85473
|
-
return
|
|
86654
|
+
function canonicalCommand(word3) {
|
|
86655
|
+
return word3 !== undefined && Object.hasOwn(ALIASES, word3) ? ALIASES[word3] : word3;
|
|
85474
86656
|
}
|
|
85475
|
-
var ROUTED_SUBCOMMANDS = Object.fromEntries(Object.entries(COMMANDS).filter(([, route]) => route.subcommands !== undefined).map(([
|
|
85476
|
-
function routeFor(
|
|
85477
|
-
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;
|
|
85478
86660
|
}
|
|
85479
86661
|
function subHandlerFor(route, sub) {
|
|
85480
86662
|
const subcommands = route?.subcommands;
|
|
@@ -85516,8 +86698,8 @@ async function run2(argv, deps) {
|
|
|
85516
86698
|
return 1;
|
|
85517
86699
|
}
|
|
85518
86700
|
const extractedForNotice = extractGlobalFlags(argv);
|
|
85519
|
-
const
|
|
85520
|
-
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 });
|
|
85521
86703
|
return code;
|
|
85522
86704
|
}
|
|
85523
86705
|
async function runCommand(argv, deps) {
|
|
@@ -85528,7 +86710,7 @@ async function runCommand(argv, deps) {
|
|
|
85528
86710
|
return 2;
|
|
85529
86711
|
}
|
|
85530
86712
|
const { rest, flags } = extracted;
|
|
85531
|
-
const plugin = pluginInvocation(argv, deps.env, (
|
|
86713
|
+
const plugin = pluginInvocation(argv, deps.env, (word4) => ROUTED_COMMANDS.has(canonicalCommand(word4) ?? ""));
|
|
85532
86714
|
if (plugin !== undefined) {
|
|
85533
86715
|
const leading = extractGlobalFlags(argv.slice(0, argv.length - plugin.args.length - 1));
|
|
85534
86716
|
const pluginFlags = "error" in leading ? flags : leading.flags;
|
|
@@ -85609,9 +86791,9 @@ async function runCommand(argv, deps) {
|
|
|
85609
86791
|
vaultFactor: flags.vaultFactor,
|
|
85610
86792
|
vaultDevice: flags.vaultDevice
|
|
85611
86793
|
};
|
|
85612
|
-
const
|
|
85613
|
-
const actsAsIdentity =
|
|
85614
|
-
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) {
|
|
85615
86797
|
const verdict = await verifyProfileAccount(ctx, config2);
|
|
85616
86798
|
if (!verdict.ok) {
|
|
85617
86799
|
writeLocalFailure(deps, { code: "ACCOUNT_MISMATCH", message: verdict.message, suggestion: verdict.suggestion }, flags.json);
|
|
@@ -85645,11 +86827,11 @@ function splitFix(message) {
|
|
|
85645
86827
|
const fixAt = message.indexOf(" Run: ");
|
|
85646
86828
|
return fixAt === -1 ? { message } : { message: message.slice(0, fixAt), suggestion: message.slice(fixAt + 1) };
|
|
85647
86829
|
}
|
|
85648
|
-
function unknownCommand(deps, token,
|
|
86830
|
+
function unknownCommand(deps, token, word3) {
|
|
85649
86831
|
if (token !== undefined)
|
|
85650
86832
|
deps.stderr.write(`Unknown command: ${token}
|
|
85651
86833
|
`);
|
|
85652
|
-
deps.stderr.write((
|
|
86834
|
+
deps.stderr.write((word3 === undefined ? undefined : renderTopic(word3)) ?? renderTopLevel());
|
|
85653
86835
|
return 1;
|
|
85654
86836
|
}
|
|
85655
86837
|
async function migrateProfiles(deps) {
|
|
@@ -85670,10 +86852,13 @@ async function migrateProfiles(deps) {
|
|
|
85670
86852
|
await deps.writeConfig({ profiles: config2.profiles, activeProfile: config2.activeProfile });
|
|
85671
86853
|
return config2;
|
|
85672
86854
|
}
|
|
85673
|
-
function realOpenBrowser(
|
|
86855
|
+
function realOpenBrowser(serverUrl) {
|
|
86856
|
+
const url = browserUrl(serverUrl);
|
|
86857
|
+
if (url === null)
|
|
86858
|
+
return;
|
|
85674
86859
|
try {
|
|
85675
86860
|
const platform = process.platform;
|
|
85676
|
-
const child = platform === "darwin" ? spawn3("open", [url], { stdio: "ignore", detached: true }) : platform === "win32" ? spawn3("
|
|
86861
|
+
const child = platform === "darwin" ? spawn3("open", [url], { stdio: "ignore", detached: true }) : platform === "win32" ? spawn3("rundll32", ["url.dll,FileProtocolHandler", url], { stdio: "ignore", detached: true }) : spawn3("xdg-open", [url], { stdio: "ignore", detached: true });
|
|
85677
86862
|
child.on("error", () => {});
|
|
85678
86863
|
child.unref();
|
|
85679
86864
|
} catch {}
|