@candledottv/agent-sdk 0.6.9 → 0.6.11
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 +68 -1
- package/dist/client.d.ts +250 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +378 -19
- package/dist/lab.d.ts +85 -0
- package/dist/lab.d.ts.map +1 -0
- package/dist/token-clusters.d.ts +36 -0
- package/dist/token-clusters.d.ts.map +1 -0
- package/dist/token-pages.d.ts +85 -0
- package/dist/token-pages.d.ts.map +1 -0
- package/dist/token-pages.types.d.ts +153 -0
- package/dist/token-pages.types.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4389,6 +4389,201 @@ async function readPerpsHistoryPage(req) {
|
|
|
4389
4389
|
return kind === "fills" ? { ...envelope, fills: page } : { ...envelope, funding: page };
|
|
4390
4390
|
}
|
|
4391
4391
|
|
|
4392
|
+
// src/token-clusters.ts
|
|
4393
|
+
var CLUSTER_REASONS = [
|
|
4394
|
+
"disabled",
|
|
4395
|
+
"invalid_config",
|
|
4396
|
+
"unsupported_chain",
|
|
4397
|
+
"invalid_mint",
|
|
4398
|
+
"key_missing",
|
|
4399
|
+
"gate_uncalibrated",
|
|
4400
|
+
"market_data_missing",
|
|
4401
|
+
"market_data_stale",
|
|
4402
|
+
"token_too_new",
|
|
4403
|
+
"gate_not_met",
|
|
4404
|
+
"not_indexed",
|
|
4405
|
+
"empty_analysis",
|
|
4406
|
+
"invalid_response",
|
|
4407
|
+
"timeout",
|
|
4408
|
+
"upstream_unavailable",
|
|
4409
|
+
"upstream_4xx",
|
|
4410
|
+
"upstream_5xx",
|
|
4411
|
+
"rate_limited",
|
|
4412
|
+
"quota_exhausted",
|
|
4413
|
+
"budget_exhausted",
|
|
4414
|
+
"budget_unavailable",
|
|
4415
|
+
"request_in_progress",
|
|
4416
|
+
"transport_error",
|
|
4417
|
+
"internal_unavailable"
|
|
4418
|
+
];
|
|
4419
|
+
var CLUSTER_MESSAGE = "Cluster analysis not available for this token";
|
|
4420
|
+
var CLUSTER_LABEL = "Third-party cluster analysis";
|
|
4421
|
+
var CLUSTER_CAVEAT = "Third-party estimates of linked wallets. Exchange, pool and service wallets may be grouped together. These figures do not establish common ownership, insider conduct or wrongdoing.";
|
|
4422
|
+
function clusterUnavailable(mint, reason, retryAt) {
|
|
4423
|
+
return {
|
|
4424
|
+
status: "unavailable",
|
|
4425
|
+
mint,
|
|
4426
|
+
reason,
|
|
4427
|
+
message: CLUSTER_MESSAGE,
|
|
4428
|
+
...retryAt !== undefined && Number.isFinite(retryAt) ? { retryAt: new Date(retryAt).toISOString() } : {}
|
|
4429
|
+
};
|
|
4430
|
+
}
|
|
4431
|
+
function readClusterResult(value, mint) {
|
|
4432
|
+
if (!value || typeof value !== "object")
|
|
4433
|
+
return null;
|
|
4434
|
+
const v = value;
|
|
4435
|
+
if (v.mint !== mint)
|
|
4436
|
+
return null;
|
|
4437
|
+
if (v.status === "unavailable" && CLUSTER_REASONS.includes(v.reason)) {
|
|
4438
|
+
const retry = v.retryAt === undefined ? undefined : typeof v.retryAt === "string" ? Date.parse(v.retryAt) : NaN;
|
|
4439
|
+
if (retry !== undefined && !Number.isFinite(retry))
|
|
4440
|
+
return null;
|
|
4441
|
+
return clusterUnavailable(mint, v.reason, retry);
|
|
4442
|
+
}
|
|
4443
|
+
if (v.status !== "available" || v.chain !== "solana" || v.source !== "third_party" || !["hit", "miss"].includes(v.cache) || typeof v.fetchedAt !== "string" || typeof v.expiresAt !== "string" || !Number.isFinite(Date.parse(v.fetchedAt)) || !(Date.parse(v.expiresAt) > Date.parse(v.fetchedAt)))
|
|
4444
|
+
return null;
|
|
4445
|
+
const a = v.analysis;
|
|
4446
|
+
const percent = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0 && n <= 100;
|
|
4447
|
+
if (!a || !percent(a.clusterSupplyPct) || !Array.isArray(a.clusters) || !a.clusters.length || a.clusters.length > 100)
|
|
4448
|
+
return null;
|
|
4449
|
+
let addresses = 0;
|
|
4450
|
+
for (const c of a.clusters) {
|
|
4451
|
+
if (!c || !percent(c.supplyPct) || c.supplyPct > a.clusterSupplyPct + 0.01 || !Array.isArray(c.addresses) || !Array.isArray(c.tags) || c.tags.length > 32 || c.tags.some((t) => typeof t !== "string" || t.length > 100) || c.addresses.some((x) => typeof x !== "string" || !/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(x)))
|
|
4452
|
+
return null;
|
|
4453
|
+
addresses += c.addresses.length;
|
|
4454
|
+
}
|
|
4455
|
+
if (addresses > 1000 || a.clusterSupplyPct === 0 && a.clusters.every((c) => c.supplyPct === 0))
|
|
4456
|
+
return null;
|
|
4457
|
+
return {
|
|
4458
|
+
status: "available",
|
|
4459
|
+
chain: "solana",
|
|
4460
|
+
mint,
|
|
4461
|
+
source: "third_party",
|
|
4462
|
+
label: CLUSTER_LABEL,
|
|
4463
|
+
caveat: CLUSTER_CAVEAT,
|
|
4464
|
+
fetchedAt: v.fetchedAt,
|
|
4465
|
+
expiresAt: v.expiresAt,
|
|
4466
|
+
cache: v.cache,
|
|
4467
|
+
analysis: {
|
|
4468
|
+
clusterSupplyPct: a.clusterSupplyPct,
|
|
4469
|
+
clusters: a.clusters.map((c) => ({ supplyPct: c.supplyPct, addresses: [...c.addresses], tags: [...c.tags] }))
|
|
4470
|
+
}
|
|
4471
|
+
};
|
|
4472
|
+
}
|
|
4473
|
+
|
|
4474
|
+
// src/token-pages.ts
|
|
4475
|
+
class TokenPageError extends CandleApiError {
|
|
4476
|
+
idempotencyKey;
|
|
4477
|
+
latest;
|
|
4478
|
+
retryAfterHeader;
|
|
4479
|
+
constructor(error, options = {}) {
|
|
4480
|
+
super(error);
|
|
4481
|
+
this.name = "TokenPageError";
|
|
4482
|
+
this.idempotencyKey = options.idempotencyKey;
|
|
4483
|
+
this.latest = options.latest;
|
|
4484
|
+
this.retryAfterHeader = options.retryAfterHeader ?? null;
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
function createTokenPagesClient(io) {
|
|
4488
|
+
function path(token, suffix = "") {
|
|
4489
|
+
return `/api/v1/token-pages/${encodeURIComponent(token.chain)}/${encodeURIComponent(token.address)}${suffix}`;
|
|
4490
|
+
}
|
|
4491
|
+
function query(options = {}) {
|
|
4492
|
+
const params = new URLSearchParams;
|
|
4493
|
+
if (options.limit !== undefined)
|
|
4494
|
+
params.set("limit", String(options.limit));
|
|
4495
|
+
if (options.cursor !== undefined)
|
|
4496
|
+
params.set("cursor", options.cursor);
|
|
4497
|
+
return params.size ? `?${params}` : "";
|
|
4498
|
+
}
|
|
4499
|
+
async function request(method, url, authenticated, body, idempotencyKey) {
|
|
4500
|
+
const headers = io.headers(authenticated);
|
|
4501
|
+
if (body !== undefined)
|
|
4502
|
+
headers["content-type"] = "application/json";
|
|
4503
|
+
if (idempotencyKey !== undefined)
|
|
4504
|
+
headers["Idempotency-Key"] = idempotencyKey;
|
|
4505
|
+
const init = {
|
|
4506
|
+
method,
|
|
4507
|
+
headers,
|
|
4508
|
+
redirect: "error",
|
|
4509
|
+
cache: "no-store",
|
|
4510
|
+
...body !== undefined ? { body: JSON.stringify(body) } : {}
|
|
4511
|
+
};
|
|
4512
|
+
const attempts = idempotencyKey === undefined ? 1 : 2;
|
|
4513
|
+
for (let attempt = 0;attempt < attempts; attempt++) {
|
|
4514
|
+
let response;
|
|
4515
|
+
let text;
|
|
4516
|
+
try {
|
|
4517
|
+
response = await io.fetch(`${io.apiUrl()}${url}`, init);
|
|
4518
|
+
text = await response.text();
|
|
4519
|
+
} catch {
|
|
4520
|
+
if (attempt + 1 < attempts)
|
|
4521
|
+
continue;
|
|
4522
|
+
throw new TokenPageError(new CandleApiError({
|
|
4523
|
+
code: idempotencyKey ? "OUTCOME_UNKNOWN" : "NETWORK_ERROR",
|
|
4524
|
+
status: 0,
|
|
4525
|
+
message: idempotencyKey ? "No complete answer; replay the same body with this idempotency key." : "No complete answer from Candle.",
|
|
4526
|
+
retryable: true
|
|
4527
|
+
}), { idempotencyKey });
|
|
4528
|
+
}
|
|
4529
|
+
if (!response.ok) {
|
|
4530
|
+
let refusal = {};
|
|
4531
|
+
try {
|
|
4532
|
+
refusal = JSON.parse(text);
|
|
4533
|
+
} catch {}
|
|
4534
|
+
const error = refusal?.success === false && typeof refusal.error === "string" ? new CandleApiError({
|
|
4535
|
+
code: refusal.error,
|
|
4536
|
+
message: `Token page request refused (${refusal.error}).`,
|
|
4537
|
+
status: response.status,
|
|
4538
|
+
retryable: false
|
|
4539
|
+
}) : candleApiErrorFromResponse(response.status, text);
|
|
4540
|
+
throw new TokenPageError(error, {
|
|
4541
|
+
idempotencyKey,
|
|
4542
|
+
latest: refusal?.latest,
|
|
4543
|
+
retryAfterHeader: response.headers.get("retry-after")
|
|
4544
|
+
});
|
|
4545
|
+
}
|
|
4546
|
+
try {
|
|
4547
|
+
const envelope = JSON.parse(text);
|
|
4548
|
+
if (envelope?.success !== true || !("data" in envelope))
|
|
4549
|
+
throw new Error;
|
|
4550
|
+
return envelope.data;
|
|
4551
|
+
} catch {
|
|
4552
|
+
throw new TokenPageError(new CandleApiError({
|
|
4553
|
+
code: idempotencyKey ? "OUTCOME_UNKNOWN" : "BAD_RESPONSE",
|
|
4554
|
+
message: "Candle returned an invalid response.",
|
|
4555
|
+
status: response.status,
|
|
4556
|
+
retryable: false
|
|
4557
|
+
}), { idempotencyKey });
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
throw new Error("Unreachable");
|
|
4561
|
+
}
|
|
4562
|
+
async function write(method, url, body, options = {}) {
|
|
4563
|
+
const idempotencyKey = options.idempotencyKey ?? crypto.randomUUID();
|
|
4564
|
+
if (!idempotencyKey.length || idempotencyKey.length > 128)
|
|
4565
|
+
throw new Error("idempotencyKey must be 1 to 128 characters");
|
|
4566
|
+
return { data: await request(method, url, true, body, idempotencyKey), idempotencyKey };
|
|
4567
|
+
}
|
|
4568
|
+
const sectionPath = (section) => `/sections/${encodeURIComponent(section)}`;
|
|
4569
|
+
return {
|
|
4570
|
+
updates: (token, options) => request("GET", path(token, `/updates${query(options)}`), false),
|
|
4571
|
+
postUpdate: (token, body, options) => write("POST", path(token, "/updates"), body, options),
|
|
4572
|
+
deletePost: (token, id, options) => write("DELETE", path(token, `/posts/${encodeURIComponent(id)}`), {}, options),
|
|
4573
|
+
seen: (token) => request("GET", path(token, "/seen"), true),
|
|
4574
|
+
markSeen: (token, visibleUpdateSequence, options) => write("POST", path(token, "/seen"), { visibleUpdateSequence }, options),
|
|
4575
|
+
about: (token) => request("GET", path(token), false),
|
|
4576
|
+
capabilities: (token) => request("GET", path(token, "/capabilities"), true),
|
|
4577
|
+
burns: (token, options) => request("GET", path(token, `/burns${query(options)}`), false),
|
|
4578
|
+
history: (token, section, options) => request("GET", path(token, `${sectionPath(section)}/history${query(options)}`), false),
|
|
4579
|
+
creatorChallenge: (token) => request("POST", path(token, "/creator-challenges"), true, {}),
|
|
4580
|
+
claim: (token, body, options) => write("POST", path(token, "/claims"), body, options),
|
|
4581
|
+
setSection: (token, section, body, options) => write("PUT", path(token, sectionPath(section)), body, options),
|
|
4582
|
+
restoreSection: (token, section, body, options) => write("POST", path(token, `${sectionPath(section)}/restore`), body, options),
|
|
4583
|
+
flag: (token, body, options) => write("POST", path(token, "/flags"), body, options)
|
|
4584
|
+
};
|
|
4585
|
+
}
|
|
4586
|
+
|
|
4392
4587
|
// src/wallet-import.ts
|
|
4393
4588
|
import { Chacha20Poly1305 } from "@hpke/chacha20poly1305";
|
|
4394
4589
|
import { CipherSuite, DhkemP256HkdfSha256, HkdfSha256 } from "@hpke/core";
|
|
@@ -4607,6 +4802,7 @@ function isAtomicSubmitOutcome(value) {
|
|
|
4607
4802
|
return false;
|
|
4608
4803
|
}
|
|
4609
4804
|
var DEFAULT_MAX_RETRIES = 3;
|
|
4805
|
+
var CLUSTER_READ_TIMEOUT_MS = 12000;
|
|
4610
4806
|
var DEFAULT_POLL_MS = 2000;
|
|
4611
4807
|
var DEFAULT_WAIT_TIMEOUT_MS = 180000;
|
|
4612
4808
|
function isLoopbackHost(hostname) {
|
|
@@ -4696,6 +4892,18 @@ function keyedRedirectGuard(doFetch) {
|
|
|
4696
4892
|
}
|
|
4697
4893
|
|
|
4698
4894
|
class CandleClient {
|
|
4895
|
+
tokenPages = createTokenPagesClient({
|
|
4896
|
+
fetch: (input, init) => this.fetchImpl(input, init),
|
|
4897
|
+
apiUrl: () => this.apiUrl,
|
|
4898
|
+
headers: (authenticated) => {
|
|
4899
|
+
if (authenticated)
|
|
4900
|
+
this.requireKey("tokenPages");
|
|
4901
|
+
const headers = this.headers();
|
|
4902
|
+
if (!authenticated)
|
|
4903
|
+
delete headers["x-api-key"];
|
|
4904
|
+
return headers;
|
|
4905
|
+
}
|
|
4906
|
+
});
|
|
4699
4907
|
apiUrl;
|
|
4700
4908
|
apiKey;
|
|
4701
4909
|
fetchImpl;
|
|
@@ -4861,9 +5069,48 @@ class CandleClient {
|
|
|
4861
5069
|
nextCursor: body.nextCursor
|
|
4862
5070
|
};
|
|
4863
5071
|
}
|
|
5072
|
+
async getTokenCard(chain, mint, opts = {}) {
|
|
5073
|
+
const query = opts.tag !== undefined ? `?${new URLSearchParams({ cst: opts.tag }).toString()}` : "";
|
|
5074
|
+
const body = await this.requestJson("GET", `/api/v1/tokens/${chain}/${encodeURIComponent(mint)}/card${query}`);
|
|
5075
|
+
return body.card;
|
|
5076
|
+
}
|
|
5077
|
+
async editTokenMetadata(request, opts = {}) {
|
|
5078
|
+
this.requireKey("editTokenMetadata()");
|
|
5079
|
+
const body = {
|
|
5080
|
+
...request.description !== undefined ? { description: request.description } : {},
|
|
5081
|
+
...request.socials !== undefined ? { socials: request.socials } : {}
|
|
5082
|
+
};
|
|
5083
|
+
const res = await this.fetchImpl(`${this.apiUrl}/api/v1/tokens/${request.chain}/${encodeURIComponent(request.mint)}/metadata`, {
|
|
5084
|
+
method: "PATCH",
|
|
5085
|
+
headers: { ...this.headers({ json: true }), "Idempotency-Key": opts.idempotencyKey ?? crypto.randomUUID() },
|
|
5086
|
+
body: JSON.stringify(body)
|
|
5087
|
+
});
|
|
5088
|
+
return (await this.parseResponse(res)).payload;
|
|
5089
|
+
}
|
|
5090
|
+
async tokenMetadataHistory(params) {
|
|
5091
|
+
const query = new URLSearchParams({
|
|
5092
|
+
...params.limit !== undefined ? { limit: String(params.limit) } : {},
|
|
5093
|
+
...params.cursor !== undefined ? { cursor: params.cursor } : {}
|
|
5094
|
+
}).toString();
|
|
5095
|
+
const body = await this.requestJson("GET", `/api/v1/tokens/${params.chain}/${encodeURIComponent(params.mint)}/metadata/history${query ? `?${query}` : ""}`);
|
|
5096
|
+
return body.payload;
|
|
5097
|
+
}
|
|
4864
5098
|
async verify(chain, mint) {
|
|
4865
5099
|
return this.requestJson("GET", `/api/v1/verify/${chain}/${encodeURIComponent(mint)}`);
|
|
4866
5100
|
}
|
|
5101
|
+
async getTokenClusters(mint, opts = {}) {
|
|
5102
|
+
const chain = opts.chain ?? "solana";
|
|
5103
|
+
try {
|
|
5104
|
+
const res = await this.fetchImpl(`${this.apiUrl}/api/v1/tokens/${encodeURIComponent(mint)}/clusters?chain=${encodeURIComponent(chain)}`, { method: "GET", cache: "no-store", signal: AbortSignal.timeout(CLUSTER_READ_TIMEOUT_MS) });
|
|
5105
|
+
noteLatestSdkVersion(res.headers?.get?.("x-candle-sdk-latest") ?? null);
|
|
5106
|
+
if (res.ok) {
|
|
5107
|
+
const body = JSON.parse(await res.text());
|
|
5108
|
+
if (body?.success === true)
|
|
5109
|
+
return readClusterResult(body.data, mint) ?? clusterUnavailable(mint, "transport_error");
|
|
5110
|
+
}
|
|
5111
|
+
} catch {}
|
|
5112
|
+
return clusterUnavailable(mint, "transport_error");
|
|
5113
|
+
}
|
|
4867
5114
|
async getOwnProfile() {
|
|
4868
5115
|
this.requireKey("getOwnProfile()");
|
|
4869
5116
|
return this.requestJson("GET", "/api/v1/agent/keys/self/public-profile");
|
|
@@ -6504,7 +6751,7 @@ function generateClientLaunchId() {
|
|
|
6504
6751
|
function generateClientTradeId() {
|
|
6505
6752
|
return generateSdkId();
|
|
6506
6753
|
}
|
|
6507
|
-
var SDK_VERSION = "0.6.
|
|
6754
|
+
var SDK_VERSION = "0.6.11";
|
|
6508
6755
|
var sdkUpdateWarned = false;
|
|
6509
6756
|
function noteLatestSdkVersion(value) {
|
|
6510
6757
|
if (sdkUpdateWarned || !value || !/^\d+\.\d+\.\d+$/.test(value))
|
|
@@ -6629,6 +6876,107 @@ class KeychainSecretStore {
|
|
|
6629
6876
|
await this.exec("secret-tool", ["clear", "service", SERVICE, "account", ref]);
|
|
6630
6877
|
}
|
|
6631
6878
|
}
|
|
6879
|
+
// src/lab.ts
|
|
6880
|
+
var LAB_DEFAULT_URL = "https://api.hacc.fun";
|
|
6881
|
+
var LAB_PATH = "/v1/strategy-lab";
|
|
6882
|
+
var LAB_CALL_TIMEOUT_MS = 1e4;
|
|
6883
|
+
var LAB_REPLAY_INTERVAL_MS = 5000;
|
|
6884
|
+
var LAB_REPLAY_TIMEOUT_MS = 660000;
|
|
6885
|
+
function record(value) {
|
|
6886
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6887
|
+
}
|
|
6888
|
+
function transportRefusal(code, message) {
|
|
6889
|
+
return { ok: false, refusal: { status: 0, code, message } };
|
|
6890
|
+
}
|
|
6891
|
+
function query(values) {
|
|
6892
|
+
const search = new URLSearchParams;
|
|
6893
|
+
for (const [key, value] of Object.entries(values))
|
|
6894
|
+
if (value !== undefined)
|
|
6895
|
+
search.set(key, String(value));
|
|
6896
|
+
return search.size ? `?${search}` : "";
|
|
6897
|
+
}
|
|
6898
|
+
function createLabClient(options) {
|
|
6899
|
+
const base = new URL(options.apiUrl ?? LAB_DEFAULT_URL);
|
|
6900
|
+
if (base.protocol !== "https:" || base.username || base.password || base.search || base.hash) {
|
|
6901
|
+
throw new Error("The lab needs an HTTPS CANDLE_HACC_URL without credentials, query or fragment.");
|
|
6902
|
+
}
|
|
6903
|
+
const root = base.toString().replace(/\/+$/, "");
|
|
6904
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
6905
|
+
const callMs = options.callTimeoutMs ?? LAB_CALL_TIMEOUT_MS;
|
|
6906
|
+
const now = options.now ?? Date.now;
|
|
6907
|
+
const sleep3 = options.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
|
|
6908
|
+
async function call(method, path, valid = () => true, body, timeoutMs = callMs) {
|
|
6909
|
+
const controller = new AbortController;
|
|
6910
|
+
let timedOut = false;
|
|
6911
|
+
const timer = setTimeout(() => {
|
|
6912
|
+
timedOut = true;
|
|
6913
|
+
controller.abort();
|
|
6914
|
+
}, timeoutMs);
|
|
6915
|
+
try {
|
|
6916
|
+
const response = await doFetch(`${root}${LAB_PATH}${path}`, {
|
|
6917
|
+
method,
|
|
6918
|
+
redirect: "error",
|
|
6919
|
+
signal: controller.signal,
|
|
6920
|
+
headers: {
|
|
6921
|
+
authorization: `Bearer ${options.apiKey}`,
|
|
6922
|
+
accept: "application/json",
|
|
6923
|
+
...body !== undefined ? { "content-type": "application/json" } : {}
|
|
6924
|
+
},
|
|
6925
|
+
...body !== undefined ? { body: JSON.stringify(body) } : {}
|
|
6926
|
+
});
|
|
6927
|
+
const received = await response.json().catch(() => {
|
|
6928
|
+
return;
|
|
6929
|
+
});
|
|
6930
|
+
if (timedOut)
|
|
6931
|
+
return transportRefusal("TIMEOUT", "The lab request timed out.");
|
|
6932
|
+
if (!response.ok) {
|
|
6933
|
+
return {
|
|
6934
|
+
ok: false,
|
|
6935
|
+
refusal: {
|
|
6936
|
+
status: response.status,
|
|
6937
|
+
code: record(received) && typeof received.code === "string" ? received.code : `HTTP_${response.status}`,
|
|
6938
|
+
message: record(received) && typeof received.error === "string" ? received.error : `Lab request failed (${response.status}).`,
|
|
6939
|
+
body: received
|
|
6940
|
+
}
|
|
6941
|
+
};
|
|
6942
|
+
}
|
|
6943
|
+
if (!record(received) || !valid(received)) {
|
|
6944
|
+
return transportRefusal("BAD_RESPONSE", "The lab returned an unreadable response.");
|
|
6945
|
+
}
|
|
6946
|
+
return { ok: true, status: response.status, body: received };
|
|
6947
|
+
} catch {
|
|
6948
|
+
return transportRefusal(timedOut ? "TIMEOUT" : "NETWORK_ERROR", timedOut ? "The lab request timed out." : "Could not reach the lab.");
|
|
6949
|
+
} finally {
|
|
6950
|
+
clearTimeout(timer);
|
|
6951
|
+
}
|
|
6952
|
+
}
|
|
6953
|
+
const jobPath = (id) => `/replay/${encodeURIComponent(String(id))}`;
|
|
6954
|
+
const validId = (id) => typeof id === "string" && id.length > 0 || typeof id === "number" && Number.isSafeInteger(id) && id > 0;
|
|
6955
|
+
const validJob = (body) => validId(body.job_id) && typeof body.status === "string";
|
|
6956
|
+
return {
|
|
6957
|
+
status: () => call("GET", "/status", (b) => typeof b.plan === "string" && typeof b.allowed === "boolean" && record(b.replays)),
|
|
6958
|
+
replay: (body) => call("POST", "/replay", (b) => validId(b.job_id), body),
|
|
6959
|
+
replayGet: (id) => call("GET", jobPath(id), validJob),
|
|
6960
|
+
replayCancel: (id) => call("POST", `${jobPath(id)}/cancel`),
|
|
6961
|
+
runners: (q = {}) => call("GET", `/runners${query(q)}`),
|
|
6962
|
+
edges: (q = {}) => call("GET", `/edges${query(q)}`),
|
|
6963
|
+
async waitForReplay(id, wait = {}) {
|
|
6964
|
+
const intervalMs = wait.intervalMs ?? LAB_REPLAY_INTERVAL_MS;
|
|
6965
|
+
const timeoutMs = wait.timeoutMs ?? LAB_REPLAY_TIMEOUT_MS;
|
|
6966
|
+
if (!Number.isFinite(intervalMs) || intervalMs <= 0 || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
6967
|
+
throw new Error("Replay polling interval and timeout must be positive finite numbers.");
|
|
6968
|
+
}
|
|
6969
|
+
const deadline = now() + timeoutMs;
|
|
6970
|
+
while (now() < deadline) {
|
|
6971
|
+
const answer = await call("GET", jobPath(id), validJob, undefined, Math.min(callMs, deadline - now()));
|
|
6972
|
+
if (!answer.ok || !["pending", "running"].includes(answer.body.status))
|
|
6973
|
+
return answer;
|
|
6974
|
+
await sleep3(Math.min(intervalMs, Math.max(0, deadline - now())));
|
|
6975
|
+
}
|
|
6976
|
+
return transportRefusal("REPLAY_TIMEOUT", `Replay ${id} is still pending or running. Read it with candle lab replay get ${id}.`);
|
|
6977
|
+
}
|
|
6978
|
+
};
|
|
6979
|
+
}
|
|
6632
6980
|
// src/plans.ts
|
|
6633
6981
|
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.";
|
|
6634
6982
|
var PLAN_CAPABILITY_LABELS = {
|
|
@@ -6717,28 +7065,28 @@ function newClientTurnId() {
|
|
|
6717
7065
|
return crypto.randomUUID();
|
|
6718
7066
|
}
|
|
6719
7067
|
function quantRefusalCode(body) {
|
|
6720
|
-
const
|
|
6721
|
-
if (typeof
|
|
6722
|
-
return
|
|
6723
|
-
const inner = asRecord(
|
|
7068
|
+
const record2 = asRecord(body);
|
|
7069
|
+
if (typeof record2?.code === "string")
|
|
7070
|
+
return record2.code;
|
|
7071
|
+
const inner = asRecord(record2?.error);
|
|
6724
7072
|
return typeof inner?.code === "string" ? inner.code : undefined;
|
|
6725
7073
|
}
|
|
6726
7074
|
function asRecord(value) {
|
|
6727
7075
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
6728
7076
|
}
|
|
6729
7077
|
function refusalOf(status, body) {
|
|
6730
|
-
const
|
|
6731
|
-
const relayCode = typeof
|
|
7078
|
+
const record2 = asRecord(body) ?? {};
|
|
7079
|
+
const relayCode = typeof record2.code === "string" ? record2.code : undefined;
|
|
6732
7080
|
const code = quantRefusalCode(body) ?? `HTTP_${status}`;
|
|
6733
7081
|
const refusal = { code, status, shape: relayCode !== undefined ? "relay" : "envelope" };
|
|
6734
|
-
if (typeof
|
|
6735
|
-
refusal.retryable =
|
|
6736
|
-
if (typeof
|
|
6737
|
-
refusal.resetsAt =
|
|
6738
|
-
if (typeof
|
|
6739
|
-
refusal.turnId =
|
|
6740
|
-
if (typeof
|
|
6741
|
-
refusal.limit =
|
|
7082
|
+
if (typeof record2.retryable === "boolean")
|
|
7083
|
+
refusal.retryable = record2.retryable;
|
|
7084
|
+
if (typeof record2.resetsAt === "string" || typeof record2.resetsAt === "number")
|
|
7085
|
+
refusal.resetsAt = record2.resetsAt;
|
|
7086
|
+
if (typeof record2.turnId === "string")
|
|
7087
|
+
refusal.turnId = record2.turnId;
|
|
7088
|
+
if (typeof record2.limit === "number")
|
|
7089
|
+
refusal.limit = record2.limit;
|
|
6742
7090
|
return refusal;
|
|
6743
7091
|
}
|
|
6744
7092
|
function parseStatus(body) {
|
|
@@ -6873,12 +7221,12 @@ function createQuantKeyClient(options) {
|
|
|
6873
7221
|
}),
|
|
6874
7222
|
cancel: (turnId) => call("POST", `${turnPath(turnId)}/cancel`, (body) => asRecord(body) ?? {}, { timeoutMs: callMs }),
|
|
6875
7223
|
history: (input = {}) => {
|
|
6876
|
-
const
|
|
7224
|
+
const query2 = new URLSearchParams;
|
|
6877
7225
|
if (input.before !== undefined)
|
|
6878
|
-
|
|
7226
|
+
query2.set("before", input.before);
|
|
6879
7227
|
if (input.limit !== undefined)
|
|
6880
|
-
|
|
6881
|
-
const encoded =
|
|
7228
|
+
query2.set("limit", String(input.limit));
|
|
7229
|
+
const encoded = query2.toString();
|
|
6882
7230
|
const suffix = encoded ? `?${encoded}` : "";
|
|
6883
7231
|
return call("GET", `/history${suffix}`, parseHistory, { timeoutMs: callMs });
|
|
6884
7232
|
}
|
|
@@ -7083,9 +7431,11 @@ export {
|
|
|
7083
7431
|
encryptWalletKeyForImport,
|
|
7084
7432
|
effectivePerpsRisk,
|
|
7085
7433
|
createQuantKeyClient,
|
|
7434
|
+
createLabClient,
|
|
7086
7435
|
TradeLegOutcomeError,
|
|
7087
7436
|
TradeLandedUnconfirmedError,
|
|
7088
7437
|
TradeLandedFeeLegError,
|
|
7438
|
+
TokenPageError,
|
|
7089
7439
|
SelfLaunchOutcomeError,
|
|
7090
7440
|
QUANT_POLL_TIMEOUT_MS,
|
|
7091
7441
|
QUANT_DEFAULT_MAX_TEXT_CHARS,
|
|
@@ -7103,6 +7453,11 @@ export {
|
|
|
7103
7453
|
PERPS_FUNDING_HISTORY_MAX_MS,
|
|
7104
7454
|
PERPS_FLATTEN_MAX_STEPS,
|
|
7105
7455
|
PERPS_CANDLE_INTERVALS,
|
|
7456
|
+
LAB_REPLAY_TIMEOUT_MS,
|
|
7457
|
+
LAB_REPLAY_INTERVAL_MS,
|
|
7458
|
+
LAB_PATH,
|
|
7459
|
+
LAB_DEFAULT_URL,
|
|
7460
|
+
LAB_CALL_TIMEOUT_MS,
|
|
7106
7461
|
KeychainSecretStore,
|
|
7107
7462
|
JsonRpcError,
|
|
7108
7463
|
InMemorySecretStore,
|
|
@@ -7116,6 +7471,10 @@ export {
|
|
|
7116
7471
|
CandleOperationError,
|
|
7117
7472
|
CandleClient,
|
|
7118
7473
|
CandleApiError,
|
|
7474
|
+
CLUSTER_REASONS,
|
|
7475
|
+
CLUSTER_MESSAGE,
|
|
7476
|
+
CLUSTER_LABEL,
|
|
7477
|
+
CLUSTER_CAVEAT,
|
|
7119
7478
|
CANDLE_PERPS_WITHDRAW_PINS,
|
|
7120
7479
|
CANDLE_HYPERLIQUID_BUILDER_ADDRESS,
|
|
7121
7480
|
BRIDGE_ERROR_CODES
|
package/dist/lab.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency-free hacc strategy lab client (BE-2038, strategy lab spec R18).
|
|
3
|
+
* The CLI carries a byte-identical copy. Copy changes verbatim:
|
|
4
|
+
* `cp packages/sdk/src/lab.ts packages/cli/src/lab.ts`.
|
|
5
|
+
* Bodies, including refusals and older results without a lead card, are kept unchanged.
|
|
6
|
+
*/
|
|
7
|
+
export declare const LAB_DEFAULT_URL = "https://api.hacc.fun";
|
|
8
|
+
export declare const LAB_PATH = "/v1/strategy-lab";
|
|
9
|
+
export declare const LAB_CALL_TIMEOUT_MS = 10000;
|
|
10
|
+
export declare const LAB_REPLAY_INTERVAL_MS = 5000;
|
|
11
|
+
export declare const LAB_REPLAY_TIMEOUT_MS = 660000;
|
|
12
|
+
export type LabRecord = Record<string, unknown>;
|
|
13
|
+
export type LabJobId = string | number;
|
|
14
|
+
export interface LabStatus extends LabRecord {
|
|
15
|
+
plan: string;
|
|
16
|
+
allowed: boolean;
|
|
17
|
+
replays: {
|
|
18
|
+
cap: number | null;
|
|
19
|
+
used: number | null;
|
|
20
|
+
resets_at: string;
|
|
21
|
+
};
|
|
22
|
+
active_job_id: LabJobId | null;
|
|
23
|
+
}
|
|
24
|
+
export interface LabReplayInput {
|
|
25
|
+
definition: LabRecord;
|
|
26
|
+
from: string;
|
|
27
|
+
to: string;
|
|
28
|
+
asset_ids?: number[];
|
|
29
|
+
cost?: {
|
|
30
|
+
fee_bps_per_side?: number;
|
|
31
|
+
slippage_bps_per_side?: number;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export interface LabReplayStarted extends LabRecord {
|
|
35
|
+
job_id: LabJobId;
|
|
36
|
+
}
|
|
37
|
+
export interface LabReplayJob extends LabReplayStarted {
|
|
38
|
+
status: string;
|
|
39
|
+
error?: string | null;
|
|
40
|
+
result?: LabRecord | null;
|
|
41
|
+
}
|
|
42
|
+
export interface LabRefusal {
|
|
43
|
+
status: number;
|
|
44
|
+
code: string;
|
|
45
|
+
message: string;
|
|
46
|
+
/** The server's complete body, retained for machine output and future additive fields. */
|
|
47
|
+
body?: unknown;
|
|
48
|
+
}
|
|
49
|
+
export type LabAnswer<T> = {
|
|
50
|
+
ok: true;
|
|
51
|
+
status: number;
|
|
52
|
+
body: T;
|
|
53
|
+
} | {
|
|
54
|
+
ok: false;
|
|
55
|
+
refusal: LabRefusal;
|
|
56
|
+
};
|
|
57
|
+
export interface LabClientOptions {
|
|
58
|
+
apiKey: string;
|
|
59
|
+
/** Pass CANDLE_HACC_URL here; it must be HTTPS, including for local servers. */
|
|
60
|
+
apiUrl?: string;
|
|
61
|
+
fetch?: typeof fetch;
|
|
62
|
+
callTimeoutMs?: number;
|
|
63
|
+
now?: () => number;
|
|
64
|
+
sleep?: (ms: number) => Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
export interface LabClient {
|
|
67
|
+
status(): Promise<LabAnswer<LabStatus>>;
|
|
68
|
+
replay(body: LabReplayInput): Promise<LabAnswer<LabReplayStarted>>;
|
|
69
|
+
replayGet(id: LabJobId): Promise<LabAnswer<LabReplayJob>>;
|
|
70
|
+
replayCancel(id: LabJobId): Promise<LabAnswer<LabRecord>>;
|
|
71
|
+
runners(q?: {
|
|
72
|
+
hours?: number;
|
|
73
|
+
min_wallets?: number;
|
|
74
|
+
}): Promise<LabAnswer<LabRecord>>;
|
|
75
|
+
edges(q?: {
|
|
76
|
+
days?: number;
|
|
77
|
+
min_n?: number;
|
|
78
|
+
}): Promise<LabAnswer<LabRecord>>;
|
|
79
|
+
waitForReplay(id: LabJobId, options?: {
|
|
80
|
+
intervalMs?: number;
|
|
81
|
+
timeoutMs?: number;
|
|
82
|
+
}): Promise<LabAnswer<LabReplayJob>>;
|
|
83
|
+
}
|
|
84
|
+
export declare function createLabClient(options: LabClientOptions): LabClient;
|
|
85
|
+
//# sourceMappingURL=lab.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lab.d.ts","sourceRoot":"","sources":["../src/lab.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Public protocol for the optional cluster read. Unavailable never contains numeric analysis. */
|
|
2
|
+
export declare const CLUSTER_REASONS: readonly ["disabled", "invalid_config", "unsupported_chain", "invalid_mint", "key_missing", "gate_uncalibrated", "market_data_missing", "market_data_stale", "token_too_new", "gate_not_met", "not_indexed", "empty_analysis", "invalid_response", "timeout", "upstream_unavailable", "upstream_4xx", "upstream_5xx", "rate_limited", "quota_exhausted", "budget_exhausted", "budget_unavailable", "request_in_progress", "transport_error", "internal_unavailable"];
|
|
3
|
+
export type ClusterReason = (typeof CLUSTER_REASONS)[number];
|
|
4
|
+
export declare const CLUSTER_MESSAGE = "Cluster analysis not available for this token";
|
|
5
|
+
export declare const CLUSTER_LABEL = "Third-party cluster analysis";
|
|
6
|
+
export declare const CLUSTER_CAVEAT = "Third-party estimates of linked wallets. Exchange, pool and service wallets may be grouped together. These figures do not establish common ownership, insider conduct or wrongdoing.";
|
|
7
|
+
export interface ClusterAnalysis {
|
|
8
|
+
clusterSupplyPct: number;
|
|
9
|
+
clusters: {
|
|
10
|
+
supplyPct: number;
|
|
11
|
+
addresses: string[];
|
|
12
|
+
tags: string[];
|
|
13
|
+
}[];
|
|
14
|
+
}
|
|
15
|
+
export type ClusterResult = {
|
|
16
|
+
status: "available";
|
|
17
|
+
chain: "solana";
|
|
18
|
+
mint: string;
|
|
19
|
+
source: "third_party";
|
|
20
|
+
label: typeof CLUSTER_LABEL;
|
|
21
|
+
caveat: string;
|
|
22
|
+
fetchedAt: string;
|
|
23
|
+
expiresAt: string;
|
|
24
|
+
cache: "hit" | "miss";
|
|
25
|
+
analysis: ClusterAnalysis;
|
|
26
|
+
} | {
|
|
27
|
+
status: "unavailable";
|
|
28
|
+
mint: string;
|
|
29
|
+
reason: ClusterReason;
|
|
30
|
+
message: typeof CLUSTER_MESSAGE;
|
|
31
|
+
retryAt?: string;
|
|
32
|
+
};
|
|
33
|
+
export declare function clusterUnavailable(mint: string, reason: ClusterReason, retryAt?: number): ClusterResult;
|
|
34
|
+
/** Construct only protocol fields; malformed/unknown server output is a client availability failure. */
|
|
35
|
+
export declare function readClusterResult(value: unknown, mint: string): ClusterResult | null;
|
|
36
|
+
//# sourceMappingURL=token-clusters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-clusters.d.ts","sourceRoot":"","sources":["../src/token-clusters.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,eAAO,MAAM,eAAe,scAyBlB,CAAA;AACV,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAC5D,eAAO,MAAM,eAAe,kDAAkD,CAAA;AAC9E,eAAO,MAAM,aAAa,iCAAiC,CAAA;AAC3D,eAAO,MAAM,cAAc,yLAC6J,CAAA;AACxL,MAAM,WAAW,eAAe;IAC9B,gBAAgB,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAA;CACvE;AACD,MAAM,MAAM,aAAa,GACrB;IACE,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,QAAQ,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,aAAa,CAAA;IACrB,KAAK,EAAE,OAAO,aAAa,CAAA;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,KAAK,GAAG,MAAM,CAAA;IACrB,QAAQ,EAAE,eAAe,CAAA;CAC1B,GACD;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,OAAO,eAAe,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AACrH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,aAAa,CAQvG;AAED,wGAAwG;AACxG,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAuDpF"}
|