@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/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.9";
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 record = asRecord(body);
6721
- if (typeof record?.code === "string")
6722
- return record.code;
6723
- const inner = asRecord(record?.error);
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 record = asRecord(body) ?? {};
6731
- const relayCode = typeof record.code === "string" ? record.code : undefined;
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 record.retryable === "boolean")
6735
- refusal.retryable = record.retryable;
6736
- if (typeof record.resetsAt === "string" || typeof record.resetsAt === "number")
6737
- refusal.resetsAt = record.resetsAt;
6738
- if (typeof record.turnId === "string")
6739
- refusal.turnId = record.turnId;
6740
- if (typeof record.limit === "number")
6741
- refusal.limit = record.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 query = new URLSearchParams;
7224
+ const query2 = new URLSearchParams;
6877
7225
  if (input.before !== undefined)
6878
- query.set("before", input.before);
7226
+ query2.set("before", input.before);
6879
7227
  if (input.limit !== undefined)
6880
- query.set("limit", String(input.limit));
6881
- const encoded = query.toString();
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"}