@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/README.md +2 -1
- package/dist/client.d.ts +316 -31
- package/dist/client.d.ts.map +1 -1
- package/dist/hyperliquid-markets.d.ts +48 -0
- package/dist/hyperliquid-markets.d.ts.map +1 -1
- package/dist/hyperliquid-withdraw.d.ts +11 -19
- package/dist/hyperliquid-withdraw.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +331 -32
- package/dist/quant.d.ts +118 -0
- package/dist/quant.d.ts.map +1 -0
- package/package.json +1 -1
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 :
|
|
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
|
|
3188
|
-
return refuse("the
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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,
|
package/dist/quant.d.ts
ADDED
|
@@ -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":"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"}
|
package/package.json
CHANGED