@candledottv/agent-sdk 0.6.3 → 0.6.5

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
@@ -2035,18 +2035,55 @@ function perpsCandleWindow(opts, now) {
2035
2035
  return usage(`interval must be one of ${PERPS_CANDLE_INTERVALS.join(" ")}.`);
2036
2036
  const count = bounded(opts.count, 100, 1, 5000, "count");
2037
2037
  const raw = opts.end;
2038
- const endTime = raw === undefined ? now : typeof raw === "number" || /^\d+$/.test(raw) ? Number(raw) : /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})$/.test(raw) ? Date.parse(raw) : NaN;
2038
+ const endTime = raw === undefined ? now : epoch(raw);
2039
2039
  const startTime = endTime - count * at(INTERVAL_MS, index);
2040
2040
  if (!Number.isSafeInteger(endTime) || endTime < 0 || endTime > now || startTime < 0)
2041
2041
  return usage("end must be an ISO 8601 time or epoch milliseconds, no later than now.");
2042
2042
  return { interval, count, startTime, endTime };
2043
2043
  }
2044
+ function epoch(raw) {
2045
+ return typeof raw === "number" || /^\d+$/.test(raw) ? Number(raw) : /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})$/.test(raw) ? Date.parse(raw) : NaN;
2046
+ }
2047
+ var PERPS_FUNDING_HISTORY_MAX_MS = 30 * 86400000;
2048
+ var PERPS_FUNDING_HISTORY_MAX_ROWS = 500;
2049
+ function perpsFundingWindow(opts, now) {
2050
+ const endTime = opts.end === undefined ? now : epoch(opts.end);
2051
+ if (!Number.isSafeInteger(endTime) || endTime < 0 || endTime > now)
2052
+ return usage("end must be an ISO 8601 time or epoch milliseconds, no later than now.");
2053
+ const startTime = opts.start === undefined ? endTime - 86400000 : epoch(opts.start);
2054
+ if (!Number.isSafeInteger(startTime) || startTime < 0 || startTime > endTime)
2055
+ return usage("start must be an ISO 8601 time or epoch milliseconds, no later than end.");
2056
+ if (endTime - startTime > PERPS_FUNDING_HISTORY_MAX_MS)
2057
+ return usage("start must be at most 30 days before end.");
2058
+ return { startTime, endTime };
2059
+ }
2044
2060
  function bounded(value, fallback, min, max, name) {
2045
2061
  const n = value ?? fallback;
2046
2062
  if (!Number.isInteger(n) || n < min || n > max)
2047
2063
  return usage(`${name} must be an integer from ${min} to ${max}.`);
2048
2064
  return n;
2049
2065
  }
2066
+ function parsePerpsFundingHistory(value, coin, window) {
2067
+ if (!Array.isArray(value))
2068
+ return unavailable();
2069
+ const funding = [];
2070
+ for (const row of value) {
2071
+ if (!object(row) || row.coin !== coin || !integer(row.time) || !decimal2(row.fundingRate) || !decimal2(row.premium))
2072
+ return unavailable();
2073
+ if (row.time >= window.startTime && row.time <= window.endTime)
2074
+ funding.push({
2075
+ time: row.time,
2076
+ fundingRate: row.fundingRate,
2077
+ fundingAprPct: fixed(scaled(row.fundingRate) * 876000n, 2),
2078
+ premium: row.premium
2079
+ });
2080
+ }
2081
+ funding.sort((a, b) => a.time - b.time);
2082
+ return {
2083
+ funding: funding.slice(0, PERPS_FUNDING_HISTORY_MAX_ROWS),
2084
+ truncated: funding.length > PERPS_FUNDING_HISTORY_MAX_ROWS
2085
+ };
2086
+ }
2050
2087
 
2051
2088
  class PerpsMarketReader {
2052
2089
  opts;
@@ -2324,6 +2361,44 @@ class PerpsMarketReader {
2324
2361
  candles: candles.slice(-window.count)
2325
2362
  };
2326
2363
  }
2364
+ async fundingHistory(coin, opts = {}) {
2365
+ this.key();
2366
+ const window = perpsFundingWindow(opts, this.now());
2367
+ const [config, core] = await Promise.all([this.config(), this.core()]);
2368
+ const canonical2 = at(core.universe, resolve(core, coin)).name;
2369
+ const value = await info(this.opts.fetcher, this.opts.network, {
2370
+ type: "fundingHistory",
2371
+ coin: canonical2,
2372
+ startTime: window.startTime,
2373
+ endTime: window.endTime
2374
+ });
2375
+ return {
2376
+ ...this.envelope(config),
2377
+ coin: canonical2,
2378
+ startTime: window.startTime,
2379
+ endTime: window.endTime,
2380
+ ...parsePerpsFundingHistory(value, canonical2, window)
2381
+ };
2382
+ }
2383
+ async predictedFunding() {
2384
+ this.key();
2385
+ const [config, core, extras] = await Promise.all([this.config(), this.core(), this.extras()]);
2386
+ if (extras.predicted === null)
2387
+ return unavailable();
2388
+ const predicted = [];
2389
+ for (const meta of core.universe) {
2390
+ const row = extras.predicted.get(meta.name);
2391
+ if (row)
2392
+ predicted.push({
2393
+ coin: meta.name,
2394
+ fundingRate: row.fundingRate,
2395
+ fundingAprPct: fixed(scaled(row.fundingRate) * 876000n, 2),
2396
+ nextFundingTime: row.nextFundingTime
2397
+ });
2398
+ }
2399
+ predicted.sort((a, b) => a.coin < b.coin ? -1 : a.coin > b.coin ? 1 : 0);
2400
+ return { ...this.envelope(config, extras.warnings, this.extrasCache?.at ?? this.now()), predicted };
2401
+ }
2327
2402
  }
2328
2403
 
2329
2404
  // src/hyperliquid-risk.ts
@@ -3087,7 +3162,6 @@ var CANDLE_PERPS_WITHDRAW_PINS = {
3087
3162
  depositories: ["0x66cf0aace1b4e562593bec10ec7868fba9932224"],
3088
3163
  amountForm: "fixed6"
3089
3164
  };
3090
- var RELAY_API_URL = "https://api.relay.link";
3091
3165
  var ZERO = "0x0000000000000000000000000000000000000000";
3092
3166
  var EVM_ADDRESS_RE2 = /^0x[0-9a-fA-F]{40}$/;
3093
3167
  var BYTES32_RE = /^0x[0-9a-fA-F]{64}$/;
@@ -3184,8 +3258,10 @@ function verifyWithdrawMapping(build, expect) {
3184
3258
  return refuse("the NonceMapping wallet is not this key's account");
3185
3259
  if (!sameEvm(message.depositor, expect.account))
3186
3260
  return refuse("the NonceMapping depositor is not this key's account");
3187
- if (typeof message.id !== "string" || !BYTES32_RE.test(message.id) || message.id !== build.relayRequestId)
3188
- return refuse("the NonceMapping names another Relay request");
3261
+ if (typeof build.relayOrderId !== "string" || !BYTES32_RE.test(build.relayOrderId))
3262
+ return refuse("the build has no Relay order id");
3263
+ if (typeof message.id !== "string" || !BYTES32_RE.test(message.id) || message.id !== build.relayOrderId)
3264
+ return refuse("the NonceMapping names another Relay order");
3189
3265
  if (message.nonce !== build.nonce || !Number.isSafeInteger(build.nonce) || build.nonce <= 0)
3190
3266
  return refuse("the NonceMapping nonce is not the build's");
3191
3267
  return { ok: true };
@@ -3232,24 +3308,6 @@ function verifyWithdrawSendAsset(authorization, expect) {
3232
3308
  }
3233
3309
  return { ok: true };
3234
3310
  }
3235
- async function relayRecipientProblem(fetchImpl, requestId, recipient, relayUrl = RELAY_API_URL) {
3236
- let body;
3237
- try {
3238
- const res = await fetchImpl(`${relayUrl}/requests/v3?id=${encodeURIComponent(requestId)}`, { method: "GET" });
3239
- if (!res.ok)
3240
- return `Relay's record of the request could not be read (HTTP ${res.status})`;
3241
- body = await res.json();
3242
- } catch (error) {
3243
- return `Relay's record of the request could not be read (${error instanceof Error ? error.message : "unknown"})`;
3244
- }
3245
- const requests = isObject(body) && Array.isArray(body.requests) ? body.requests : [];
3246
- const match = requests.find((r) => isObject(r) && r.id === requestId);
3247
- if (!isObject(match))
3248
- return "Relay does not list this request";
3249
- const named = match.recipient;
3250
- const same = recipient.startsWith("0x") ? sameEvm(named, recipient) : named === recipient;
3251
- return same ? null : `Relay's request pays ${String(named)}, not this key's wallet ${recipient}`;
3252
- }
3253
3311
 
3254
3312
  // src/internal/rpc-endpoint.ts
3255
3313
  function describeRpcEndpoint(url) {
@@ -4167,15 +4225,59 @@ class CandleClient {
4167
4225
  }
4168
4226
  async previewCloseEmptyAccounts(req = {}) {
4169
4227
  this.requireKey("previewCloseEmptyAccounts()");
4170
- return this.requestJson("POST", "/api/v1/agent/wallets/embedded/close-empty/preview", req);
4228
+ const { wallet, ...body } = req;
4229
+ return this.requestJson("POST", wallet === undefined ? "/api/v1/agent/wallets/embedded/close-empty/preview" : `/api/v1/agent/wallets/${encodeURIComponent(wallet)}/close-empty/preview`, body);
4171
4230
  }
4172
4231
  async closeEmptyAccounts(req) {
4173
4232
  this.requireKey("closeEmptyAccounts()");
4174
- return this.requestJson("POST", "/api/v1/agent/wallets/embedded/close-empty", req);
4233
+ const { wallet, privyWalletId, ...body } = req;
4234
+ if (wallet === undefined)
4235
+ return this.requestJson("POST", "/api/v1/agent/wallets/embedded/close-empty", body);
4236
+ if (!privyWalletId)
4237
+ throw new Error("closeEmptyAccounts() with a TEE wallet requires privyWalletId: the same wallet's Privy wallet id, which signLinkedTransaction() signs for");
4238
+ const base = `/api/v1/agent/wallets/${encodeURIComponent(wallet)}/close-empty`;
4239
+ const build = await this.requestJson("POST", `${base}/build`, body);
4240
+ if (build.status === "nothing_to_close")
4241
+ return {
4242
+ closed: [],
4243
+ signatures: [],
4244
+ lamportsRecovered: "0",
4245
+ solRecovered: "0",
4246
+ feeLamports: "0",
4247
+ netLamports: "0",
4248
+ netSol: "0",
4249
+ failed: [],
4250
+ unattempted: [],
4251
+ notClosed: [],
4252
+ skipped: [],
4253
+ remaining: 0,
4254
+ ...build
4255
+ };
4256
+ if (build.submitState !== undefined) {
4257
+ const job = await this.getCloseEmptyAccountsJob(req.clientTradeId, { wallet });
4258
+ const status = job.httpStatus ?? 200;
4259
+ if (status >= 400)
4260
+ throw candleApiErrorFromResponse(status, JSON.stringify(job.result ?? {}));
4261
+ return job.result;
4262
+ }
4263
+ const signed = [];
4264
+ for (const transaction of build.transactions) {
4265
+ const result = await this.signLinkedTransaction({
4266
+ chain: "solana",
4267
+ linkedWalletId: wallet,
4268
+ privyWalletId,
4269
+ unsignedTransactionBase64: transaction.unsignedTransaction
4270
+ });
4271
+ signed.push({ index: transaction.index, signedTransaction: result.signedTransaction });
4272
+ }
4273
+ return this.requestJson("POST", `${base}/submit`, {
4274
+ clientTradeId: req.clientTradeId,
4275
+ signed
4276
+ });
4175
4277
  }
4176
- async getCloseEmptyAccountsJob(clientTradeId) {
4278
+ async getCloseEmptyAccountsJob(clientTradeId, opts = {}) {
4177
4279
  this.requireKey("getCloseEmptyAccountsJob()");
4178
- const body = await this.requestJson("GET", `/api/v1/agent/wallets/embedded/close-empty/jobs/${encodeURIComponent(clientTradeId)}`);
4280
+ const body = await this.requestJson("GET", opts.wallet === undefined ? `/api/v1/agent/wallets/embedded/close-empty/jobs/${encodeURIComponent(clientTradeId)}` : `/api/v1/agent/wallets/${encodeURIComponent(opts.wallet)}/close-empty/jobs/${encodeURIComponent(clientTradeId)}`);
4179
4281
  return body.job;
4180
4282
  }
4181
4283
  async importWallet(params) {
@@ -4533,6 +4635,10 @@ class CandleClient {
4533
4635
  const body = { ...req, clientLaunchId };
4534
4636
  return this.runOperation({ clientLaunchId }, "server-executed", (op) => this.postAtomicBuild(body, clientLaunchId, op));
4535
4637
  }
4638
+ async dryRunAtomicLaunch(req) {
4639
+ this.requireKey("dryRunAtomicLaunch()");
4640
+ return this.requestJson("POST", "/api/v1/launch/atomic/dry-run", req);
4641
+ }
4536
4642
  async postAtomicBuild(body, clientLaunchId, op) {
4537
4643
  const built = await this.requestJson("POST", "/api/v1/launch/atomic/build", body, op);
4538
4644
  return { ...built, clientLaunchId };
@@ -4965,7 +5071,8 @@ class CandleClient {
4965
5071
  asset: params.asset,
4966
5072
  amountRaw: params.amountRaw,
4967
5073
  ...params.perpsWalletId !== undefined ? { perpsWalletId: params.perpsWalletId } : {},
4968
- ...params.maxSlippageBps !== undefined ? { maxSlippageBps: params.maxSlippageBps } : {}
5074
+ ...params.maxSlippageBps !== undefined ? { maxSlippageBps: params.maxSlippageBps } : {},
5075
+ ...params.submit === false ? { preview: true } : {}
4969
5076
  });
4970
5077
  if (build.job !== undefined)
4971
5078
  throw new Error(`perpsDeposit(): ${params.clientDepositId} already names a deposit; read it with perpsDepositStatus()`);
@@ -4974,6 +5081,8 @@ class CandleClient {
4974
5081
  throw new Error(`perps: refused to sign this deposit: ${problem}`);
4975
5082
  if (params.submit === false)
4976
5083
  return { build, submitted: false, result: null };
5084
+ if (build.preview === true || build.depositId === undefined)
5085
+ throw new Error("perps: refused to sign this deposit: the API answered a preview, not a build");
4977
5086
  const submitPath = "/api/v1/agent/perps/deposit/submit";
4978
5087
  const ids = { clientDepositId: params.clientDepositId, depositId: build.depositId };
4979
5088
  if (build.chain === "solana") {
@@ -5192,9 +5301,6 @@ class CandleClient {
5192
5301
  });
5193
5302
  if (!mapping.ok)
5194
5303
  throw new Error(`perpsWithdraw: refused to sign this withdrawal: ${mapping.reason}`);
5195
- const recipient = await relayRecipientProblem(this.fetchImpl, build.relayRequestId, build.to.address);
5196
- if (recipient)
5197
- throw new Error(`perpsWithdraw: refused to sign this withdrawal: ${recipient}`);
5198
5304
  const mappingSignature = await this.signLinkedTypedData({
5199
5305
  linkedWalletId: params.walletId,
5200
5306
  privyWalletId: params.privyWalletId,
@@ -5452,7 +5558,7 @@ function generateClientLaunchId() {
5452
5558
  function generateClientTradeId() {
5453
5559
  return generateSdkId();
5454
5560
  }
5455
- var SDK_VERSION = "0.6.3";
5561
+ var SDK_VERSION = "0.6.5";
5456
5562
  var sdkUpdateWarned = false;
5457
5563
  function noteLatestSdkVersion(value) {
5458
5564
  if (sdkUpdateWarned || !value || !/^\d+\.\d+\.\d+$/.test(value))
@@ -5650,6 +5756,188 @@ function planTableMarkdown(table) {
5650
5756
  return [line(headers), line(headers.map(() => "---")), ...rows.map(line)].join(`
5651
5757
  `);
5652
5758
  }
5759
+ // src/quant.ts
5760
+ var QUANT_AGENT_PATH = "/api/v1/agent/quant";
5761
+ var QUANT_CALL_TIMEOUT_MS = 5000;
5762
+ var QUANT_POLL_TIMEOUT_MS = 35000;
5763
+ var QUANT_DEFAULT_MAX_TEXT_CHARS = 4000;
5764
+ function quantCodePoints(text) {
5765
+ let n = 0;
5766
+ for (const _ of text)
5767
+ n++;
5768
+ return n;
5769
+ }
5770
+ function newClientTurnId() {
5771
+ return crypto.randomUUID();
5772
+ }
5773
+ function quantRefusalCode(body) {
5774
+ const record = asRecord(body);
5775
+ if (typeof record?.code === "string")
5776
+ return record.code;
5777
+ const inner = asRecord(record?.error);
5778
+ return typeof inner?.code === "string" ? inner.code : undefined;
5779
+ }
5780
+ function asRecord(value) {
5781
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
5782
+ }
5783
+ function refusalOf(status, body) {
5784
+ const record = asRecord(body) ?? {};
5785
+ const relayCode = typeof record.code === "string" ? record.code : undefined;
5786
+ const code = quantRefusalCode(body) ?? `HTTP_${status}`;
5787
+ const refusal = { code, status, shape: relayCode !== undefined ? "relay" : "envelope" };
5788
+ if (typeof record.retryable === "boolean")
5789
+ refusal.retryable = record.retryable;
5790
+ if (typeof record.resetsAt === "string" || typeof record.resetsAt === "number")
5791
+ refusal.resetsAt = record.resetsAt;
5792
+ if (typeof record.turnId === "string")
5793
+ refusal.turnId = record.turnId;
5794
+ if (typeof record.limit === "number")
5795
+ refusal.limit = record.limit;
5796
+ return refusal;
5797
+ }
5798
+ function parseStatus(body) {
5799
+ const b = asRecord(body);
5800
+ if (typeof b?.enabled !== "boolean")
5801
+ return;
5802
+ const status = { enabled: b.enabled };
5803
+ if (typeof b.allowed === "boolean")
5804
+ status.allowed = b.allowed;
5805
+ if (typeof b.tier === "string" || b.tier === null)
5806
+ status.tier = b.tier;
5807
+ if (typeof b.marketAccess === "boolean")
5808
+ status.marketAccess = b.marketAccess;
5809
+ if (typeof b.linked === "boolean")
5810
+ status.linked = b.linked;
5811
+ if (typeof b.remaining === "number" || b.remaining === null)
5812
+ status.remaining = b.remaining;
5813
+ if (typeof b.maxTextChars === "number" || b.maxTextChars === null)
5814
+ status.maxTextChars = b.maxTextChars;
5815
+ if (typeof b.activeTurnId === "string" || b.activeTurnId === null)
5816
+ status.activeTurnId = b.activeTurnId;
5817
+ return status;
5818
+ }
5819
+ function parseTurn(body) {
5820
+ const b = asRecord(body);
5821
+ if (typeof b?.turnId !== "string" || b.turnId.length === 0)
5822
+ return;
5823
+ return { turnId: b.turnId, ...typeof b.cursor === "number" ? { cursor: b.cursor } : {} };
5824
+ }
5825
+ function parseEvents(body) {
5826
+ const b = asRecord(body);
5827
+ if (!b || !Array.isArray(b.events) || typeof b.next !== "number" || typeof b.status !== "string")
5828
+ return;
5829
+ const events = [];
5830
+ for (const raw of b.events) {
5831
+ const e = asRecord(raw);
5832
+ if (typeof e?.seq !== "number" || typeof e.kind !== "string")
5833
+ continue;
5834
+ events.push({
5835
+ seq: e.seq,
5836
+ kind: e.kind,
5837
+ data: asRecord(e.data) ?? {},
5838
+ ...typeof e.createdAt === "string" ? { createdAt: e.createdAt } : {}
5839
+ });
5840
+ }
5841
+ return { events, next: b.next, status: b.status };
5842
+ }
5843
+ function parseHistory(body) {
5844
+ const b = asRecord(body);
5845
+ if (!b || !Array.isArray(b.items))
5846
+ return;
5847
+ const items = [];
5848
+ for (const raw of b.items) {
5849
+ const item = asRecord(raw);
5850
+ if (typeof item?.id !== "string" || typeof item.text !== "string")
5851
+ continue;
5852
+ if (item.role !== "user" && item.role !== "assistant")
5853
+ continue;
5854
+ items.push({
5855
+ id: item.id,
5856
+ role: item.role,
5857
+ text: item.text,
5858
+ createdAt: typeof item.createdAt === "string" ? item.createdAt : ""
5859
+ });
5860
+ }
5861
+ return { items, next: typeof b.next === "string" ? b.next : null };
5862
+ }
5863
+ function createQuantKeyClient(options) {
5864
+ const base = new URL(options.apiUrl);
5865
+ const local = base.hostname === "localhost" || base.hostname === "127.0.0.1" || base.hostname === "[::1]";
5866
+ if (base.protocol !== "https:" && !(base.protocol === "http:" && local)) {
5867
+ throw new Error("Quant needs an https API URL: the key would otherwise travel in clear.");
5868
+ }
5869
+ const root = base.toString().replace(/\/+$/, "");
5870
+ const doFetch = options.fetch ?? globalThis.fetch;
5871
+ const callMs = options.callTimeoutMs ?? QUANT_CALL_TIMEOUT_MS;
5872
+ const pollMs = options.pollTimeoutMs ?? QUANT_POLL_TIMEOUT_MS;
5873
+ async function call(method, path, parse, opts) {
5874
+ const controller = new AbortController;
5875
+ let timedOut = false;
5876
+ const timer = setTimeout(() => {
5877
+ timedOut = true;
5878
+ controller.abort();
5879
+ }, opts.timeoutMs);
5880
+ const onAbort = () => controller.abort();
5881
+ opts.signal?.addEventListener("abort", onAbort, { once: true });
5882
+ try {
5883
+ const response = await doFetch(`${root}${QUANT_AGENT_PATH}${path}`, {
5884
+ method,
5885
+ redirect: "error",
5886
+ signal: controller.signal,
5887
+ headers: {
5888
+ "x-api-key": options.apiKey,
5889
+ accept: "application/json",
5890
+ ...opts.body !== undefined ? { "content-type": "application/json" } : {}
5891
+ },
5892
+ ...opts.body !== undefined ? { body: JSON.stringify(opts.body) } : {}
5893
+ });
5894
+ let body;
5895
+ try {
5896
+ body = await response.json();
5897
+ } catch {
5898
+ body = undefined;
5899
+ }
5900
+ if (!response.ok)
5901
+ return { ok: false, refusal: refusalOf(response.status, body) };
5902
+ const parsed = parse(body);
5903
+ if (parsed === undefined) {
5904
+ return { ok: false, refusal: { code: "BAD_RESPONSE", status: response.status, shape: "transport" } };
5905
+ }
5906
+ return { ok: true, status: response.status, body: parsed };
5907
+ } catch {
5908
+ return {
5909
+ ok: false,
5910
+ refusal: { code: timedOut ? "TIMEOUT" : "NETWORK_ERROR", status: 0, shape: "transport", retryable: true }
5911
+ };
5912
+ } finally {
5913
+ clearTimeout(timer);
5914
+ opts.signal?.removeEventListener("abort", onAbort);
5915
+ }
5916
+ }
5917
+ const turnPath = (turnId) => `/turns/${encodeURIComponent(turnId)}`;
5918
+ return {
5919
+ status: () => call("GET", "/status", parseStatus, { timeoutMs: callMs }),
5920
+ sendTurn: (input) => call("POST", "/turns", parseTurn, {
5921
+ body: { text: input.text, clientTurnId: input.clientTurnId },
5922
+ timeoutMs: callMs
5923
+ }),
5924
+ events: (turnId, after, opts) => call("GET", `${turnPath(turnId)}/events?after=${Math.max(0, Math.floor(after))}`, parseEvents, {
5925
+ timeoutMs: pollMs,
5926
+ ...opts?.signal ? { signal: opts.signal } : {}
5927
+ }),
5928
+ cancel: (turnId) => call("POST", `${turnPath(turnId)}/cancel`, (body) => asRecord(body) ?? {}, { timeoutMs: callMs }),
5929
+ history: (input = {}) => {
5930
+ const query = new URLSearchParams;
5931
+ if (input.before !== undefined)
5932
+ query.set("before", input.before);
5933
+ if (input.limit !== undefined)
5934
+ query.set("limit", String(input.limit));
5935
+ const encoded = query.toString();
5936
+ const suffix = encoded ? `?${encoded}` : "";
5937
+ return call("GET", `/history${suffix}`, parseHistory, { timeoutMs: callMs });
5938
+ }
5939
+ };
5940
+ }
5653
5941
  // src/file-lock.ts
5654
5942
  var STALE_MS = 30000;
5655
5943
  var RETRY_MS = 25;
@@ -5815,10 +6103,11 @@ export {
5815
6103
  verifyWebhookSignature,
5816
6104
  verifyPerpsBuildAgainstVenue,
5817
6105
  verifyPerpsBuild,
5818
- relayRecipientProblem,
5819
6106
  readPerpsRisk,
5820
6107
  readPerpsHistoryPage,
5821
6108
  readHyperliquidMarkets,
6109
+ quantRefusalCode,
6110
+ quantCodePoints,
5822
6111
  planTableRows,
5823
6112
  planTableMarkdown,
5824
6113
  planPromotionLine,
@@ -5828,11 +6117,14 @@ export {
5828
6117
  perpsRiskExitCode,
5829
6118
  perpsPortfolioSection,
5830
6119
  perpsMarketGate,
6120
+ perpsFundingWindow,
5831
6121
  perpsDepositProblem,
5832
6122
  perpsCandleWindow,
5833
6123
  parseWithdrawAmount,
5834
6124
  parseSendAssetAmount,
6125
+ parsePerpsFundingHistory,
5835
6126
  operationIds,
6127
+ newClientTurnId,
5836
6128
  isSolanaRpcErrorData,
5837
6129
  hyperliquidWalletSnapshot,
5838
6130
  hyperliquidRiskReport,
@@ -5844,10 +6136,15 @@ export {
5844
6136
  formatPlanBps,
5845
6137
  encryptWalletKeyForImport,
5846
6138
  effectivePerpsRisk,
6139
+ createQuantKeyClient,
5847
6140
  TradeLegOutcomeError,
5848
6141
  TradeLandedUnconfirmedError,
5849
6142
  TradeLandedFeeLegError,
5850
6143
  SelfLaunchOutcomeError,
6144
+ QUANT_POLL_TIMEOUT_MS,
6145
+ QUANT_DEFAULT_MAX_TEXT_CHARS,
6146
+ QUANT_CALL_TIMEOUT_MS,
6147
+ QUANT_AGENT_PATH,
5851
6148
  PerpsWorkflowError,
5852
6149
  PerpsMarketReader,
5853
6150
  PerpsMarketError,
@@ -5856,6 +6153,8 @@ export {
5856
6153
  PLAN_CAPABILITY_LABELS,
5857
6154
  PERPS_HISTORY_MAX_LIMIT,
5858
6155
  PERPS_HISTORY_DEFAULT_LIMIT,
6156
+ PERPS_FUNDING_HISTORY_MAX_ROWS,
6157
+ PERPS_FUNDING_HISTORY_MAX_MS,
5859
6158
  PERPS_FLATTEN_MAX_STEPS,
5860
6159
  PERPS_CANDLE_INTERVALS,
5861
6160
  KeychainSecretStore,
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Quant over an agent key (BE-1678's `/api/v1/agent/quant/*`; spec S5 Q-2, BE-1737): a plain HTTP
3
+ * client for the five key routes, with the route spec's timing rules. It holds no state: the caller
4
+ * keeps the `clientTurnId`, the poll cursor and anything it shows.
5
+ *
6
+ * GET /status 5 s
7
+ * POST /turns {text, clientTurnId} 5 s (resend the same `clientTurnId` after a lost answer)
8
+ * GET /turns/:id/events?after= 35 s (one held poll, about 25 s on the server)
9
+ * POST /turns/:id/cancel 5 s
10
+ * GET /history?before=&limit= 5 s
11
+ *
12
+ * A refusal is read as `body.code ?? body.error?.code`: the Quant handlers answer `{code, ...}` (the
13
+ * relay shape), the key middleware answers `{success: false, error: {code}}` (the envelope shape),
14
+ * and `RATE_LIMITED` comes in both, meaning different limits. `shape` says which one arrived.
15
+ *
16
+ * The key is sent as `x-api-key` and never follows a redirect (`redirect: "error"`), so it cannot be
17
+ * forwarded to another host. Use a Read key (`account:read`): Quant needs nothing more.
18
+ *
19
+ * Dependency-free on purpose. The CLI carries a byte-identical copy (`packages/cli/src/quant.ts`,
20
+ * kept equal by `quant.drift.test.ts`). Copy any change verbatim:
21
+ * `cp packages/sdk/src/quant.ts packages/cli/src/quant.ts`.
22
+ */
23
+ export declare const QUANT_AGENT_PATH = "/api/v1/agent/quant";
24
+ export declare const QUANT_CALL_TIMEOUT_MS = 5000;
25
+ export declare const QUANT_POLL_TIMEOUT_MS = 35000;
26
+ /** hacc's B0 figure, used until a status read says otherwise (the route's `DEFAULT_MAX_TEXT_CHARS`). */
27
+ export declare const QUANT_DEFAULT_MAX_TEXT_CHARS = 4000;
28
+ /** `GET /status`. With the switch off the route answers `{enabled: false}` and nothing else. */
29
+ export interface QuantKeyStatus {
30
+ enabled: boolean;
31
+ allowed?: boolean;
32
+ tier?: string | null;
33
+ marketAccess?: boolean;
34
+ linked?: boolean;
35
+ remaining?: number | null;
36
+ maxTextChars?: number | null;
37
+ activeTurnId?: string | null;
38
+ }
39
+ export interface QuantTurnStarted {
40
+ turnId: string;
41
+ cursor?: number;
42
+ }
43
+ /** One event of a turn: `turn_started`, `working`, `tool_started`, `tool_finished`, `text`, `done`, `error`. */
44
+ export interface QuantTurnEvent {
45
+ seq: number;
46
+ kind: string;
47
+ data: Record<string, unknown>;
48
+ createdAt?: string;
49
+ }
50
+ export interface QuantEventsPage {
51
+ events: QuantTurnEvent[];
52
+ next: number;
53
+ /** `running`, or the terminal state (`done`, `error`, `cancelled`, ...). */
54
+ status: string;
55
+ }
56
+ export interface QuantHistoryItem {
57
+ id: string;
58
+ role: "user" | "assistant";
59
+ text: string;
60
+ createdAt: string;
61
+ }
62
+ export interface QuantHistoryPage {
63
+ items: QuantHistoryItem[];
64
+ next: string | null;
65
+ }
66
+ /**
67
+ * Why a call did not succeed. `shape` is `relay` for `{code}`, `envelope` for `{error: {code}}`, and
68
+ * `transport` when no answer arrived (`NETWORK_ERROR`, `TIMEOUT`) or it could not be read
69
+ * (`BAD_RESPONSE`): a lost answer to `POST /turns` is safe to resend with the same `clientTurnId`.
70
+ */
71
+ export interface QuantRefusal {
72
+ code: string;
73
+ status: number;
74
+ shape: "relay" | "envelope" | "transport";
75
+ retryable?: boolean;
76
+ resetsAt?: string | number;
77
+ turnId?: string;
78
+ limit?: number;
79
+ }
80
+ export type QuantAnswer<T> = {
81
+ ok: true;
82
+ status: number;
83
+ body: T;
84
+ } | {
85
+ ok: false;
86
+ refusal: QuantRefusal;
87
+ };
88
+ export interface QuantKeyClientOptions {
89
+ apiUrl: string;
90
+ apiKey: string;
91
+ fetch?: typeof fetch;
92
+ callTimeoutMs?: number;
93
+ pollTimeoutMs?: number;
94
+ }
95
+ export interface QuantKeyClient {
96
+ status(): Promise<QuantAnswer<QuantKeyStatus>>;
97
+ sendTurn(input: {
98
+ text: string;
99
+ clientTurnId: string;
100
+ }): Promise<QuantAnswer<QuantTurnStarted>>;
101
+ events(turnId: string, after: number, options?: {
102
+ signal?: AbortSignal;
103
+ }): Promise<QuantAnswer<QuantEventsPage>>;
104
+ cancel(turnId: string): Promise<QuantAnswer<Record<string, unknown>>>;
105
+ history(input?: {
106
+ before?: string;
107
+ limit?: number;
108
+ }): Promise<QuantAnswer<QuantHistoryPage>>;
109
+ }
110
+ /** Unicode code points, the unit hacc counts `maxTextChars` in. */
111
+ export declare function quantCodePoints(text: string): number;
112
+ /** A `clientTurnId`: a uuid v4, kept with the draft until a `turnId` arrives. */
113
+ export declare function newClientTurnId(): string;
114
+ /** The refusal code of a body in either shape, or undefined when it names none. */
115
+ export declare function quantRefusalCode(body: unknown): string | undefined;
116
+ /** The key client. Refuses an `http://` API URL other than this machine's: the key would travel in clear. */
117
+ export declare function createQuantKeyClient(options: QuantKeyClientOptions): QuantKeyClient;
118
+ //# sourceMappingURL=quant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quant.d.ts","sourceRoot":"","sources":["../src/quant.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,eAAO,MAAM,gBAAgB,wBAAwB,CAAA;AACrD,eAAO,MAAM,qBAAqB,OAAQ,CAAA;AAC1C,eAAO,MAAM,qBAAqB,QAAS,CAAA;AAC3C,wGAAwG;AACxG,eAAO,MAAM,4BAA4B,OAAQ,CAAA;AAEjD,gGAAgG;AAChG,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,gHAAgH;AAChH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,gBAAgB,EAAE,CAAA;IACzB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,OAAO,GAAG,UAAU,GAAG,WAAW,CAAA;IACzC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,CAAA;AAEzG,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC,CAAA;IAC9C,QAAQ,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,CAAA;IAC/F,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,CAAA;IAChH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACrE,OAAO,CAAC,KAAK,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,CAAA;CAC7F;AAED,mEAAmE;AACnE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIpD;AAED,iFAAiF;AACjF,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAKlE;AA2ED,6GAA6G;AAC7G,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CAoFnF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@candledottv/agent-sdk",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "TypeScript SDK for the Candle agent rail: typed headless launches, market reads, and webhook verification",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",