@okx_ai/okx-trade-cli 1.3.1-beta.2 → 1.3.1-beta.4

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
@@ -5,24 +5,39 @@ import { createRequire as createRequire3 } from "module";
5
5
 
6
6
  // ../core/dist/index.js
7
7
  import { ProxyAgent } from "undici";
8
+ import { isIP } from "net";
9
+ import { lookup as dnsLookup } from "dns";
10
+ import { Agent } from "undici";
11
+ import { execFile } from "child_process";
12
+ import { homedir } from "os";
13
+ import { join } from "path";
14
+ import {
15
+ readFileSync,
16
+ writeFileSync,
17
+ mkdirSync,
18
+ unlinkSync,
19
+ renameSync
20
+ } from "fs";
21
+ import { homedir as homedir2 } from "os";
22
+ import { join as join2, dirname } from "path";
8
23
  import { createHmac } from "crypto";
9
24
  import fs from "fs";
10
25
  import path from "path";
11
26
  import os from "os";
12
- import { writeFileSync, renameSync, unlinkSync, mkdirSync } from "fs";
13
- import { join, resolve, basename, sep } from "path";
27
+ import { writeFileSync as writeFileSync2, renameSync as renameSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2 } from "fs";
28
+ import { join as join3, resolve, basename, sep } from "path";
14
29
  import { randomUUID } from "crypto";
15
30
  import yauzl from "yauzl";
16
- import { createWriteStream, mkdirSync as mkdirSync2 } from "fs";
17
- import { resolve as resolve2, dirname } from "path";
18
- import { readFileSync, existsSync } from "fs";
19
- import { join as join2 } from "path";
20
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync2 } from "fs";
21
- import { join as join3, dirname as dirname2 } from "path";
22
- import { homedir } from "os";
23
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync3 } from "fs";
24
- import { join as join4, dirname as dirname3 } from "path";
25
- import { homedir as homedir2 } from "os";
31
+ import { createWriteStream, mkdirSync as mkdirSync3 } from "fs";
32
+ import { resolve as resolve2, dirname as dirname2 } from "path";
33
+ import { readFileSync as readFileSync2, existsSync } from "fs";
34
+ import { join as join4 } from "path";
35
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync2 } from "fs";
36
+ import { join as join5, dirname as dirname3 } from "path";
37
+ import { homedir as homedir3 } from "os";
38
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync3 } from "fs";
39
+ import { join as join6, dirname as dirname4 } from "path";
40
+ import { homedir as homedir4 } from "os";
26
41
 
27
42
  // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
28
43
  function getLineColFromPtr(string, ptr) {
@@ -851,9 +866,9 @@ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
851
866
  }
852
867
 
853
868
  // ../core/dist/index.js
854
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync4 } from "fs";
855
- import { join as join5 } from "path";
856
- import { homedir as homedir3 } from "os";
869
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync4 } from "fs";
870
+ import { join as join7 } from "path";
871
+ import { homedir as homedir5 } from "os";
857
872
  import fs2 from "fs";
858
873
  import path2 from "path";
859
874
  import os2 from "os";
@@ -861,6 +876,290 @@ import * as fs3 from "fs";
861
876
  import * as path3 from "path";
862
877
  import * as os3 from "os";
863
878
  import { execFileSync } from "child_process";
879
+ var EXEC_TIMEOUT_MS = 3e4;
880
+ var ALLOWED_DOMAIN_RE = /^[\w.-]+\.okx\.com$/;
881
+ var DOH_BIN_DIR = join(homedir(), ".okx", "bin");
882
+ function getDohBinaryPath() {
883
+ if (process.env.OKX_DOH_BINARY_PATH) {
884
+ return process.env.OKX_DOH_BINARY_PATH;
885
+ }
886
+ const ext = process.platform === "win32" ? ".exe" : "";
887
+ return join(DOH_BIN_DIR, `okx-doh-resolver${ext}`);
888
+ }
889
+ function execDohBinary(domain, exclude = [], userAgent) {
890
+ if (!ALLOWED_DOMAIN_RE.test(domain)) {
891
+ return Promise.resolve(null);
892
+ }
893
+ const binPath = getDohBinaryPath();
894
+ const args = ["--domain", domain];
895
+ if (exclude.length > 0) {
896
+ args.push("--exclude", exclude.join(","));
897
+ }
898
+ if (userAgent) {
899
+ args.push("--user-agent", userAgent);
900
+ }
901
+ return new Promise((resolve3) => {
902
+ execFile(
903
+ binPath,
904
+ args,
905
+ { timeout: EXEC_TIMEOUT_MS, encoding: "utf-8" },
906
+ (error, stdout) => {
907
+ if (error) {
908
+ resolve3(null);
909
+ return;
910
+ }
911
+ try {
912
+ const result = JSON.parse(stdout);
913
+ if (result.code === 0 && result.data) {
914
+ resolve3(result.data);
915
+ } else {
916
+ resolve3(null);
917
+ }
918
+ } catch {
919
+ resolve3(null);
920
+ }
921
+ }
922
+ );
923
+ });
924
+ }
925
+ function getDefaultCachePath() {
926
+ return process.env.OKX_DOH_CACHE_PATH || join2(homedir2(), ".okx", "doh-cache.json");
927
+ }
928
+ function readCache(hostname, cachePath = getDefaultCachePath()) {
929
+ try {
930
+ const raw = readFileSync(cachePath, "utf-8");
931
+ const file = JSON.parse(raw);
932
+ return file[hostname] ?? null;
933
+ } catch {
934
+ return null;
935
+ }
936
+ }
937
+ function writeCache(hostname, entry, cachePath = getDefaultCachePath()) {
938
+ try {
939
+ const dir = dirname(cachePath);
940
+ mkdirSync(dir, { recursive: true });
941
+ let file = {};
942
+ try {
943
+ file = JSON.parse(readFileSync(cachePath, "utf-8"));
944
+ } catch {
945
+ }
946
+ file[hostname] = entry;
947
+ const tmpPath = `${cachePath}.tmp`;
948
+ writeFileSync(tmpPath, JSON.stringify(file));
949
+ renameSync(tmpPath, cachePath);
950
+ } catch {
951
+ }
952
+ }
953
+ var FAILED_NODE_TTL_MS = 60 * 60 * 1e3;
954
+ function classifyAndCache(node, hostname, failedNodes, cachePath) {
955
+ if (!node) {
956
+ return { mode: null, node: null };
957
+ }
958
+ if (node.ip === hostname || node.host === hostname) {
959
+ writeCache(hostname, {
960
+ mode: "direct",
961
+ node: null,
962
+ failedNodes,
963
+ updatedAt: Date.now()
964
+ }, cachePath);
965
+ return { mode: "direct", node: null };
966
+ }
967
+ writeCache(hostname, {
968
+ mode: "proxy",
969
+ node,
970
+ failedNodes,
971
+ updatedAt: Date.now()
972
+ }, cachePath);
973
+ return { mode: "proxy", node };
974
+ }
975
+ function getActiveFailedNodes(nodes) {
976
+ if (!nodes || nodes.length === 0) return [];
977
+ const now = Date.now();
978
+ return nodes.filter((n) => now - n.failedAt < FAILED_NODE_TTL_MS);
979
+ }
980
+ function resolveDoh(hostname, cachePath) {
981
+ const entry = readCache(hostname, cachePath);
982
+ if (entry) {
983
+ if (entry.mode === "direct") {
984
+ return { mode: "direct", node: null };
985
+ }
986
+ if (entry.mode === "proxy" && entry.node) {
987
+ return { mode: "proxy", node: entry.node };
988
+ }
989
+ }
990
+ return { mode: null, node: null };
991
+ }
992
+ async function reResolveDoh(hostname, failedIp, userAgent, cachePath) {
993
+ const entry = readCache(hostname, cachePath);
994
+ const active = getActiveFailedNodes(entry?.failedNodes);
995
+ const now = Date.now();
996
+ const alreadyFailed = failedIp && active.some((n) => n.ip === failedIp);
997
+ const failedNodes = failedIp && !alreadyFailed ? [...active, { ip: failedIp, failedAt: now }] : active;
998
+ const excludeIps = failedNodes.map((n) => n.ip);
999
+ const node = await execDohBinary(hostname, excludeIps, userAgent);
1000
+ return classifyAndCache(node, hostname, failedNodes, cachePath);
1001
+ }
1002
+ function vlog(message) {
1003
+ process.stderr.write(`[verbose] ${message}
1004
+ `);
1005
+ }
1006
+ var DohManager = class {
1007
+ opts;
1008
+ // DoH proxy state (lazy-resolved on first request)
1009
+ dohResolved = false;
1010
+ dohRetried = false;
1011
+ directUnverified = false;
1012
+ // The first direct connection has not yet been verified
1013
+ dohNode = null;
1014
+ dohAgent = null;
1015
+ dohBaseUrl = null;
1016
+ constructor(opts) {
1017
+ this.opts = opts;
1018
+ }
1019
+ /**
1020
+ * Lazily resolve the DoH proxy node on the first request.
1021
+ * Uses cache-first strategy via the resolver.
1022
+ */
1023
+ prepareDoh() {
1024
+ if (this.dohResolved || this.opts.hasCustomProxy) return;
1025
+ this.dohResolved = true;
1026
+ try {
1027
+ const { hostname, protocol } = new URL(this.opts.baseUrl);
1028
+ const result = resolveDoh(hostname);
1029
+ if (!result.mode) {
1030
+ this.directUnverified = true;
1031
+ if (this.opts.verbose) {
1032
+ vlog("DoH: no cache, trying direct connection first");
1033
+ }
1034
+ return;
1035
+ }
1036
+ if (result.mode === "direct") {
1037
+ if (this.opts.verbose) {
1038
+ vlog("DoH: mode=direct (overseas or cached), using direct connection");
1039
+ }
1040
+ return;
1041
+ }
1042
+ if (result.node) {
1043
+ this.applyNode(result.node, protocol);
1044
+ }
1045
+ } catch (err) {
1046
+ if (this.opts.verbose) {
1047
+ const cause = err instanceof Error ? err.message : String(err);
1048
+ vlog(`DoH resolution failed, falling back to direct: ${cause}`);
1049
+ }
1050
+ }
1051
+ }
1052
+ /** Get connection parameters for the current request. */
1053
+ getConnectionParams() {
1054
+ const baseUrl = this.dohNode ? this.dohBaseUrl : this.opts.baseUrl;
1055
+ const result = { baseUrl };
1056
+ if (this.dohAgent) {
1057
+ result.dispatcher = this.dohAgent;
1058
+ }
1059
+ if (this.dohNode) {
1060
+ result.userAgent = this.dohUserAgent;
1061
+ }
1062
+ return result;
1063
+ }
1064
+ /** Whether a DoH proxy node is currently active. */
1065
+ get isProxyActive() {
1066
+ return this.dohNode !== null;
1067
+ }
1068
+ /** Whether we have already retried after network failure. */
1069
+ get hasRetried() {
1070
+ return this.dohRetried;
1071
+ }
1072
+ /**
1073
+ * Handle network failure: re-resolve with --exclude and retry once.
1074
+ * Returns true if retry should proceed, false if already retried.
1075
+ */
1076
+ async handleNetworkFailure() {
1077
+ if (this.dohRetried) return false;
1078
+ this.dohRetried = true;
1079
+ const failedIp = this.dohNode?.ip ?? "";
1080
+ const { hostname, protocol } = new URL(this.opts.baseUrl);
1081
+ this.dohNode = null;
1082
+ this.dohAgent = null;
1083
+ this.dohBaseUrl = null;
1084
+ if (!failedIp) this.directUnverified = false;
1085
+ if (this.opts.verbose) {
1086
+ vlog(failedIp ? `DoH: proxy node ${failedIp} failed, re-resolving with --exclude` : "DoH: direct connection failed, calling binary for DoH resolution");
1087
+ }
1088
+ try {
1089
+ const result = await reResolveDoh(hostname, failedIp, this.dohUserAgent);
1090
+ if (result.mode === "proxy" && result.node) {
1091
+ this.applyNode(result.node, protocol);
1092
+ this.dohRetried = false;
1093
+ return true;
1094
+ }
1095
+ } catch {
1096
+ }
1097
+ if (this.opts.verbose) {
1098
+ vlog("DoH: re-resolution failed or switched to direct, retrying with direct connection");
1099
+ }
1100
+ return true;
1101
+ }
1102
+ /**
1103
+ * After a successful HTTP response on direct connection, cache mode=direct.
1104
+ * (Even if the business response is an error, the network path is valid.)
1105
+ */
1106
+ cacheDirectIfNeeded() {
1107
+ if (!this.directUnverified || this.dohNode) return;
1108
+ this.directUnverified = false;
1109
+ const { hostname } = new URL(this.opts.baseUrl);
1110
+ writeCache(hostname, {
1111
+ mode: "direct",
1112
+ node: null,
1113
+ failedNodes: [],
1114
+ updatedAt: Date.now()
1115
+ });
1116
+ if (this.opts.verbose) {
1117
+ vlog("DoH: direct connection succeeded, cached mode=direct");
1118
+ }
1119
+ }
1120
+ /** User-Agent for DoH proxy requests: OKX/@okx_ai/{packageName}/{version} */
1121
+ get dohUserAgent() {
1122
+ return `OKX/@okx_ai/${this.opts.packageUserAgent ?? "unknown"}`;
1123
+ }
1124
+ /**
1125
+ * Apply a DoH node: set up the custom Agent + base URL.
1126
+ *
1127
+ * node.ip may be a real IP or a domain (CNAME like *.aliyunddos1021.com).
1128
+ * - Real IP → use directly in lookup callback
1129
+ * - Domain → dns.lookup on every connection to get a fresh IP
1130
+ */
1131
+ applyNode(node, protocol) {
1132
+ this.dohNode = node;
1133
+ this.dohBaseUrl = `${protocol}//${node.host}`;
1134
+ const nodeIpIsRealIp = !!isIP(node.ip);
1135
+ this.dohAgent = new Agent({
1136
+ connect: {
1137
+ lookup: (_hostname, options, callback) => {
1138
+ if (nodeIpIsRealIp) {
1139
+ if (options?.all) {
1140
+ callback(null, [{ address: node.ip, family: 4 }]);
1141
+ } else {
1142
+ callback(null, node.ip, 4);
1143
+ }
1144
+ } else {
1145
+ dnsLookup(node.ip, { family: 4 }, (err, address, family) => {
1146
+ if (err) {
1147
+ callback(err, "", 0);
1148
+ } else if (options?.all) {
1149
+ callback(null, [{ address, family }]);
1150
+ } else {
1151
+ callback(null, address, family);
1152
+ }
1153
+ });
1154
+ }
1155
+ }
1156
+ }
1157
+ });
1158
+ if (this.opts.verbose) {
1159
+ vlog(`DoH proxy active: \u2192 ${node.host} (${node.ip}), ttl=${node.ttl}s`);
1160
+ }
1161
+ }
1162
+ };
864
1163
  function getNow() {
865
1164
  return (/* @__PURE__ */ new Date()).toISOString();
866
1165
  }
@@ -1071,7 +1370,7 @@ function maskKey(key) {
1071
1370
  if (key.length <= 8) return "***";
1072
1371
  return `${key.slice(0, 3)}***${key.slice(-3)}`;
1073
1372
  }
1074
- function vlog(message) {
1373
+ function vlog2(message) {
1075
1374
  process.stderr.write(`[verbose] ${message}
1076
1375
  `);
1077
1376
  }
@@ -1079,25 +1378,32 @@ var OkxRestClient = class _OkxRestClient {
1079
1378
  config;
1080
1379
  rateLimiter;
1081
1380
  dispatcher;
1381
+ doh;
1082
1382
  constructor(config) {
1083
1383
  this.config = config;
1084
1384
  this.rateLimiter = new RateLimiter(3e4, config.verbose);
1085
1385
  if (config.proxyUrl) {
1086
1386
  this.dispatcher = new ProxyAgent(config.proxyUrl);
1087
1387
  }
1388
+ this.doh = new DohManager({
1389
+ baseUrl: config.baseUrl,
1390
+ packageUserAgent: config.userAgent,
1391
+ verbose: config.verbose,
1392
+ hasCustomProxy: !!config.proxyUrl
1393
+ });
1088
1394
  }
1089
1395
  logRequest(method, url, auth) {
1090
1396
  if (!this.config.verbose) return;
1091
- vlog(`\u2192 ${method} ${url}`);
1397
+ vlog2(`\u2192 ${method} ${url}`);
1092
1398
  const authInfo = auth === "private" && this.config.apiKey ? `auth=\u2713(${maskKey(this.config.apiKey)})` : `auth=${auth}`;
1093
- vlog(` ${authInfo} demo=${this.config.demo} timeout=${this.config.timeoutMs}ms`);
1399
+ vlog2(` ${authInfo} demo=${this.config.demo} timeout=${this.config.timeoutMs}ms`);
1094
1400
  }
1095
1401
  logResponse(status, rawLen, elapsed, traceId, code, msg) {
1096
1402
  if (!this.config.verbose) return;
1097
1403
  if (code && code !== "0" && code !== "1") {
1098
- vlog(`\u2717 ${status} | code=${code} | msg=${msg ?? "-"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1404
+ vlog2(`\u2717 ${status} | code=${code} | msg=${msg ?? "-"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1099
1405
  } else {
1100
- vlog(`\u2190 ${status} | code=${code ?? "0"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1406
+ vlog2(`\u2190 ${status} | code=${code ?? "0"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1101
1407
  }
1102
1408
  }
1103
1409
  async publicGet(path42, query, rateLimit, simulatedTrading) {
@@ -1256,17 +1562,30 @@ var OkxRestClient = class _OkxRestClient {
1256
1562
  * Security: validates Content-Type and enforces maxBytes limit.
1257
1563
  */
1258
1564
  async privatePostBinary(path42, body, opts) {
1565
+ this.doh.prepareDoh();
1259
1566
  const maxBytes = opts?.maxBytes ?? _OkxRestClient.DEFAULT_MAX_BYTES;
1260
1567
  const expectedCT = opts?.expectedContentType ?? "application/octet-stream";
1261
1568
  const bodyJson = body ? JSON.stringify(body) : "";
1262
1569
  const endpoint = `POST ${path42}`;
1263
- this.logRequest("POST", `${this.config.baseUrl}${path42}`, "private");
1570
+ const conn = this.doh.getConnectionParams();
1571
+ this.logRequest("POST", `${conn.baseUrl}${path42}`, "private");
1264
1572
  const reqConfig = { method: "POST", path: path42, auth: "private" };
1265
1573
  const headers = this.buildHeaders(reqConfig, path42, bodyJson, getNow());
1574
+ if (conn.userAgent) {
1575
+ headers.set("User-Agent", conn.userAgent);
1576
+ }
1266
1577
  const t0 = Date.now();
1267
- const response = await this.fetchBinary(path42, endpoint, headers, bodyJson, t0);
1578
+ let response;
1579
+ try {
1580
+ response = await this.fetchBinary(path42, endpoint, headers, bodyJson, t0);
1581
+ } catch (error) {
1582
+ this.doh.handleNetworkFailure().catch(() => {
1583
+ });
1584
+ throw error;
1585
+ }
1268
1586
  const elapsed = Date.now() - t0;
1269
1587
  const traceId = extractTraceId(response.headers);
1588
+ this.doh.cacheDirectIfNeeded();
1270
1589
  if (!response.ok) {
1271
1590
  const text = await response.text();
1272
1591
  this.logResponse(response.status, text.length, elapsed, traceId, String(response.status));
@@ -1285,24 +1604,25 @@ var OkxRestClient = class _OkxRestClient {
1285
1604
  throw new OkxApiError(`Response size ${buffer.length} bytes exceeds limit of ${maxBytes} bytes.`, { code: "RESPONSE_TOO_LARGE", endpoint, traceId });
1286
1605
  }
1287
1606
  if (this.config.verbose) {
1288
- vlog(`\u2190 ${response.status} | binary ${buffer.length}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1607
+ vlog2(`\u2190 ${response.status} | binary ${buffer.length}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1289
1608
  }
1290
1609
  return { endpoint, requestTime: (/* @__PURE__ */ new Date()).toISOString(), data: buffer, contentType: ct, contentLength: buffer.length, traceId };
1291
1610
  }
1292
1611
  /** Execute fetch for binary endpoint, wrapping network errors. */
1293
1612
  async fetchBinary(path42, endpoint, headers, bodyJson, t0) {
1613
+ const conn = this.doh.getConnectionParams();
1294
1614
  try {
1295
1615
  const fetchOptions = {
1296
1616
  method: "POST",
1297
1617
  headers,
1298
1618
  body: bodyJson || void 0,
1299
- signal: AbortSignal.timeout(this.config.timeoutMs)
1619
+ signal: AbortSignal.timeout(this.config.timeoutMs),
1620
+ dispatcher: this.dispatcher ?? conn.dispatcher
1300
1621
  };
1301
- if (this.dispatcher) fetchOptions.dispatcher = this.dispatcher;
1302
- return await fetch(`${this.config.baseUrl}${path42}`, fetchOptions);
1622
+ return await fetch(`${conn.baseUrl}${path42}`, fetchOptions);
1303
1623
  } catch (error) {
1304
1624
  if (this.config.verbose) {
1305
- vlog(`\u2717 NetworkError after ${Date.now() - t0}ms: ${error instanceof Error ? error.message : String(error)}`);
1625
+ vlog2(`\u2717 NetworkError after ${Date.now() - t0}ms: ${error instanceof Error ? error.message : String(error)}`);
1306
1626
  }
1307
1627
  throw new NetworkError(`Failed to call OKX endpoint ${endpoint}.`, endpoint, error);
1308
1628
  }
@@ -1330,10 +1650,38 @@ var OkxRestClient = class _OkxRestClient {
1330
1650
  // ---------------------------------------------------------------------------
1331
1651
  // JSON request
1332
1652
  // ---------------------------------------------------------------------------
1653
+ /**
1654
+ * Handle network error during a JSON request: refresh DoH and maybe retry.
1655
+ * Always either returns a retry result or throws NetworkError.
1656
+ */
1657
+ async handleRequestNetworkError(error, reqConfig, requestPath, t0) {
1658
+ if (!this.doh.hasRetried) {
1659
+ if (this.config.verbose) {
1660
+ const cause = error instanceof Error ? error.message : String(error);
1661
+ vlog2(`Network failure, refreshing DoH: ${cause}`);
1662
+ }
1663
+ const shouldRetry = await this.doh.handleNetworkFailure();
1664
+ if (shouldRetry && reqConfig.method === "GET") {
1665
+ return this.request(reqConfig);
1666
+ }
1667
+ }
1668
+ if (this.config.verbose) {
1669
+ const elapsed = Date.now() - t0;
1670
+ const cause = error instanceof Error ? error.message : String(error);
1671
+ vlog2(`\u2717 NetworkError after ${elapsed}ms: ${cause}`);
1672
+ }
1673
+ throw new NetworkError(
1674
+ `Failed to call OKX endpoint ${reqConfig.method} ${requestPath}.`,
1675
+ `${reqConfig.method} ${requestPath}`,
1676
+ error
1677
+ );
1678
+ }
1333
1679
  async request(reqConfig) {
1680
+ this.doh.prepareDoh();
1334
1681
  const queryString = buildQueryString(reqConfig.query);
1335
1682
  const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
1336
- const url = `${this.config.baseUrl}${requestPath}`;
1683
+ const conn = this.doh.getConnectionParams();
1684
+ const url = `${conn.baseUrl}${requestPath}`;
1337
1685
  const bodyJson = reqConfig.body ? JSON.stringify(reqConfig.body) : "";
1338
1686
  const timestamp = getNow();
1339
1687
  this.logRequest(reqConfig.method, url, reqConfig.auth);
@@ -1341,6 +1689,9 @@ var OkxRestClient = class _OkxRestClient {
1341
1689
  await this.rateLimiter.consume(reqConfig.rateLimit);
1342
1690
  }
1343
1691
  const headers = this.buildHeaders(reqConfig, requestPath, bodyJson, timestamp);
1692
+ if (conn.userAgent) {
1693
+ headers.set("User-Agent", conn.userAgent);
1694
+ }
1344
1695
  const t0 = Date.now();
1345
1696
  let response;
1346
1697
  try {
@@ -1348,27 +1699,17 @@ var OkxRestClient = class _OkxRestClient {
1348
1699
  method: reqConfig.method,
1349
1700
  headers,
1350
1701
  body: reqConfig.method === "POST" ? bodyJson : void 0,
1351
- signal: AbortSignal.timeout(this.config.timeoutMs)
1702
+ signal: AbortSignal.timeout(this.config.timeoutMs),
1703
+ dispatcher: this.dispatcher ?? conn.dispatcher
1352
1704
  };
1353
- if (this.dispatcher) {
1354
- fetchOptions.dispatcher = this.dispatcher;
1355
- }
1356
1705
  response = await fetch(url, fetchOptions);
1357
1706
  } catch (error) {
1358
- if (this.config.verbose) {
1359
- const elapsed2 = Date.now() - t0;
1360
- const cause = error instanceof Error ? error.message : String(error);
1361
- vlog(`\u2717 NetworkError after ${elapsed2}ms: ${cause}`);
1362
- }
1363
- throw new NetworkError(
1364
- `Failed to call OKX endpoint ${reqConfig.method} ${requestPath}.`,
1365
- `${reqConfig.method} ${requestPath}`,
1366
- error
1367
- );
1707
+ return await this.handleRequestNetworkError(error, reqConfig, requestPath, t0);
1368
1708
  }
1369
1709
  const rawText = await response.text();
1370
1710
  const elapsed = Date.now() - t0;
1371
1711
  const traceId = extractTraceId(response.headers);
1712
+ this.doh.cacheDirectIfNeeded();
1372
1713
  return this.processResponse(rawText, response, elapsed, traceId, reqConfig, requestPath);
1373
1714
  }
1374
1715
  };
@@ -1393,9 +1734,6 @@ function readNumber(args, key) {
1393
1734
  if (value === void 0 || value === null) {
1394
1735
  return void 0;
1395
1736
  }
1396
- if (typeof value === "string" && /^-?\d+(\.\d+)?$/.test(value)) {
1397
- return parseFloat(value);
1398
- }
1399
1737
  if (typeof value !== "number" || Number.isNaN(value)) {
1400
1738
  throw new ValidationError(`Parameter "${key}" must be a number.`);
1401
1739
  }
@@ -1475,8 +1813,7 @@ var OKX_INST_TYPES = [
1475
1813
  "SWAP",
1476
1814
  "FUTURES",
1477
1815
  "OPTION",
1478
- "MARGIN",
1479
- "EVENTS"
1816
+ "MARGIN"
1480
1817
  ];
1481
1818
  function publicRateLimit(key, rps = 20) {
1482
1819
  return {
@@ -1774,7 +2111,6 @@ var MODULES = [
1774
2111
  "futures",
1775
2112
  "option",
1776
2113
  "account",
1777
- "event",
1778
2114
  ...EARN_SUB_MODULE_IDS,
1779
2115
  ...BOT_SUB_MODULE_IDS,
1780
2116
  "skills"
@@ -1968,7 +2304,7 @@ function registerAccountTools() {
1968
2304
  properties: {
1969
2305
  instType: {
1970
2306
  type: "string",
1971
- enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION", "EVENTS"]
2307
+ enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION"]
1972
2308
  },
1973
2309
  ccy: {
1974
2310
  type: "string",
@@ -2034,7 +2370,7 @@ function registerAccountTools() {
2034
2370
  properties: {
2035
2371
  instType: {
2036
2372
  type: "string",
2037
- enum: ["SWAP", "FUTURES", "MARGIN", "OPTION", "EVENTS"],
2373
+ enum: ["SWAP", "FUTURES", "MARGIN", "OPTION"],
2038
2374
  description: "Default SWAP"
2039
2375
  },
2040
2376
  instId: {
@@ -2095,7 +2431,7 @@ function registerAccountTools() {
2095
2431
  properties: {
2096
2432
  instType: {
2097
2433
  type: "string",
2098
- enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION", "EVENTS"]
2434
+ enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION"]
2099
2435
  },
2100
2436
  instId: {
2101
2437
  type: "string",
@@ -2206,14 +2542,14 @@ function registerAccountTools() {
2206
2542
  {
2207
2543
  name: "account_get_positions",
2208
2544
  module: "account",
2209
- description: "Get current open positions across all instrument types (MARGIN, SWAP, FUTURES, OPTION, EVENTS). Use swap_get_positions for SWAP/FUTURES-only queries.",
2545
+ description: "Get current open positions across all instrument types (MARGIN, SWAP, FUTURES, OPTION). Use swap_get_positions for SWAP/FUTURES-only queries.",
2210
2546
  isWrite: false,
2211
2547
  inputSchema: {
2212
2548
  type: "object",
2213
2549
  properties: {
2214
2550
  instType: {
2215
2551
  type: "string",
2216
- enum: ["MARGIN", "SWAP", "FUTURES", "OPTION", "EVENTS"]
2552
+ enum: ["MARGIN", "SWAP", "FUTURES", "OPTION"]
2217
2553
  },
2218
2554
  instId: {
2219
2555
  type: "string",
@@ -3252,19 +3588,19 @@ function safeWriteFile(targetDir, fileName, data) {
3252
3588
  throw new Error(`Invalid file name: "${fileName}"`);
3253
3589
  }
3254
3590
  const resolvedDir = resolve(targetDir);
3255
- const filePath = join(resolvedDir, safeName);
3591
+ const filePath = join3(resolvedDir, safeName);
3256
3592
  const resolvedPath = resolve(filePath);
3257
3593
  if (!resolvedPath.startsWith(resolvedDir + sep)) {
3258
3594
  throw new Error(`Path traversal detected: "${fileName}" resolves outside target directory`);
3259
3595
  }
3260
- mkdirSync(resolvedDir, { recursive: true });
3596
+ mkdirSync2(resolvedDir, { recursive: true });
3261
3597
  const tmpPath = `${resolvedPath}.${randomUUID()}.tmp`;
3262
3598
  try {
3263
- writeFileSync(tmpPath, data);
3264
- renameSync(tmpPath, resolvedPath);
3599
+ writeFileSync2(tmpPath, data);
3600
+ renameSync2(tmpPath, resolvedPath);
3265
3601
  } catch (err) {
3266
3602
  try {
3267
- unlinkSync(tmpPath);
3603
+ unlinkSync2(tmpPath);
3268
3604
  } catch {
3269
3605
  }
3270
3606
  throw err;
@@ -3330,7 +3666,7 @@ async function extractSkillZip(zipPath, targetDir, limits) {
3330
3666
  const maxFiles = limits?.maxFiles ?? DEFAULT_MAX_FILES;
3331
3667
  const maxCompressionRatio = limits?.maxCompressionRatio ?? DEFAULT_MAX_COMPRESSION_RATIO;
3332
3668
  const resolvedTarget = resolve2(targetDir);
3333
- mkdirSync2(resolvedTarget, { recursive: true });
3669
+ mkdirSync3(resolvedTarget, { recursive: true });
3334
3670
  return new Promise((resolvePromise, reject) => {
3335
3671
  yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
3336
3672
  if (err) return reject(err);
@@ -3353,7 +3689,7 @@ async function extractSkillZip(zipPath, targetDir, limits) {
3353
3689
  zipfile.close();
3354
3690
  return reject(streamErr);
3355
3691
  }
3356
- mkdirSync2(dirname(resolvedPath), { recursive: true });
3692
+ mkdirSync3(dirname2(resolvedPath), { recursive: true });
3357
3693
  const writeStream = createWriteStream(resolvedPath);
3358
3694
  readStream.pipe(writeStream);
3359
3695
  writeStream.on("close", () => zipfile.readEntry());
@@ -3369,11 +3705,11 @@ async function extractSkillZip(zipPath, targetDir, limits) {
3369
3705
  });
3370
3706
  }
3371
3707
  function readMetaJson(contentDir) {
3372
- const metaPath = join2(contentDir, "_meta.json");
3708
+ const metaPath = join4(contentDir, "_meta.json");
3373
3709
  if (!existsSync(metaPath)) {
3374
3710
  throw new Error(`_meta.json not found in ${contentDir}. Invalid skill package.`);
3375
3711
  }
3376
- const raw = readFileSync(metaPath, "utf-8");
3712
+ const raw = readFileSync2(metaPath, "utf-8");
3377
3713
  let parsed;
3378
3714
  try {
3379
3715
  parsed = JSON.parse(raw);
@@ -3395,26 +3731,26 @@ function readMetaJson(contentDir) {
3395
3731
  };
3396
3732
  }
3397
3733
  function validateSkillMdExists(contentDir) {
3398
- const skillMdPath = join2(contentDir, "SKILL.md");
3734
+ const skillMdPath = join4(contentDir, "SKILL.md");
3399
3735
  if (!existsSync(skillMdPath)) {
3400
3736
  throw new Error(`SKILL.md not found in ${contentDir}. Invalid skill package.`);
3401
3737
  }
3402
3738
  }
3403
- var DEFAULT_REGISTRY_PATH = join3(homedir(), ".okx", "skills", "registry.json");
3739
+ var DEFAULT_REGISTRY_PATH = join5(homedir3(), ".okx", "skills", "registry.json");
3404
3740
  function readRegistry(registryPath = DEFAULT_REGISTRY_PATH) {
3405
3741
  if (!existsSync2(registryPath)) {
3406
3742
  return { version: 1, skills: {} };
3407
3743
  }
3408
3744
  try {
3409
- const raw = readFileSync2(registryPath, "utf-8");
3745
+ const raw = readFileSync3(registryPath, "utf-8");
3410
3746
  return JSON.parse(raw);
3411
3747
  } catch {
3412
3748
  return { version: 1, skills: {} };
3413
3749
  }
3414
3750
  }
3415
3751
  function writeRegistry(registry, registryPath = DEFAULT_REGISTRY_PATH) {
3416
- mkdirSync3(dirname2(registryPath), { recursive: true });
3417
- writeFileSync2(registryPath, JSON.stringify(registry, null, 2) + "\n", "utf-8");
3752
+ mkdirSync4(dirname3(registryPath), { recursive: true });
3753
+ writeFileSync3(registryPath, JSON.stringify(registry, null, 2) + "\n", "utf-8");
3418
3754
  }
3419
3755
  function upsertSkillRecord(meta, registryPath = DEFAULT_REGISTRY_PATH) {
3420
3756
  const registry = readRegistry(registryPath);
@@ -5049,838 +5385,79 @@ function registerDcdTools() {
5049
5385
  termRate: newQuote["termRate"]
5050
5386
  }
5051
5387
  };
5052
- }
5053
- throw error;
5054
- }
5055
- });
5056
- }
5057
- }
5058
- ];
5059
- }
5060
- function registerAutoEarnTools() {
5061
- return [
5062
- {
5063
- name: "earn_auto_set",
5064
- module: "earn.autoearn",
5065
- description: "Enable or disable auto-earn for a currency. earnType='0' for auto-lend+stake (most currencies); earnType='1' for USDG earn (USDG, BUIDL). Use account_get_balance first: if autoLendStatus or autoStakingStatus != 'unsupported', use earnType='0'; for USDG/BUIDL use earnType='1'. [CAUTION] Cannot disable within 24h of enabling.",
5066
- isWrite: true,
5067
- inputSchema: {
5068
- type: "object",
5069
- properties: {
5070
- ccy: {
5071
- type: "string",
5072
- description: "Currency, e.g. SOL, USDG"
5073
- },
5074
- action: {
5075
- type: "string",
5076
- description: "turn_on or turn_off"
5077
- },
5078
- earnType: {
5079
- type: "string",
5080
- description: "0=auto-lend+stake (default), 1=USDG earn. Omit to use default."
5081
- }
5082
- },
5083
- required: ["ccy", "action"]
5084
- },
5085
- handler: async (rawArgs, context) => {
5086
- const args = asRecord(rawArgs);
5087
- const response = await context.client.privatePost(
5088
- "/api/v5/account/set-auto-earn",
5089
- compactObject({
5090
- ccy: requireString(args, "ccy"),
5091
- action: requireString(args, "action"),
5092
- earnType: readString(args, "earnType") ?? "0"
5093
- }),
5094
- privateRateLimit("earn_auto_set", 10)
5095
- );
5096
- return normalizeResponse(response);
5097
- }
5098
- }
5099
- ];
5100
- }
5101
- var EARN_DEMO_MESSAGE = "Earn features (savings, DCD, on-chain staking, auto-earn) are not available in simulated trading mode.";
5102
- var EARN_DEMO_SUGGESTION = "Switch to a live account to use Earn features.";
5103
- var DEMO_GUARD_SKIP = /* @__PURE__ */ new Set(["dcd_redeem", "earn_fixed_purchase"]);
5104
- function withDemoGuard(tool) {
5105
- if (!tool.isWrite || DEMO_GUARD_SKIP.has(tool.name)) return tool;
5106
- const originalHandler = tool.handler;
5107
- return {
5108
- ...tool,
5109
- handler: async (args, context) => {
5110
- if (context.config.demo) {
5111
- throw new ConfigError(EARN_DEMO_MESSAGE, EARN_DEMO_SUGGESTION);
5112
- }
5113
- return originalHandler(args, context);
5114
- }
5115
- };
5116
- }
5117
- function registerAllEarnTools() {
5118
- const tools = [
5119
- ...registerEarnTools(),
5120
- ...registerOnchainEarnTools(),
5121
- ...registerDcdTools(),
5122
- ...registerAutoEarnTools()
5123
- ];
5124
- return tools.map(withDemoGuard);
5125
- }
5126
- function findDateIdx(parts) {
5127
- for (let i = 1; i < parts.length; i++) {
5128
- if (/^\d{6}$/.test(parts[i])) return i;
5129
- }
5130
- return -1;
5131
- }
5132
- function inferExpiryMsFromInstId(instId) {
5133
- const parts = instId.split("-");
5134
- const upper = instId.toUpperCase();
5135
- const dateIdx = findDateIdx(parts);
5136
- if (dateIdx < 0) return null;
5137
- const dp = parts[dateIdx];
5138
- const year = 2e3 + parseInt(dp.slice(0, 2), 10);
5139
- const month = parseInt(dp.slice(2, 4), 10) - 1;
5140
- const day = parseInt(dp.slice(4, 6), 10);
5141
- const isUpDown = upper.includes("UPDOWN");
5142
- let timePart;
5143
- if (isUpDown) {
5144
- const candidate = parts[dateIdx + 2];
5145
- timePart = candidate && /^\d{4}$/.test(candidate) ? candidate : parts[dateIdx + 1];
5146
- } else {
5147
- timePart = parts[dateIdx + 1];
5148
- }
5149
- if (!timePart || !/^\d{4}$/.test(timePart)) return null;
5150
- const hour = parseInt(timePart.slice(0, 2), 10);
5151
- const min = parseInt(timePart.slice(2, 4), 10);
5152
- return Date.UTC(year, month, day, hour - 8, min, 0, 0);
5153
- }
5154
- function extractSeriesId(instId) {
5155
- const parts = instId.split("-");
5156
- for (let i = 0; i < parts.length; i++) {
5157
- if (/^\d{6}$/.test(parts[i])) {
5158
- return parts.slice(0, i).join("-");
5159
- }
5160
- }
5161
- return instId;
5162
- }
5163
- function fmtTimeToken(t) {
5164
- return t.length === 4 ? `${t.slice(0, 2)}:${t.slice(2)}` : t;
5165
- }
5166
- function fmtUpDownName(seriesId, dateStr, parts, dateIdx) {
5167
- const t1 = parts[dateIdx + 1] ?? "";
5168
- const t2 = parts[dateIdx + 2] ?? "";
5169
- const timeRange = t1 && t2 ? ` ${fmtTimeToken(t1)}-${fmtTimeToken(t2)}` : "";
5170
- return `${seriesId} Up/Down \xB7 ${dateStr}${timeRange}`;
5171
- }
5172
- function fmtStrikeName(seriesId, dateStr, label, parts, dateIdx) {
5173
- const strike = parts[dateIdx + 2] ?? "";
5174
- const strikeStr = strike && /^\d+$/.test(strike) ? Number(strike).toLocaleString("en-US") : "";
5175
- return strikeStr ? `${seriesId} ${label} ${strikeStr} \xB7 ${dateStr}` : `${seriesId} \xB7 ${dateStr}`;
5176
- }
5177
- function formatDisplayTitle(instId) {
5178
- const parts = instId.split("-");
5179
- const upper = instId.toUpperCase();
5180
- const seriesId = parts[0] ?? instId;
5181
- const dateIdx = findDateIdx(parts);
5182
- if (dateIdx < 0) return instId;
5183
- const d = parts[dateIdx];
5184
- const month = parseInt(d.slice(2, 4), 10);
5185
- const day = parseInt(d.slice(4, 6), 10);
5186
- const dateStr = `${month}/${day}`;
5187
- if (upper.includes("UPDOWN") || upper.includes("UP-DOWN")) {
5188
- return fmtUpDownName(seriesId, dateStr, parts, dateIdx);
5189
- }
5190
- if (upper.includes("ABOVE")) {
5191
- return fmtStrikeName(seriesId, dateStr, "above", parts, dateIdx);
5192
- }
5193
- if (upper.includes("TOUCH")) {
5194
- return fmtStrikeName(seriesId, dateStr, "touch", parts, dateIdx);
5195
- }
5196
- return `${seriesId} \xB7 ${dateStr}`;
5197
- }
5198
- var OUTCOME_LABELS = {
5199
- "0": "pending",
5200
- "1": "YES",
5201
- "2": "NO"
5202
- };
5203
- var ORDER_STATE_MAP = {
5204
- live: "Unfilled",
5205
- partially_filled: "Partially filled",
5206
- filled: "Filled",
5207
- canceled: "Canceled",
5208
- mmp_canceled: "Canceled"
5209
- };
5210
- function mapOrderState(raw) {
5211
- return ORDER_STATE_MAP[raw] ?? raw;
5212
- }
5213
- var TIMESTAMP_FIELDS = /* @__PURE__ */ new Set([
5214
- "expTime",
5215
- "settleTime",
5216
- "listTime",
5217
- "uTime",
5218
- "cTime",
5219
- "fixTime"
5220
- ]);
5221
- var DEFAULT_SETTLE_CCY = "USDT";
5222
- function convertTimestamps(item) {
5223
- const result = { ...item };
5224
- for (const key of TIMESTAMP_FIELDS) {
5225
- if (!(key in result)) continue;
5226
- const v = Number(result[key]);
5227
- if (v > 0) {
5228
- const d = new Date(v + 8 * 60 * 60 * 1e3);
5229
- const yyyy = d.getUTCFullYear();
5230
- const mo = String(d.getUTCMonth() + 1).padStart(2, "0");
5231
- const dd = String(d.getUTCDate()).padStart(2, "0");
5232
- const hh = String(d.getUTCHours()).padStart(2, "0");
5233
- const mi = String(d.getUTCMinutes()).padStart(2, "0");
5234
- result[key] = `${yyyy}-${mo}-${dd} ${hh}:${mi} UTC+8`;
5235
- } else {
5236
- delete result[key];
5237
- }
5238
- }
5239
- return result;
5240
- }
5241
- function normalizeWrite3(response) {
5242
- const data = response.data;
5243
- if (Array.isArray(data) && data.length > 0) {
5244
- const failed = data.filter(
5245
- (item) => item !== null && typeof item === "object" && "sCode" in item && item["sCode"] !== "0"
5246
- );
5247
- if (failed.length > 0) {
5248
- const messages2 = failed.map(
5249
- (item) => `[${item["sCode"]}] ${item["sMsg"] ?? "Operation failed"}`
5250
- );
5251
- throw new OkxApiError(messages2.join("; "), {
5252
- code: String(failed[0]["sCode"] ?? ""),
5253
- endpoint: response.endpoint
5254
- });
5255
- }
5256
- }
5257
- return { endpoint: response.endpoint, requestTime: response.requestTime, data };
5258
- }
5259
- function extractQuoteCcy(underlying) {
5260
- if (!underlying) return DEFAULT_SETTLE_CCY;
5261
- const parts = underlying.split("-");
5262
- return parts.length >= 2 ? parts[parts.length - 1].toUpperCase() : DEFAULT_SETTLE_CCY;
5263
- }
5264
- async function fetchAvailableBalance(client, ccy = DEFAULT_SETTLE_CCY) {
5265
- try {
5266
- const r = await client.privateGet(
5267
- "/api/v5/account/balance",
5268
- { ccy }
5269
- );
5270
- const data = r["data"];
5271
- if (Array.isArray(data) && data.length > 0) {
5272
- const details = data[0]["details"];
5273
- if (Array.isArray(details) && details.length > 0) {
5274
- const entry = details.find(
5275
- (d) => String(d["ccy"] ?? "").toUpperCase() === ccy.toUpperCase()
5276
- );
5277
- if (!entry) return { balance: null, ccy };
5278
- const bal = String(entry["availBal"] ?? "") || null;
5279
- return { balance: bal, ccy };
5280
- }
5281
- }
5282
- } catch {
5283
- }
5284
- return { balance: null, ccy };
5285
- }
5286
- var KNOWN_UNDERLYINGS = /^(BTC|ETH|TRX|EOS|SOL|IOTA|KISHU|SUSHI|BTG|XTZ|SOLVU)/i;
5287
- function extractUnderlying(seriesId) {
5288
- const m = seriesId.match(KNOWN_UNDERLYINGS);
5289
- return m ? m[1].toUpperCase() : null;
5290
- }
5291
- function resolveOutcome(value) {
5292
- const map = {
5293
- up: "yes",
5294
- yes: "yes",
5295
- down: "no",
5296
- no: "no"
5297
- };
5298
- const resolved = map[value.toLowerCase()];
5299
- if (!resolved) {
5300
- throw new Error(
5301
- `Invalid outcome "${value}". Use: UP or YES for Up/Yes, DOWN or NO for Down/No.`
5302
- );
5303
- }
5304
- return resolved;
5305
- }
5306
- function filterBrowseCandidates(allSeries, underlyingFilter) {
5307
- const isHumanReadable = (id) => /^(BTC|ETH|TRX|EOS|SOL|IOTA|KISHU|SUSHI|BTG|XTZ|SOLVU)-/.test(id);
5308
- const seen = /* @__PURE__ */ new Set();
5309
- const candidates = [];
5310
- for (const s of allSeries) {
5311
- const settlement = s["settlement"];
5312
- const method = String(settlement?.["method"] ?? "");
5313
- const uly = String(settlement?.["underlying"] ?? "");
5314
- if (underlyingFilter && !uly.startsWith(underlyingFilter)) continue;
5315
- const key = `${method}:${uly}`;
5316
- if (!seen.has(key) && isHumanReadable(String(s["seriesId"] ?? ""))) {
5317
- seen.add(key);
5318
- candidates.push(s);
5319
- }
5320
- }
5321
- return candidates;
5322
- }
5323
- function isActiveMarket(m, isUpDown, now) {
5324
- if (!isUpDown && (!m["floorStrike"] || m["floorStrike"] === "")) return false;
5325
- const expMs = Number(m["expTime"] ?? 0);
5326
- return expMs <= 0 || expMs > now;
5327
- }
5328
- function toContractSummary(m) {
5329
- const converted = convertTimestamps(m);
5330
- const id = String(m["instId"] ?? "");
5331
- return {
5332
- instId: id,
5333
- displayTitle: formatDisplayTitle(id),
5334
- expTime: converted["expTime"],
5335
- floorStrike: m["floorStrike"],
5336
- px: m["px"],
5337
- outcome: OUTCOME_LABELS[String(m["outcome"] ?? "")] ?? m["outcome"]
5338
- };
5339
- }
5340
- async function fetchActiveContractsForSeries(client, s) {
5341
- const seriesId = String(s["seriesId"] ?? "");
5342
- const settlement = s["settlement"];
5343
- const method = String(settlement?.["method"] ?? "");
5344
- const isUpDown = method === "price_up_down";
5345
- try {
5346
- const r = await client.privateGet(
5347
- "/api/v5/public/event-contract/markets",
5348
- compactObject({ seriesId, state: "live" }),
5349
- privateRateLimit("event_browse", 20)
5350
- );
5351
- const normalized = normalizeResponse(r);
5352
- const markets = Array.isArray(normalized["data"]) ? normalized["data"] : [];
5353
- const now = Date.now();
5354
- const active = markets.filter((m) => isActiveMarket(m, isUpDown, now)).map(toContractSummary);
5355
- if (active.length === 0) return null;
5356
- return {
5357
- seriesId,
5358
- method: String(settlement?.["method"] ?? ""),
5359
- underlying: String(settlement?.["underlying"] ?? ""),
5360
- freq: String(s["freq"] ?? ""),
5361
- contracts: active
5362
- };
5363
- } catch {
5364
- return null;
5365
- }
5366
- }
5367
- function resolveUnderlyingFromSeriesResp(seriesResp) {
5368
- if (!seriesResp) return null;
5369
- const sResp = seriesResp;
5370
- const sData = Array.isArray(sResp["data"]) ? sResp["data"] : [];
5371
- if (sData.length > 0) {
5372
- const settlement = sData[0]["settlement"];
5373
- return String(settlement?.["underlying"] ?? "") || null;
5374
- }
5375
- return null;
5376
- }
5377
- function translateAndSortMarkets(rawData, limit) {
5378
- const sorted = [...rawData].sort((a, b) => {
5379
- const tA = Number(a["expTime"] ?? 0);
5380
- const tB = Number(b["expTime"] ?? 0);
5381
- return tA - tB;
5382
- });
5383
- const sliced = limit && limit > 0 ? sorted.slice(0, limit) : sorted;
5384
- return sliced.map((item) => {
5385
- const converted = convertTimestamps(item);
5386
- if (typeof converted["outcome"] === "string") {
5387
- converted["outcome"] = OUTCOME_LABELS[converted["outcome"]] ?? converted["outcome"];
5388
- }
5389
- converted["displayTitle"] = formatDisplayTitle(String(item["instId"] ?? ""));
5390
- return converted;
5391
- });
5392
- }
5393
- function enrichFill(item) {
5394
- const subType = String(item["subType"] ?? "");
5395
- const isSettle = subType === "414" || subType === "415";
5396
- const enriched = {
5397
- ...item,
5398
- displayTitle: formatDisplayTitle(String(item["instId"] ?? "")),
5399
- outcome: OUTCOME_LABELS[String(item["outcome"] ?? "")] ?? item["outcome"],
5400
- type: isSettle ? "settlement" : "fill"
5401
- };
5402
- if (isSettle) {
5403
- const fillPnl = parseFloat(String(item["fillPnl"] ?? "NaN"));
5404
- const isWin = subType === "414";
5405
- enriched["settlementResult"] = isWin ? "win" : "loss";
5406
- enriched["pnl"] = isNaN(fillPnl) ? void 0 : fillPnl;
5407
- }
5408
- return enriched;
5409
- }
5410
- async function fetchIdxPx(client, underlying) {
5411
- try {
5412
- const r = await client.publicGet(
5413
- "/api/v5/market/index-tickers",
5414
- { instId: underlying },
5415
- publicRateLimit("fetchIdxPx", 20)
5416
- );
5417
- const data = r["data"];
5418
- if (Array.isArray(data) && data.length > 0) {
5419
- return String(data[0]["idxPx"] ?? "") || null;
5420
- }
5421
- } catch {
5422
- }
5423
- return null;
5424
- }
5425
- function handlePlaceOrderError(base, rawArgs, endpoint) {
5426
- if (!Array.isArray(base["data"])) return;
5427
- const item = base["data"][0];
5428
- const sCode = item && String(item["sCode"] ?? "");
5429
- if (!sCode || sCode === "0") return;
5430
- const sMsg = String(item["sMsg"] ?? "Order failed");
5431
- if (sCode === "51001") {
5432
- const instId = requireString(asRecord(rawArgs), "instId");
5433
- const seriesId = extractSeriesId(instId);
5434
- const expiryMs = inferExpiryMsFromInstId(instId);
5435
- const isExpired = expiryMs !== null && expiryMs < Date.now();
5436
- const reason = isExpired ? `The contract (${instId}) has expired.` : `The contract (${instId}) was not found \u2014 it may not exist or has not started yet.`;
5437
- throw new OkxApiError(
5438
- `${reason} Ask the user if they'd like to place the same order on the next session. If yes, call event_get_markets with seriesId=${seriesId} and state=live to find available contracts.`,
5439
- { code: sCode, endpoint }
5440
- );
5441
- }
5442
- throw new OkxApiError(`[${sCode}] ${sMsg}`, { code: sCode, endpoint });
5443
- }
5444
- var OUTCOME_SCHEMA = {
5445
- type: "string",
5446
- enum: ["UP", "YES", "DOWN", "NO"],
5447
- description: `Which outcome to trade.
5448
- UP/DOWN direction contracts: UP (price rises during the period) or DOWN (price falls).
5449
- YES/NO price-target or touch contracts: YES (condition met) or NO (condition not met).
5450
- Check the series type from event_get_series to determine which applies.
5451
- NOTE: px is the event contract price (0.01\u20130.99), NOT the underlying asset price. It reflects market-implied probability when actively trading.`
5452
- };
5453
- function registerEventContractTools() {
5454
- return [
5455
- // -----------------------------------------------------------------------
5456
- // Read-only — browse (user-facing) + series / events / markets (internal)
5457
- // -----------------------------------------------------------------------
5458
- {
5459
- name: "event_browse",
5460
- module: "event",
5461
- description: "Browse currently active (in-progress) event contracts. Call when user asks what event contracts are available to trade. Internally fetches series and live markets in parallel, returns only in-progress contracts (floorStrike set). If a live quote field px is present, it is the event contract price (0.01\u20130.99), not the underlying asset price; it reflects the market-implied probability when actively trading. Grouped by settlement type and underlying.",
5462
- isWrite: false,
5463
- inputSchema: {
5464
- type: "object",
5465
- properties: {
5466
- underlying: {
5467
- type: "string",
5468
- description: "Filter by underlying asset, e.g. BTC-USD, ETH-USD. Omit for all."
5469
- }
5470
- }
5471
- },
5472
- handler: async (rawArgs, context) => {
5473
- const args = asRecord(rawArgs);
5474
- const underlyingFilter = readString(args, "underlying");
5475
- const seriesResp = await context.client.privateGet(
5476
- "/api/v5/public/event-contract/series",
5477
- compactObject({}),
5478
- privateRateLimit("event_browse", 10)
5479
- );
5480
- const normalizedSeries = normalizeResponse(seriesResp);
5481
- const allSeries = Array.isArray(normalizedSeries["data"]) ? normalizedSeries["data"] : [];
5482
- const candidates = filterBrowseCandidates(allSeries, underlyingFilter);
5483
- const marketResults = await Promise.all(
5484
- candidates.map((s) => fetchActiveContractsForSeries(context.client, s))
5485
- );
5486
- const results = marketResults.filter(Boolean);
5487
- return {
5488
- data: results,
5489
- total: results.reduce((n, r) => n + (r?.contracts?.length ?? 0), 0)
5490
- };
5491
- }
5492
- },
5493
- {
5494
- name: "event_get_series",
5495
- module: "event",
5496
- description: "List event contract series. Returns all available series with settlement type and underlying. Use event_browse to see currently active contracts.",
5497
- isWrite: false,
5498
- inputSchema: {
5499
- type: "object",
5500
- properties: {
5501
- seriesId: {
5502
- type: "string",
5503
- description: "Filter by series ID, e.g. BTC-ABOVE-DAILY. Omit for all."
5504
- }
5505
- }
5506
- },
5507
- handler: async (rawArgs, context) => {
5508
- const args = asRecord(rawArgs);
5509
- const response = await context.client.privateGet(
5510
- "/api/v5/public/event-contract/series",
5511
- compactObject({ seriesId: readString(args, "seriesId") }),
5512
- privateRateLimit("event_get_series", 20)
5513
- );
5514
- return normalizeResponse(response);
5515
- }
5516
- },
5517
- {
5518
- name: "event_get_events",
5519
- module: "event",
5520
- description: "List expiry periods within a series. state: preopen|live|settling|expired. expTime is pre-formatted UTC+8.",
5521
- isWrite: false,
5522
- inputSchema: {
5523
- type: "object",
5524
- properties: {
5525
- seriesId: {
5526
- type: "string",
5527
- description: "Series ID, e.g. BTC-ABOVE-DAILY (required)"
5528
- },
5529
- eventId: {
5530
- type: "string",
5531
- description: "Filter by event ID, e.g. BTC-ABOVE-DAILY-260224-1600"
5532
- },
5533
- state: {
5534
- type: "string",
5535
- enum: ["preopen", "live", "settling", "expired"],
5536
- description: "preopen=markets not yet trading; live=active; settling=awaiting settlement; expired=done"
5537
- },
5538
- limit: {
5539
- type: "number",
5540
- description: "Max results (default 100, max 100)"
5541
- },
5542
- before: {
5543
- type: "string",
5544
- description: "Pagination: return records newer than this expTime"
5545
- },
5546
- after: {
5547
- type: "string",
5548
- description: "Pagination: return records older than this expTime"
5549
- }
5550
- },
5551
- required: ["seriesId"]
5552
- },
5553
- handler: async (rawArgs, context) => {
5554
- const args = asRecord(rawArgs);
5555
- const response = await context.client.privateGet(
5556
- "/api/v5/public/event-contract/events",
5557
- compactObject({
5558
- seriesId: requireString(args, "seriesId"),
5559
- eventId: readString(args, "eventId"),
5560
- state: readString(args, "state"),
5561
- limit: readNumber(args, "limit"),
5562
- before: readString(args, "before"),
5563
- after: readString(args, "after")
5564
- }),
5565
- privateRateLimit("event_get_events", 20)
5566
- );
5567
- const base = normalizeResponse(response);
5568
- const data = Array.isArray(base["data"]) ? base["data"].map(convertTimestamps) : base["data"];
5569
- return { ...base, data };
5570
- }
5571
- },
5572
- {
5573
- name: "event_get_markets",
5574
- module: "event",
5575
- description: "List tradeable contracts within a series. state=live for active contracts, state=expired for settlement results. floorStrike=strike price; px (when present) is the event contract price (0.01\u20130.99), not the underlying asset price \u2014 reflects the market-implied probability when actively trading; outcome pre-translated (pending/YES/NO/UP/DOWN); timestamps UTC+8.",
5576
- isWrite: false,
5577
- inputSchema: {
5578
- type: "object",
5579
- properties: {
5580
- seriesId: {
5581
- type: "string",
5582
- description: "Series ID, e.g. BTC-ABOVE-DAILY (required)"
5583
- },
5584
- eventId: {
5585
- type: "string",
5586
- description: "Filter by event ID, e.g. BTC-ABOVE-DAILY-260224-1600"
5587
- },
5588
- instId: {
5589
- type: "string",
5590
- description: "Filter by instrument ID"
5591
- },
5592
- state: {
5593
- type: "string",
5594
- enum: ["preopen", "live", "settling", "expired"],
5595
- description: "preopen=not yet trading; live=active; settling=awaiting settlement; expired=settled"
5596
- },
5597
- limit: {
5598
- type: "number",
5599
- description: "Max results (default 100, max 100)"
5600
- },
5601
- before: {
5602
- type: "string",
5603
- description: "Pagination: return records newer than this expTime"
5604
- },
5605
- after: {
5606
- type: "string",
5607
- description: "Pagination: return records older than this expTime"
5608
- }
5609
- },
5610
- required: ["seriesId"]
5611
- },
5612
- handler: async (rawArgs, context) => {
5613
- const args = asRecord(rawArgs);
5614
- const seriesId = requireString(args, "seriesId");
5615
- const knownUnderlying = extractUnderlying(seriesId);
5616
- const [marketsResp, seriesResp, idxPxFromKnown] = await Promise.all([
5617
- context.client.privateGet(
5618
- "/api/v5/public/event-contract/markets",
5619
- compactObject({
5620
- seriesId,
5621
- eventId: readString(args, "eventId"),
5622
- instId: readString(args, "instId"),
5623
- state: readString(args, "state"),
5624
- before: readString(args, "before"),
5625
- after: readString(args, "after")
5626
- }),
5627
- privateRateLimit("event_get_markets", 20)
5628
- ),
5629
- knownUnderlying ? Promise.resolve(null) : context.client.privateGet(
5630
- "/api/v5/public/event-contract/series",
5631
- compactObject({ seriesId }),
5632
- privateRateLimit("event_get_series", 20)
5633
- ),
5634
- knownUnderlying ? fetchIdxPx(context.client, knownUnderlying + "-USDT") : Promise.resolve(null)
5635
- ]);
5636
- let underlying = knownUnderlying ? knownUnderlying + "-USDT" : null;
5637
- if (!underlying) {
5638
- underlying = resolveUnderlyingFromSeriesResp(seriesResp);
5639
- }
5640
- const idxPx = idxPxFromKnown ?? (underlying && !knownUnderlying ? await fetchIdxPx(context.client, underlying) : null);
5641
- const base = normalizeResponse(marketsResp);
5642
- const limit = readNumber(args, "limit");
5643
- const rawData = Array.isArray(base["data"]) ? base["data"] : [];
5644
- const translated = translateAndSortMarkets(rawData, limit);
5645
- return {
5646
- ...base,
5647
- data: translated,
5648
- currentIdxPx: idxPx,
5649
- underlying
5650
- };
5651
- }
5652
- },
5653
- {
5654
- name: "event_get_orders",
5655
- module: "event",
5656
- description: "Query event contract orders. state=live for open orders; omit for history. outcome pre-translated (YES/NO/UP/DOWN).",
5657
- isWrite: false,
5658
- inputSchema: {
5659
- type: "object",
5660
- properties: {
5661
- instId: {
5662
- type: "string",
5663
- description: "Event contract instrument ID"
5664
- },
5665
- state: {
5666
- type: "string",
5667
- description: "live=pending orders; omit for history"
5668
- },
5669
- limit: {
5670
- type: "number",
5671
- description: "Max results (default 20)"
5672
- }
5673
- }
5674
- },
5675
- handler: async (rawArgs, context) => {
5676
- const args = asRecord(rawArgs);
5677
- const state = readString(args, "state");
5678
- const isPending = state === "live";
5679
- const endpoint = isPending ? "/api/v5/trade/orders-pending" : "/api/v5/trade/orders-history";
5680
- const response = await context.client.privateGet(
5681
- endpoint,
5682
- compactObject({
5683
- instType: "EVENTS",
5684
- instId: readString(args, "instId"),
5685
- limit: readNumber(args, "limit")
5686
- }),
5687
- privateRateLimit("event_get_orders", 20)
5688
- );
5689
- const base = normalizeResponse(response);
5690
- const data = Array.isArray(base["data"]) ? base["data"].map((item) => ({
5691
- ...item,
5692
- displayTitle: formatDisplayTitle(String(item["instId"] ?? "")),
5693
- outcome: OUTCOME_LABELS[String(item["outcome"] ?? "")] ?? item["outcome"],
5694
- state: String(item["state"] ?? ""),
5695
- stateLabel: mapOrderState(String(item["state"] ?? ""))
5696
- })) : base["data"];
5697
- return { ...base, data };
5698
- }
5699
- },
5700
- {
5701
- name: "event_get_fills",
5702
- module: "event",
5703
- description: "Get event contract fill history. outcome pre-translated (YES/NO/UP/DOWN). Each record includes a 'type' field: 'fill' (subType 410, opening trade) or 'settlement' (subType 414 win / subType 415 loss, contract expiry payout). Settlement records include 'settlementResult' (win/loss) and 'pnl' fields \u2014 no separate market lookup needed to determine outcome.",
5704
- isWrite: false,
5705
- inputSchema: {
5706
- type: "object",
5707
- properties: {
5708
- instId: {
5709
- type: "string",
5710
- description: "Event contract instrument ID"
5711
- },
5712
- limit: {
5713
- type: "number",
5714
- description: "Max results (default 20)"
5715
- }
5716
- }
5717
- },
5718
- handler: async (rawArgs, context) => {
5719
- const args = asRecord(rawArgs);
5720
- const response = await context.client.privateGet(
5721
- "/api/v5/trade/fills",
5722
- compactObject({
5723
- instType: "EVENTS",
5724
- instId: readString(args, "instId"),
5725
- limit: readNumber(args, "limit")
5726
- }),
5727
- privateRateLimit("event_get_fills", 20)
5728
- );
5729
- const base = normalizeResponse(response);
5730
- const data = Array.isArray(base["data"]) ? base["data"].map(enrichFill) : base["data"];
5731
- return { ...base, data };
5732
- }
5733
- },
5734
- // -----------------------------------------------------------------------
5735
- // Private — write
5736
- // -----------------------------------------------------------------------
5737
- {
5738
- name: "event_place_order",
5739
- module: "event",
5740
- description: `Place an event contract order. [CAUTION] Places a real order.
5741
- - outcome: UP/YES (bet price goes up/condition met) or DOWN/NO (bet price goes down/condition not met)
5742
- - For limit orders: px is the event contract price (0.01\u20130.99), NOT the underlying asset price. It reflects market-implied probability when actively trading
5743
- - tdMode is always isolated; speedBump is auto-set per exchange requirement \u2014 do not pass either`,
5744
- isWrite: true,
5745
- inputSchema: {
5746
- type: "object",
5747
- properties: {
5748
- instId: {
5749
- type: "string",
5750
- description: "Event contract instrument ID, e.g. BTC-ABOVE-DAILY-260224-1600-120000"
5751
- },
5752
- side: {
5753
- type: "string",
5754
- enum: ["buy", "sell"],
5755
- description: "buy=open position, sell=close position"
5756
- },
5757
- outcome: OUTCOME_SCHEMA,
5758
- ordType: {
5759
- type: "string",
5760
- enum: ["market", "limit", "post_only"],
5761
- description: "Order type (default market)"
5762
- },
5763
- sz: {
5764
- type: "string",
5765
- description: "For limit/post_only: number of contracts. For market: quote currency amount (server converts to contracts using best available price; actual fill count may differ)."
5766
- },
5767
- px: {
5768
- type: "string",
5769
- description: "Event contract price (0.01\u20130.99). Required when ordType=limit. Do NOT use for market orders."
5388
+ }
5389
+ throw error;
5770
5390
  }
5771
- },
5772
- required: ["instId", "side", "outcome", "sz"]
5773
- },
5774
- handler: async (rawArgs, context) => {
5775
- const args = asRecord(rawArgs);
5776
- const ordType = readString(args, "ordType") ?? "market";
5777
- const speedBump = ordType !== "post_only" ? "1" : void 0;
5778
- const instId = requireString(args, "instId");
5779
- const response = await context.client.privatePost(
5780
- "/api/v5/trade/order",
5781
- compactObject({
5782
- instId,
5783
- tdMode: "isolated",
5784
- side: requireString(args, "side"),
5785
- outcome: resolveOutcome(requireString(args, "outcome")),
5786
- ordType,
5787
- sz: requireString(args, "sz"),
5788
- px: readString(args, "px"),
5789
- speedBump,
5790
- tag: context.config.sourceTag
5791
- }),
5792
- privateRateLimit("event_place_order", 60)
5793
- );
5794
- const base = normalizeResponse(response);
5795
- handlePlaceOrderError(base, asRecord(rawArgs), response.endpoint);
5796
- const data = Array.isArray(base["data"]) ? base["data"].map(({ tag: _t, ...rest }) => rest) : base["data"];
5797
- const placeSeriesId = extractSeriesId(instId);
5798
- const placeUnderlying = extractUnderlying(placeSeriesId);
5799
- const placeCcy = extractQuoteCcy(placeUnderlying ? placeUnderlying + "-USDT" : null);
5800
- const balResult = await fetchAvailableBalance(context.client, placeCcy);
5801
- const result = { ...base, data };
5802
- if (balResult.balance) {
5803
- result["availableBalance"] = balResult.balance;
5804
- result["availableBalanceCcy"] = balResult.ccy;
5805
- }
5806
- if (ordType === "market") {
5807
- result["orderNote"] = "Market order: sz is a quote currency amount. The exchange converts it to contracts based on best available price.";
5808
- }
5809
- return result;
5810
- }
5811
- },
5812
- {
5813
- name: "event_amend_order",
5814
- module: "event",
5815
- description: "Amend a pending event contract order (change price or size). [CAUTION] Modifies a real order. Only limit/post_only orders can be amended.",
5816
- isWrite: true,
5817
- inputSchema: {
5818
- type: "object",
5819
- properties: {
5820
- instId: { type: "string", description: "Event contract instrument ID" },
5821
- ordId: { type: "string", description: "Order ID to amend" },
5822
- newPx: { type: "string", description: "New event contract price (0.01\u20130.99). Omit to keep current." },
5823
- newSz: { type: "string", description: "New size in contracts (omit to keep current)" }
5824
- },
5825
- required: ["instId", "ordId"]
5826
- },
5827
- handler: async (rawArgs, context) => {
5828
- const args = asRecord(rawArgs);
5829
- const response = await context.client.privatePost(
5830
- "/api/v5/trade/amend-order",
5831
- compactObject({
5832
- instId: requireString(args, "instId"),
5833
- ordId: requireString(args, "ordId"),
5834
- newPx: readString(args, "newPx"),
5835
- newSz: readString(args, "newSz"),
5836
- speedBump: "1"
5837
- }),
5838
- privateRateLimit("event_amend_order", 60)
5839
- );
5840
- return normalizeWrite3(response);
5391
+ });
5841
5392
  }
5842
- },
5393
+ }
5394
+ ];
5395
+ }
5396
+ function registerAutoEarnTools() {
5397
+ return [
5843
5398
  {
5844
- name: "event_cancel_order",
5845
- module: "event",
5846
- description: "Cancel a pending event contract order. [CAUTION] Cancels a real order. instId must be the full event contract instrument ID (e.g. BTC-ABOVE-DAILY-260224-1600-69700), NOT a spot trading pair.",
5399
+ name: "earn_auto_set",
5400
+ module: "earn.autoearn",
5401
+ description: "Enable or disable auto-earn for a currency. earnType='0' for auto-lend+stake (most currencies); earnType='1' for USDG earn (USDG, BUIDL). Use account_get_balance first: if autoLendStatus or autoStakingStatus != 'unsupported', use earnType='0'; for USDG/BUIDL use earnType='1'. [CAUTION] Cannot disable within 24h of enabling.",
5847
5402
  isWrite: true,
5848
5403
  inputSchema: {
5849
5404
  type: "object",
5850
5405
  properties: {
5851
- instId: {
5406
+ ccy: {
5852
5407
  type: "string",
5853
- description: "Event contract instrument ID"
5408
+ description: "Currency, e.g. SOL, USDG"
5854
5409
  },
5855
- ordId: {
5410
+ action: {
5411
+ type: "string",
5412
+ description: "turn_on or turn_off"
5413
+ },
5414
+ earnType: {
5856
5415
  type: "string",
5857
- description: "Order ID to cancel"
5416
+ description: "0=auto-lend+stake (default), 1=USDG earn. Omit to use default."
5858
5417
  }
5859
5418
  },
5860
- required: ["instId", "ordId"]
5419
+ required: ["ccy", "action"]
5861
5420
  },
5862
5421
  handler: async (rawArgs, context) => {
5863
5422
  const args = asRecord(rawArgs);
5864
- const instId = requireString(args, "instId");
5865
5423
  const response = await context.client.privatePost(
5866
- "/api/v5/trade/cancel-order",
5867
- { instId, ordId: requireString(args, "ordId") },
5868
- privateRateLimit("event_cancel_order", 60)
5424
+ "/api/v5/account/set-auto-earn",
5425
+ compactObject({
5426
+ ccy: requireString(args, "ccy"),
5427
+ action: requireString(args, "action"),
5428
+ earnType: readString(args, "earnType") ?? "0"
5429
+ }),
5430
+ privateRateLimit("earn_auto_set", 10)
5869
5431
  );
5870
- if (Array.isArray(response.data) && response.data.length > 0) {
5871
- const item = response.data[0];
5872
- const sCode = item && String(item["sCode"] ?? "");
5873
- if (sCode === "51001") {
5874
- const expiryMs = inferExpiryMsFromInstId(instId);
5875
- const isExpired = expiryMs !== null && expiryMs < Date.now();
5876
- const reason = isExpired ? `The contract (${instId}) has already expired \u2014 the order was auto-cancelled at settlement. Check event_get_fills to confirm the outcome.` : `Instrument (${instId}) not found. Verify the instId with event_get_markets before retrying.`;
5877
- throw new OkxApiError(reason, { code: sCode, endpoint: response.endpoint });
5878
- }
5879
- }
5880
- return normalizeWrite3(response);
5432
+ return normalizeResponse(response);
5433
+ }
5434
+ }
5435
+ ];
5436
+ }
5437
+ var EARN_DEMO_MESSAGE = "Earn features (savings, DCD, on-chain staking, auto-earn) are not available in simulated trading mode.";
5438
+ var EARN_DEMO_SUGGESTION = "Switch to a live account to use Earn features.";
5439
+ var DEMO_GUARD_SKIP = /* @__PURE__ */ new Set(["dcd_redeem", "earn_fixed_purchase"]);
5440
+ function withDemoGuard(tool) {
5441
+ if (!tool.isWrite || DEMO_GUARD_SKIP.has(tool.name)) return tool;
5442
+ const originalHandler = tool.handler;
5443
+ return {
5444
+ ...tool,
5445
+ handler: async (args, context) => {
5446
+ if (context.config.demo) {
5447
+ throw new ConfigError(EARN_DEMO_MESSAGE, EARN_DEMO_SUGGESTION);
5881
5448
  }
5449
+ return originalHandler(args, context);
5882
5450
  }
5451
+ };
5452
+ }
5453
+ function registerAllEarnTools() {
5454
+ const tools = [
5455
+ ...registerEarnTools(),
5456
+ ...registerOnchainEarnTools(),
5457
+ ...registerDcdTools(),
5458
+ ...registerAutoEarnTools()
5883
5459
  ];
5460
+ return tools.map(withDemoGuard);
5884
5461
  }
5885
5462
  function buildContractTradeTools(cfg) {
5886
5463
  const { prefix, module, label, instTypes, instIdExample } = cfg;
@@ -8520,7 +8097,6 @@ function allToolSpecs() {
8520
8097
  ...registerOptionAlgoTools(),
8521
8098
  ...registerAlgoTradeTools(),
8522
8099
  ...registerAccountTools(),
8523
- ...registerEventContractTools(),
8524
8100
  ...registerBotTools(),
8525
8101
  ...registerAllEarnTools(),
8526
8102
  ...registerAuditTools(),
@@ -8539,12 +8115,12 @@ function createToolRunner(client, config) {
8539
8115
  };
8540
8116
  }
8541
8117
  function configFilePath() {
8542
- return join4(homedir2(), ".okx", "config.toml");
8118
+ return join6(homedir4(), ".okx", "config.toml");
8543
8119
  }
8544
8120
  function readFullConfig() {
8545
8121
  const path42 = configFilePath();
8546
8122
  if (!existsSync3(path42)) return { profiles: {} };
8547
- const raw = readFileSync3(path42, "utf-8");
8123
+ const raw = readFileSync4(path42, "utf-8");
8548
8124
  try {
8549
8125
  return parse(raw);
8550
8126
  } catch (err) {
@@ -8572,11 +8148,11 @@ var CONFIG_HEADER = `# OKX Trade Kit Configuration
8572
8148
  `;
8573
8149
  function writeFullConfig(config) {
8574
8150
  const path42 = configFilePath();
8575
- const dir = dirname3(path42);
8151
+ const dir = dirname4(path42);
8576
8152
  if (!existsSync3(dir)) {
8577
- mkdirSync4(dir, { recursive: true });
8153
+ mkdirSync5(dir, { recursive: true });
8578
8154
  }
8579
- writeFileSync3(path42, CONFIG_HEADER + stringify(config), "utf-8");
8155
+ writeFileSync4(path42, CONFIG_HEADER + stringify(config), "utf-8");
8580
8156
  }
8581
8157
  function expandShorthand(moduleId) {
8582
8158
  if (moduleId === "all") return [...MODULES];
@@ -8690,21 +8266,21 @@ function loadConfig(cli) {
8690
8266
  verbose: cli.verbose ?? false
8691
8267
  };
8692
8268
  }
8693
- var CACHE_FILE = join5(homedir3(), ".okx", "update-check.json");
8269
+ var CACHE_FILE = join7(homedir5(), ".okx", "update-check.json");
8694
8270
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
8695
- function readCache() {
8271
+ function readCache2() {
8696
8272
  try {
8697
8273
  if (existsSync4(CACHE_FILE)) {
8698
- return JSON.parse(readFileSync4(CACHE_FILE, "utf-8"));
8274
+ return JSON.parse(readFileSync5(CACHE_FILE, "utf-8"));
8699
8275
  }
8700
8276
  } catch {
8701
8277
  }
8702
8278
  return {};
8703
8279
  }
8704
- function writeCache(cache) {
8280
+ function writeCache2(cache) {
8705
8281
  try {
8706
- mkdirSync5(join5(homedir3(), ".okx"), { recursive: true });
8707
- writeFileSync4(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
8282
+ mkdirSync6(join7(homedir5(), ".okx"), { recursive: true });
8283
+ writeFileSync5(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
8708
8284
  } catch {
8709
8285
  }
8710
8286
  }
@@ -8751,14 +8327,14 @@ async function fetchLatestVersion(packageName) {
8751
8327
  function refreshCacheInBackground(packageName) {
8752
8328
  fetchLatestVersion(packageName).then((latest) => {
8753
8329
  if (!latest) return;
8754
- const cache = readCache();
8330
+ const cache = readCache2();
8755
8331
  cache[packageName] = { latestVersion: latest, checkedAt: Date.now() };
8756
- writeCache(cache);
8332
+ writeCache2(cache);
8757
8333
  }).catch(() => {
8758
8334
  });
8759
8335
  }
8760
8336
  function checkForUpdates(packageName, currentVersion) {
8761
- const cache = readCache();
8337
+ const cache = readCache2();
8762
8338
  const entry = cache[packageName];
8763
8339
  if (entry && isNewerVersion(currentVersion, entry.latestVersion)) {
8764
8340
  process.stderr.write(
@@ -9613,7 +9189,7 @@ async function cmdDiagnoseMcp(options = {}) {
9613
9189
 
9614
9190
  // src/commands/diagnose.ts
9615
9191
  var CLI_VERSION = readCliVersion();
9616
- var GIT_HASH = true ? "765bfa2" : "dev";
9192
+ var GIT_HASH = true ? "980de10" : "dev";
9617
9193
  function maskKey2(key) {
9618
9194
  if (!key) return "(not set)";
9619
9195
  if (key.length <= 8) return "****";
@@ -9910,24 +9486,24 @@ async function runCliChecks(config, profile, outputPath) {
9910
9486
 
9911
9487
  // src/commands/upgrade.ts
9912
9488
  import { spawnSync as spawnSync2 } from "child_process";
9913
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7 } from "fs";
9914
- import { dirname as dirname5, join as join7 } from "path";
9915
- import { homedir as homedir5 } from "os";
9489
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8 } from "fs";
9490
+ import { dirname as dirname6, join as join9 } from "path";
9491
+ import { homedir as homedir7 } from "os";
9916
9492
  var PACKAGES = ["@okx_ai/okx-trade-mcp", "@okx_ai/okx-trade-cli"];
9917
- var CACHE_FILE2 = join7(homedir5(), ".okx", "last_check");
9493
+ var CACHE_FILE2 = join9(homedir7(), ".okx", "last_check");
9918
9494
  var THROTTLE_MS = 12 * 60 * 60 * 1e3;
9919
- var NPM_BIN = join7(dirname5(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
9495
+ var NPM_BIN = join9(dirname6(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
9920
9496
  function readLastCheck() {
9921
9497
  try {
9922
- return parseInt(readFileSync6(CACHE_FILE2, "utf-8").trim(), 10) || 0;
9498
+ return parseInt(readFileSync7(CACHE_FILE2, "utf-8").trim(), 10) || 0;
9923
9499
  } catch {
9924
9500
  return 0;
9925
9501
  }
9926
9502
  }
9927
9503
  function writeLastCheck() {
9928
9504
  try {
9929
- mkdirSync7(join7(homedir5(), ".okx"), { recursive: true });
9930
- writeFileSync6(CACHE_FILE2, String(Math.floor(Date.now() / 1e3)), "utf-8");
9505
+ mkdirSync8(join9(homedir7(), ".okx"), { recursive: true });
9506
+ writeFileSync7(CACHE_FILE2, String(Math.floor(Date.now() / 1e3)), "utf-8");
9931
9507
  } catch {
9932
9508
  }
9933
9509
  }
@@ -10602,47 +10178,6 @@ var HELP_TREE = {
10602
10178
  }
10603
10179
  }
10604
10180
  },
10605
- event: {
10606
- description: "Event contract trading \u2014 binary outcome prediction markets (Yes/No, Up/Down)",
10607
- commands: {
10608
- browse: {
10609
- usage: "okx event browse [--underlying <asset>]",
10610
- description: "Browse all active (in-progress) event contracts; px is event contract price (0.01\u20130.99), not the underlying asset price \u2014 reflects market-implied probability when actively trading"
10611
- },
10612
- series: {
10613
- usage: "okx event series [--seriesId <id>]",
10614
- description: "List event contract series grouped by type (UP/DOWN direction or YES/NO price-target)"
10615
- },
10616
- events: {
10617
- usage: "okx event events <seriesId> [--state <preopen|live|settling|expired>] [--limit <n>]",
10618
- description: "List events in a series (each event = one expiry)"
10619
- },
10620
- markets: {
10621
- usage: "okx event markets <seriesId> [--eventId <id>] [--state <preopen|live|settling|expired>] [--limit <n>]",
10622
- description: "List markets; px is event contract price (0.01\u20130.99), not the underlying asset price \u2014 reflects market-implied probability when actively trading; expired includes translated outcome and settleValue"
10623
- },
10624
- place: {
10625
- usage: "okx event place <instId> <side> <outcome> <sz> [--px <price>] [--ordType <market|limit|post_only>]",
10626
- description: "[CAUTION] Place an event contract order. outcome: UP/YES or DOWN/NO. limit: sz=contracts, px required. market: sz=quote currency amount, no px."
10627
- },
10628
- amend: {
10629
- usage: "okx event amend <instId> <ordId> [--px <price>] [--sz <n>]",
10630
- description: "[CAUTION] Amend a pending limit order (change price or size)"
10631
- },
10632
- cancel: {
10633
- usage: "okx event cancel <instId> <ordId>",
10634
- description: "Cancel a pending event contract order"
10635
- },
10636
- orders: {
10637
- usage: "okx event orders [--instId <id>] [--state live] [--limit <n>]",
10638
- description: "List event orders (state=live for pending; omit for history)"
10639
- },
10640
- fills: {
10641
- usage: "okx event fills [--instId <id>] [--limit <n>]",
10642
- description: "Get event contract fill history"
10643
- }
10644
- }
10645
- },
10646
10181
  config: {
10647
10182
  description: "Manage CLI configuration profiles",
10648
10183
  commands: {
@@ -11001,11 +10536,6 @@ var CLI_OPTIONS = {
11001
10536
  dir: { type: "string" },
11002
10537
  page: { type: "string" },
11003
10538
  format: { type: "string" },
11004
- // event contract
11005
- underlying: { type: "string" },
11006
- seriesId: { type: "string" },
11007
- eventId: { type: "string" },
11008
- outcome: { type: "string" },
11009
10539
  // diagnostics — cli/mcp/all/output are diagnose-specific; verbose is shared
11010
10540
  verbose: { type: "boolean", default: false },
11011
10541
  mcp: { type: "boolean", default: false },
@@ -13836,13 +13366,13 @@ async function cmdDcdQuoteAndBuy(run, opts) {
13836
13366
  }
13837
13367
 
13838
13368
  // src/commands/skill.ts
13839
- import { tmpdir, homedir as homedir7 } from "os";
13840
- import { join as join9, dirname as dirname6 } from "path";
13841
- import { mkdirSync as mkdirSync8, rmSync, existsSync as existsSync7, copyFileSync as copyFileSync2 } from "fs";
13369
+ import { tmpdir, homedir as homedir9 } from "os";
13370
+ import { join as join11, dirname as dirname7 } from "path";
13371
+ import { mkdirSync as mkdirSync9, rmSync, existsSync as existsSync7, copyFileSync as copyFileSync2 } from "fs";
13842
13372
  import { execFileSync as execFileSync2 } from "child_process";
13843
13373
  import { randomUUID as randomUUID2 } from "crypto";
13844
13374
  function resolveNpx() {
13845
- const sibling = join9(dirname6(process.execPath), "npx");
13375
+ const sibling = join11(dirname7(process.execPath), "npx");
13846
13376
  if (existsSync7(sibling)) return sibling;
13847
13377
  return "npx";
13848
13378
  }
@@ -13896,13 +13426,13 @@ async function cmdSkillCategories(run, json) {
13896
13426
  outputLine("");
13897
13427
  }
13898
13428
  async function cmdSkillAdd(name, config, json) {
13899
- const tmpBase = join9(tmpdir(), `okx-skill-${randomUUID2()}`);
13900
- mkdirSync8(tmpBase, { recursive: true });
13429
+ const tmpBase = join11(tmpdir(), `okx-skill-${randomUUID2()}`);
13430
+ mkdirSync9(tmpBase, { recursive: true });
13901
13431
  try {
13902
13432
  outputLine(`Downloading ${name}...`);
13903
13433
  const client = new OkxRestClient(config);
13904
13434
  const zipPath = await downloadSkillZip(client, name, tmpBase);
13905
- const contentDir = await extractSkillZip(zipPath, join9(tmpBase, "content"));
13435
+ const contentDir = await extractSkillZip(zipPath, join11(tmpBase, "content"));
13906
13436
  const meta = readMetaJson(contentDir);
13907
13437
  validateSkillMdExists(contentDir);
13908
13438
  outputLine("Installing to detected agents...");
@@ -13912,7 +13442,7 @@ async function cmdSkillAdd(name, config, json) {
13912
13442
  timeout: 6e4
13913
13443
  });
13914
13444
  } catch (e) {
13915
- const savedZip = join9(process.cwd(), `${name}.zip`);
13445
+ const savedZip = join11(process.cwd(), `${name}.zip`);
13916
13446
  try {
13917
13447
  copyFileSync2(zipPath, savedZip);
13918
13448
  } catch {
@@ -13951,7 +13481,7 @@ function cmdSkillRemove(name, json) {
13951
13481
  timeout: 6e4
13952
13482
  });
13953
13483
  } catch {
13954
- const agentsPath = join9(homedir7(), ".agents", "skills", name);
13484
+ const agentsPath = join11(homedir9(), ".agents", "skills", name);
13955
13485
  try {
13956
13486
  rmSync(agentsPath, { recursive: true, force: true });
13957
13487
  } catch {
@@ -14024,462 +13554,10 @@ function printSkillInstallResult(meta, json) {
14024
13554
  }
14025
13555
  }
14026
13556
 
14027
- // src/commands/event-contract.ts
14028
- function getData8(result) {
14029
- return result.data;
14030
- }
14031
- function fmtMethod(raw) {
14032
- const map = {
14033
- // API returns values in various cases; normalise to lowercase for lookup
14034
- price_above: "Price Above",
14035
- price_up_down: "Up/Down",
14036
- price_once_touch: "One Touch",
14037
- // Uppercase variants (in case API returns them)
14038
- PRICE_ABOVE: "Price Above",
14039
- PRICE_UP_DOWN: "Up/Down",
14040
- PRICE_ONCE_TOUCH: "One Touch"
14041
- };
14042
- return map[String(raw)] ?? String(raw ?? "");
14043
- }
14044
- function fmtFreq(raw) {
14045
- const map = {
14046
- fifteen_min: "15min",
14047
- daily: "Daily",
14048
- hourly: "1h",
14049
- weekly: "Weekly",
14050
- FIFTEEN_MIN: "15min",
14051
- DAILY: "Daily",
14052
- HOURLY: "1h",
14053
- WEEKLY: "Weekly"
14054
- };
14055
- return map[String(raw)] ?? String(raw ?? "");
14056
- }
14057
- function fmtOutcome(raw) {
14058
- const s = String(raw ?? "").toLowerCase();
14059
- if (s === "" || s === "pending") return "";
14060
- return String(raw ?? "");
14061
- }
14062
- function fmtMarketOutcome(instId, outcome) {
14063
- const formatted = fmtOutcome(outcome);
14064
- if (formatted === "") return "";
14065
- return fmtOrderOutcome(instId, formatted);
14066
- }
14067
- function fmtOrderOutcome(instId, outcome) {
14068
- const id = String(instId ?? "").toUpperCase();
14069
- const isUpDown = id.includes("UPDOWN") || id.includes("UP-DOWN");
14070
- const normalized = String(outcome ?? "").toLowerCase();
14071
- if (outcome === "1" || outcome === 1 || normalized === "yes") return isUpDown ? "UP" : "YES";
14072
- if (outcome === "2" || outcome === 2 || normalized === "no") return isUpDown ? "DOWN" : "NO";
14073
- return String(outcome ?? "");
14074
- }
14075
- function fmtPeriodFromInstId(instId) {
14076
- const parts = instId.split("-");
14077
- const upper = instId.toUpperCase();
14078
- const dateIdx = findDateIdx(parts);
14079
- if (dateIdx < 0) return instId;
14080
- const dp = parts[dateIdx];
14081
- const date = `20${dp.slice(0, 2)}-${dp.slice(2, 4)}-${dp.slice(4, 6)}`;
14082
- const fmt = (t) => t.length === 4 ? `${t.slice(0, 2)}:${t.slice(2)}` : t;
14083
- const t1 = parts[dateIdx + 1] ?? "";
14084
- const t2 = parts[dateIdx + 2] ?? "";
14085
- const isUpDown = upper.includes("UPDOWN");
14086
- if (isUpDown && /^\d{4}$/.test(t1) && /^\d{4}$/.test(t2)) {
14087
- return `${date} ${fmt(t1)} ~ ${fmt(t2)} UTC+8`;
14088
- }
14089
- if (/^\d{4}$/.test(t1)) {
14090
- return `${date} ${fmt(t1)} UTC+8`;
14091
- }
14092
- return instId;
14093
- }
14094
- function fmtTs(raw) {
14095
- if (!raw) return "";
14096
- const n = Number(raw);
14097
- if (Number.isNaN(n)) return String(raw);
14098
- return new Date(n).toISOString().replace("T", " ").replace(".000Z", " UTC");
14099
- }
14100
- function fmtProbability(raw) {
14101
- if (raw === "" || raw == null) return "";
14102
- const n = Number(raw);
14103
- if (Number.isNaN(n)) return String(raw);
14104
- return `${(n * 100).toFixed(1)}%`;
14105
- }
14106
- async function cmdEventBrowse(run, opts) {
14107
- const result = await run("event_browse", { underlying: opts.underlying });
14108
- const data = getData8(result);
14109
- if (opts.json) return printJson(data);
14110
- if (!data || data.length === 0) {
14111
- process.stdout.write("No active event contracts found.\n");
14112
- return;
14113
- }
14114
- for (const group of data) {
14115
- const contracts = group["contracts"] ?? [];
14116
- const methodLabel = fmtMethod(group["method"]);
14117
- const freqLabel = fmtFreq(group["freq"]);
14118
- process.stdout.write(`
14119
- [${methodLabel}] ${group["underlying"]} (${freqLabel})
14120
- `);
14121
- printTable(
14122
- contracts.map((c) => ({
14123
- "Contract": formatDisplayTitle(String(c["instId"] ?? "")),
14124
- "Expiry": c["expTime"] ?? "",
14125
- "Target Price": c["floorStrike"] ? String(c["floorStrike"]) : "\u2014",
14126
- "Probability": fmtProbability(c["px"]),
14127
- "Outcome": fmtOutcome(c["outcome"]) || "\u2014",
14128
- "instId": c["instId"]
14129
- }))
14130
- );
14131
- }
14132
- const total = data.reduce((n, g) => n + (g["contracts"]?.length ?? 0), 0);
14133
- process.stdout.write(`
14134
- ${total} active contract(s) across ${data.length} series.
14135
- `);
14136
- }
14137
- var FEATURED_SERIES = /* @__PURE__ */ new Set([
14138
- "BTC-UPDOWN-15MIN",
14139
- "ETH-UPDOWN-15MIN",
14140
- "TRX-UPDOWN-15MIN",
14141
- "BTC-ABOVE-DAILY",
14142
- "ETH-ABOVE-DAILY"
14143
- ]);
14144
- var KNOWN_PREFIXES = /^(BTC|ETH|TRX|SOL|EOS|BNB|XRP|ADA|DOGE|IOTA|SUSHI|KISHU|BTG|XTZ)-/i;
14145
- function getSeriesMethod(s) {
14146
- const settlement = s["settlement"];
14147
- const method = settlement?.["method"] ?? s["method"];
14148
- return String(method ?? "").toLowerCase();
14149
- }
14150
- function isUpDownSeries(s) {
14151
- const m = getSeriesMethod(s);
14152
- return m.includes("up_down") || m.includes("updown");
14153
- }
14154
- function isFeatured(s) {
14155
- return FEATURED_SERIES.has(String(s["seriesId"] ?? ""));
14156
- }
14157
- function isKnownSeries(s) {
14158
- return KNOWN_PREFIXES.test(String(s["seriesId"] ?? ""));
14159
- }
14160
- function classifySeries(all) {
14161
- const featured = [];
14162
- const standard = [];
14163
- const testSeries = [];
14164
- for (const s of all) {
14165
- if (isFeatured(s)) featured.push(s);
14166
- else if (isKnownSeries(s)) standard.push(s);
14167
- else testSeries.push(s);
14168
- }
14169
- return { featured, standard, testSeries };
14170
- }
14171
- async function cmdEventSeries(run, opts) {
14172
- const result = await run("event_get_series", { seriesId: opts.seriesId });
14173
- const data = getData8(result);
14174
- if (opts.json) return printJson(data);
14175
- const toRow = (s, featured2) => {
14176
- const settlement = s["settlement"];
14177
- const method = settlement?.["method"] ?? s["method"];
14178
- const underlying = settlement?.["underlying"] ?? s["underlying"] ?? s["baseCcy"];
14179
- return {
14180
- Series: `${featured2 ? "\u2B50 " : ""}${String(s["seriesId"] ?? "")}`,
14181
- Type: fmtMethod(method),
14182
- Freq: fmtFreq(s["freq"]),
14183
- Underlying: String(underlying ?? ""),
14184
- State: String(s["state"] ?? "")
14185
- };
14186
- };
14187
- const all = data ?? [];
14188
- const { featured, standard, testSeries } = classifySeries(all);
14189
- const mainSeries = [...featured, ...standard];
14190
- const updown = mainSeries.filter(isUpDownSeries);
14191
- const above = mainSeries.filter((s) => !isUpDownSeries(s));
14192
- if (updown.length > 0) {
14193
- process.stdout.write("\n\u2500\u2500 Up/Down \u2500\u2500\n");
14194
- printTable(updown.map((s) => toRow(s, isFeatured(s))));
14195
- }
14196
- if (above.length > 0) {
14197
- process.stdout.write("\n\u2500\u2500 Price Above \u2500\u2500\n");
14198
- printTable(above.map((s) => toRow(s, isFeatured(s))));
14199
- }
14200
- if (testSeries.length > 0) {
14201
- if (opts.all) {
14202
- process.stdout.write("\n\u2500\u2500 Test / Other \u2500\u2500\n");
14203
- printTable(testSeries.map((s) => toRow(s, false)));
14204
- } else {
14205
- process.stdout.write(`
14206
- ${testSeries.length} test series hidden (use --all to show)
14207
- `);
14208
- }
14209
- }
14210
- }
14211
- async function cmdEventEvents(run, opts) {
14212
- const result = await run("event_get_events", {
14213
- seriesId: opts.seriesId,
14214
- state: opts.state,
14215
- limit: opts.limit
14216
- });
14217
- const data = getData8(result);
14218
- if (opts.json) return printJson(data);
14219
- printTable(
14220
- (data ?? []).map((e) => ({
14221
- eventId: e["eventId"],
14222
- state: e["state"],
14223
- expTime: fmtTs(e["expTime"]),
14224
- settleTime: fmtTs(e["settleTime"])
14225
- }))
14226
- );
14227
- }
14228
- function sortByExpiry(data) {
14229
- return [...data].sort((a, b) => {
14230
- const ea = inferExpiryMsFromInstId(String(a["instId"] ?? "")) ?? Infinity;
14231
- const eb = inferExpiryMsFromInstId(String(b["instId"] ?? "")) ?? Infinity;
14232
- return ea - eb;
14233
- });
14234
- }
14235
- async function cmdEventMarkets(run, opts) {
14236
- const result = await run("event_get_markets", {
14237
- seriesId: opts.seriesId,
14238
- eventId: opts.eventId,
14239
- instId: opts.instId,
14240
- state: opts.state,
14241
- limit: opts.limit
14242
- });
14243
- const data = getData8(result);
14244
- if (opts.json) return printJson(data);
14245
- const ext = result;
14246
- const currentIdxPx = ext["currentIdxPx"];
14247
- const underlying = ext["underlying"];
14248
- if (currentIdxPx != null) {
14249
- process.stdout.write(
14250
- `${underlying ?? ""} current index price: $${currentIdxPx}
14251
- `
14252
- );
14253
- }
14254
- const sorted = sortByExpiry(data ?? []);
14255
- printTable(
14256
- sorted.map((m) => {
14257
- const id = String(m["instId"] ?? "");
14258
- const outcome = fmtMarketOutcome(m["instId"], m["outcome"]);
14259
- return {
14260
- contract: formatDisplayTitle(id),
14261
- expTime: m["expTime"] ?? "",
14262
- targetPrice: m["floorStrike"] ?? "",
14263
- probability: fmtProbability(m["px"]),
14264
- outcome: outcome.toLowerCase() === "pending" ? "\u2014" : outcome,
14265
- settleValue: m["settleValue"] ?? "",
14266
- instId: id
14267
- };
14268
- })
14269
- );
14270
- }
14271
- async function cmdEventOrders(run, opts) {
14272
- const result = await run("event_get_orders", {
14273
- instId: opts.instId,
14274
- state: opts.state,
14275
- limit: opts.limit
14276
- });
14277
- const data = getData8(result);
14278
- if (opts.json) return printJson(data);
14279
- printTable(
14280
- (data ?? []).map((o) => ({
14281
- "Contract": formatDisplayTitle(String(o["instId"] ?? "")),
14282
- "Time": fmtTs(o["cTime"]),
14283
- "Direction": `${String(o["side"] ?? "").toUpperCase()} ${fmtOrderOutcome(o["instId"], o["outcome"]).toUpperCase()}`,
14284
- "Price": o["px"],
14285
- "Size": `${o["fillSz"] ?? 0} / ${o["sz"]}`,
14286
- "Status": o["stateLabel"] ?? o["state"],
14287
- "Order number": o["ordId"]
14288
- }))
14289
- );
14290
- }
14291
- async function cmdEventFills(run, opts) {
14292
- const result = await run("event_get_fills", {
14293
- instId: opts.instId,
14294
- limit: opts.limit
14295
- });
14296
- const data = getData8(result);
14297
- if (opts.json) return printJson(data);
14298
- printTable(
14299
- (data ?? []).map((f) => ({
14300
- "Contract": formatDisplayTitle(String(f["instId"] ?? "")),
14301
- "Direction": (() => {
14302
- const side = String(f["side"] ?? "").toUpperCase();
14303
- const outcome = fmtOrderOutcome(f["instId"], f["outcome"]).toUpperCase();
14304
- const dir = `${side} ${outcome}`.trim();
14305
- return dir || "\u2014";
14306
- })(),
14307
- "Fill Price": f["fillPx"],
14308
- "Fill Size": f["fillSz"],
14309
- "Time": fmtTs(f["ts"]),
14310
- "Order number": f["ordId"]
14311
- }))
14312
- );
14313
- }
14314
- async function handleExpiredContractFallback(run, opts) {
14315
- process.stdout.write(
14316
- `Order failed: Contract ${opts.instId} has expired.
14317
- Checking next available contracts in this series...
14318
- `
14319
- );
14320
- const seriesId = extractSeriesId(opts.instId);
14321
- try {
14322
- const mkts = await run("event_get_markets", { seriesId, state: "live" });
14323
- const mData = getData8(mkts) ?? [];
14324
- const active = mData.filter((m) => m["floorStrike"] && m["floorStrike"] !== "");
14325
- if (active.length > 0) {
14326
- printTable(
14327
- active.slice(0, 3).map((m) => ({
14328
- instId: m["instId"],
14329
- expTime: m["expTime"] ?? "",
14330
- targetPrice: m["floorStrike"] ?? ""
14331
- }))
14332
- );
14333
- const next = active[0];
14334
- const nextInstId = String(next["instId"]);
14335
- const pxFlag = opts.px ? ` --px ${opts.px}` : "";
14336
- const ordTypeFlag = opts.ordType ? ` --ordType ${opts.ordType}` : "";
14337
- process.stdout.write(
14338
- `
14339
- To place the same order on the next contract:
14340
- okx event place ${nextInstId} ${opts.side} ${opts.outcome} ${opts.sz}${pxFlag}${ordTypeFlag}
14341
- `
14342
- );
14343
- } else {
14344
- process.stdout.write(`No active contracts found in this series.
14345
- `);
14346
- }
14347
- } catch {
14348
- }
14349
- }
14350
- function handlePlaceOrderError2(msg, instId) {
14351
- if (msg.includes("not found") || msg.includes("51001")) {
14352
- process.stdout.write(`Order failed: Contract ${instId} not found or not yet available.
14353
- `);
14354
- } else {
14355
- process.stdout.write(`Order failed: ${msg}
14356
- `);
14357
- }
14358
- }
14359
- function buildPlaceConfirmation(opts, ordType) {
14360
- const contractName = formatDisplayTitle(opts.instId);
14361
- const direction = `${opts.side.toUpperCase()} ${opts.outcome.toUpperCase()}`;
14362
- if (ordType === "market") {
14363
- return `Placing: ${contractName} ${direction} sz=${opts.sz} (market order, exchange converts to contracts)
14364
- `;
14365
- }
14366
- const px = parseFloat(opts.px ?? "0");
14367
- const sz = parseFloat(opts.sz);
14368
- const cost = (sz * px).toFixed(2);
14369
- const maxGain = (sz * (1 - px)).toFixed(2);
14370
- return `Placing: ${contractName} ${direction} ${opts.sz} contracts at px=${opts.px} (cost \u2248 ${cost}, max gain \u2248 ${maxGain})
14371
- `;
14372
- }
14373
- async function cmdEventPlace(run, opts) {
14374
- const ordType = opts.ordType ?? "market";
14375
- if (ordType === "limit" && !opts.px) {
14376
- process.stderr.write("Error: --px is required for limit orders.\n");
14377
- process.exitCode = 1;
14378
- return;
14379
- }
14380
- if (!opts.json) {
14381
- process.stdout.write(buildPlaceConfirmation(opts, ordType));
14382
- }
14383
- let result;
14384
- try {
14385
- result = await run("event_place_order", {
14386
- instId: opts.instId,
14387
- side: opts.side,
14388
- outcome: opts.outcome,
14389
- sz: opts.sz,
14390
- px: opts.px,
14391
- ordType: opts.ordType
14392
- });
14393
- } catch (err) {
14394
- const msg = err instanceof Error ? err.message : String(err);
14395
- const expiryMs = inferExpiryMsFromInstId(opts.instId);
14396
- const isExpired = expiryMs !== null && expiryMs < Date.now();
14397
- if (isExpired) {
14398
- await handleExpiredContractFallback(run, opts);
14399
- } else {
14400
- handlePlaceOrderError2(msg, opts.instId);
14401
- }
14402
- return;
14403
- }
14404
- const data = getData8(result);
14405
- if (opts.json) return printJson(data);
14406
- const order = data?.[0];
14407
- const stateHint = ordType === "market" ? "market order \u2014 typically fills immediately" : `${ordType} order \u2014 may still be live; verify with: okx event orders --instId ${opts.instId} --state live`;
14408
- const period = fmtPeriodFromInstId(opts.instId);
14409
- const pxPart = opts.px ? ` px: ${opts.px}` : "";
14410
- process.stdout.write(
14411
- `Order submitted: ${order?.["ordId"]}
14412
- Period: ${period}
14413
- ${opts.side.toUpperCase()} ${opts.outcome.toUpperCase()} sz: ${opts.sz}${pxPart} type: ${ordType}
14414
- (${stateHint})
14415
- `
14416
- );
14417
- if (ordType === "market") {
14418
- process.stdout.write(" Note: exchange converts sz (amount) to contracts based on best available price\n");
14419
- }
14420
- }
14421
- async function cmdEventAmend(run, opts) {
14422
- let result;
14423
- try {
14424
- result = await run("event_amend_order", {
14425
- instId: opts.instId,
14426
- ordId: opts.ordId,
14427
- newPx: opts.px,
14428
- newSz: opts.sz
14429
- });
14430
- } catch (err) {
14431
- const msg = err instanceof Error ? err.message : String(err);
14432
- process.stdout.write(`Failed to amend order ${opts.ordId}: ${msg}
14433
- `);
14434
- return;
14435
- }
14436
- const data = getData8(result);
14437
- if (opts.json) return printJson(data);
14438
- const r = data?.[0];
14439
- const pxPart = opts.px ? ` new px: ${opts.px}` : "";
14440
- const szPart = opts.sz ? ` new sz: ${opts.sz}` : "";
14441
- process.stdout.write(
14442
- `Amended: ${r?.["ordId"] ?? opts.ordId}${pxPart}${szPart}
14443
- `
14444
- );
14445
- }
14446
- function handleCancelCatchError(instId, ordId, err) {
14447
- const msg = err instanceof Error ? err.message : String(err);
14448
- const expiryMs = inferExpiryMsFromInstId(instId);
14449
- const isExpired = expiryMs !== null && expiryMs < Date.now();
14450
- if (isExpired) {
14451
- process.stdout.write(
14452
- `Cannot cancel: contract ${instId} has already expired.
14453
- The order was auto-cancelled at settlement \u2014 no action needed.
14454
- `
14455
- );
14456
- } else {
14457
- process.stdout.write(`Failed to cancel order ${ordId}: ${msg}
14458
- `);
14459
- }
14460
- }
14461
- async function cmdEventCancel(run, opts) {
14462
- let result;
14463
- try {
14464
- result = await run("event_cancel_order", {
14465
- instId: opts.instId,
14466
- ordId: opts.ordId
14467
- });
14468
- } catch (err) {
14469
- handleCancelCatchError(opts.instId, opts.ordId, err);
14470
- return;
14471
- }
14472
- const data = getData8(result);
14473
- if (opts.json) return printJson(data);
14474
- const r = data?.[0];
14475
- process.stdout.write(`Cancelled: ${r?.["ordId"] ?? opts.ordId}
14476
- `);
14477
- }
14478
-
14479
13557
  // src/index.ts
14480
13558
  var _require3 = createRequire3(import.meta.url);
14481
13559
  var CLI_VERSION2 = _require3("../package.json").version;
14482
- var GIT_HASH2 = true ? "765bfa2" : "dev";
13560
+ var GIT_HASH2 = true ? "980de10" : "dev";
14483
13561
  function handleConfigCommand(action, rest, json, lang, force) {
14484
13562
  if (action === "init") return cmdConfigInit(lang === "zh" ? "zh" : "en");
14485
13563
  if (action === "show") return cmdConfigShow(json);
@@ -15289,33 +14367,6 @@ function handleSkillCommand(run, action, rest, v, json, config) {
15289
14367
  errorLine("Valid: search, categories, add, download, remove, check, list");
15290
14368
  process.exitCode = 1;
15291
14369
  }
15292
- function handleEventCommand(run, action, rest, v, json) {
15293
- const limit = v.limit !== void 0 ? Number(v.limit) : void 0;
15294
- const handlers = {
15295
- browse: () => cmdEventBrowse(run, { underlying: v.underlying ?? rest[0], json }),
15296
- series: () => cmdEventSeries(run, { seriesId: v.seriesId, all: v.all, json }),
15297
- events: () => cmdEventEvents(run, { seriesId: v.seriesId ?? rest[0], state: v.state, limit, json }),
15298
- markets: () => cmdEventMarkets(run, { seriesId: v.seriesId ?? rest[0], eventId: v.eventId, instId: v.instId, state: v.state, limit, json }),
15299
- place: () => cmdEventPlace(run, {
15300
- instId: v.instId ?? rest[0],
15301
- side: v.side ?? rest[1],
15302
- outcome: v.outcome ?? rest[2],
15303
- sz: v.sz ?? rest[3],
15304
- px: v.px,
15305
- ordType: v.ordType,
15306
- json
15307
- }),
15308
- amend: () => cmdEventAmend(run, { instId: v.instId ?? rest[0], ordId: v.ordId ?? rest[1], px: v.px, sz: v.sz, json }),
15309
- cancel: () => cmdEventCancel(run, { instId: v.instId ?? rest[0], ordId: v.ordId ?? rest[1], json }),
15310
- orders: () => cmdEventOrders(run, { instId: v.instId, state: v.state, limit, json }),
15311
- fills: () => cmdEventFills(run, { instId: v.instId, limit, json })
15312
- };
15313
- const handler = handlers[action];
15314
- if (handler) return handler();
15315
- process.stderr.write(`Unknown event command: ${action}
15316
- `);
15317
- process.exitCode = 1;
15318
- }
15319
14370
  function outputResult(result, json) {
15320
14371
  if (json) {
15321
14372
  outputLine(JSON.stringify(result, null, 2));
@@ -15394,7 +14445,6 @@ async function main() {
15394
14445
  swap: () => handleSwapCommand(run, action, rest, v, json),
15395
14446
  futures: () => handleFuturesCommand(run, action, rest, v, json),
15396
14447
  option: () => handleOptionCommand(run, action, rest, v, json),
15397
- event: () => handleEventCommand(run, action, rest, v, json),
15398
14448
  bot: () => handleBotCommand(run, action, rest, v, json),
15399
14449
  earn: () => handleEarnCommand(run, action, rest, v, json),
15400
14450
  skill: () => handleSkillCommand(run, action, rest, v, json, config)
@@ -15419,7 +14469,6 @@ export {
15419
14469
  handleBotGridCommand,
15420
14470
  handleConfigCommand,
15421
14471
  handleEarnCommand,
15422
- handleEventCommand,
15423
14472
  handleFuturesAlgoCommand,
15424
14473
  handleFuturesCommand,
15425
14474
  handleMarketCommand,