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