@okx_ai/okx-trade-cli 1.3.1-beta.1 → 1.3.1-beta.3

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,288 @@ 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
+ var DOH_CACHE_PATH = join2(homedir2(), ".okx", "doh-cache.json");
926
+ function readCache(hostname, cachePath = DOH_CACHE_PATH) {
927
+ try {
928
+ const raw = readFileSync(cachePath, "utf-8");
929
+ const file = JSON.parse(raw);
930
+ return file[hostname] ?? null;
931
+ } catch {
932
+ return null;
933
+ }
934
+ }
935
+ function writeCache(hostname, entry, cachePath = DOH_CACHE_PATH) {
936
+ try {
937
+ const dir = dirname(cachePath);
938
+ mkdirSync(dir, { recursive: true });
939
+ let file = {};
940
+ try {
941
+ file = JSON.parse(readFileSync(cachePath, "utf-8"));
942
+ } catch {
943
+ }
944
+ file[hostname] = entry;
945
+ const tmpPath = `${cachePath}.tmp`;
946
+ writeFileSync(tmpPath, JSON.stringify(file));
947
+ renameSync(tmpPath, cachePath);
948
+ } catch {
949
+ }
950
+ }
951
+ var FAILED_NODE_TTL_MS = 60 * 60 * 1e3;
952
+ function classifyAndCache(node, hostname, failedNodes, cachePath) {
953
+ if (!node) {
954
+ return { mode: null, node: null };
955
+ }
956
+ if (node.ip === hostname || node.host === hostname) {
957
+ writeCache(hostname, {
958
+ mode: "direct",
959
+ node: null,
960
+ failedNodes,
961
+ updatedAt: Date.now()
962
+ }, cachePath);
963
+ return { mode: "direct", node: null };
964
+ }
965
+ writeCache(hostname, {
966
+ mode: "proxy",
967
+ node,
968
+ failedNodes,
969
+ updatedAt: Date.now()
970
+ }, cachePath);
971
+ return { mode: "proxy", node };
972
+ }
973
+ function getActiveFailedNodes(nodes) {
974
+ if (!nodes || nodes.length === 0) return [];
975
+ const now = Date.now();
976
+ return nodes.filter((n) => now - n.failedAt < FAILED_NODE_TTL_MS);
977
+ }
978
+ function resolveDoh(hostname, cachePath) {
979
+ const entry = readCache(hostname, cachePath);
980
+ if (entry) {
981
+ if (entry.mode === "direct") {
982
+ return { mode: "direct", node: null };
983
+ }
984
+ if (entry.mode === "proxy" && entry.node) {
985
+ return { mode: "proxy", node: entry.node };
986
+ }
987
+ }
988
+ return { mode: null, node: null };
989
+ }
990
+ async function reResolveDoh(hostname, failedIp, userAgent, cachePath) {
991
+ const entry = readCache(hostname, cachePath);
992
+ const active = getActiveFailedNodes(entry?.failedNodes);
993
+ const now = Date.now();
994
+ const alreadyFailed = failedIp && active.some((n) => n.ip === failedIp);
995
+ const failedNodes = failedIp && !alreadyFailed ? [...active, { ip: failedIp, failedAt: now }] : active;
996
+ const excludeIps = failedNodes.map((n) => n.ip);
997
+ const node = await execDohBinary(hostname, excludeIps, userAgent);
998
+ return classifyAndCache(node, hostname, failedNodes, cachePath);
999
+ }
1000
+ function vlog(message) {
1001
+ process.stderr.write(`[verbose] ${message}
1002
+ `);
1003
+ }
1004
+ var DohManager = class {
1005
+ opts;
1006
+ // DoH proxy state (lazy-resolved on first request)
1007
+ dohResolved = false;
1008
+ dohRetried = false;
1009
+ directUnverified = false;
1010
+ // The first direct connection has not yet been verified
1011
+ dohNode = null;
1012
+ dohAgent = null;
1013
+ dohBaseUrl = null;
1014
+ constructor(opts) {
1015
+ this.opts = opts;
1016
+ }
1017
+ /**
1018
+ * Lazily resolve the DoH proxy node on the first request.
1019
+ * Uses cache-first strategy via the resolver.
1020
+ */
1021
+ prepareDoh() {
1022
+ if (this.dohResolved || this.opts.hasCustomProxy) return;
1023
+ this.dohResolved = true;
1024
+ try {
1025
+ const { hostname, protocol } = new URL(this.opts.baseUrl);
1026
+ const result = resolveDoh(hostname);
1027
+ if (!result.mode) {
1028
+ this.directUnverified = true;
1029
+ if (this.opts.verbose) {
1030
+ vlog("DoH: no cache, trying direct connection first");
1031
+ }
1032
+ return;
1033
+ }
1034
+ if (result.mode === "direct") {
1035
+ if (this.opts.verbose) {
1036
+ vlog("DoH: mode=direct (overseas or cached), using direct connection");
1037
+ }
1038
+ return;
1039
+ }
1040
+ if (result.node) {
1041
+ this.applyNode(result.node, protocol);
1042
+ }
1043
+ } catch (err) {
1044
+ if (this.opts.verbose) {
1045
+ const cause = err instanceof Error ? err.message : String(err);
1046
+ vlog(`DoH resolution failed, falling back to direct: ${cause}`);
1047
+ }
1048
+ }
1049
+ }
1050
+ /** Get connection parameters for the current request. */
1051
+ getConnectionParams() {
1052
+ const baseUrl = this.dohNode ? this.dohBaseUrl : this.opts.baseUrl;
1053
+ const result = { baseUrl };
1054
+ if (this.dohAgent) {
1055
+ result.dispatcher = this.dohAgent;
1056
+ }
1057
+ if (this.dohNode) {
1058
+ result.userAgent = this.dohUserAgent;
1059
+ }
1060
+ return result;
1061
+ }
1062
+ /** Whether a DoH proxy node is currently active. */
1063
+ get isProxyActive() {
1064
+ return this.dohNode !== null;
1065
+ }
1066
+ /** Whether we have already retried after network failure. */
1067
+ get hasRetried() {
1068
+ return this.dohRetried;
1069
+ }
1070
+ /**
1071
+ * Handle network failure: re-resolve with --exclude and retry once.
1072
+ * Returns true if retry should proceed, false if already retried.
1073
+ */
1074
+ async handleNetworkFailure() {
1075
+ if (this.dohRetried) return false;
1076
+ this.dohRetried = true;
1077
+ const failedIp = this.dohNode?.ip ?? "";
1078
+ const { hostname, protocol } = new URL(this.opts.baseUrl);
1079
+ this.dohNode = null;
1080
+ this.dohAgent = null;
1081
+ this.dohBaseUrl = null;
1082
+ if (!failedIp) this.directUnverified = false;
1083
+ if (this.opts.verbose) {
1084
+ vlog(failedIp ? `DoH: proxy node ${failedIp} failed, re-resolving with --exclude` : "DoH: direct connection failed, calling binary for DoH resolution");
1085
+ }
1086
+ try {
1087
+ const result = await reResolveDoh(hostname, failedIp, this.dohUserAgent);
1088
+ if (result.mode === "proxy" && result.node) {
1089
+ this.applyNode(result.node, protocol);
1090
+ this.dohRetried = false;
1091
+ return true;
1092
+ }
1093
+ } catch {
1094
+ }
1095
+ if (this.opts.verbose) {
1096
+ vlog("DoH: re-resolution failed or switched to direct, retrying with direct connection");
1097
+ }
1098
+ return true;
1099
+ }
1100
+ /**
1101
+ * After a successful HTTP response on direct connection, cache mode=direct.
1102
+ * (Even if the business response is an error, the network path is valid.)
1103
+ */
1104
+ cacheDirectIfNeeded() {
1105
+ if (!this.directUnverified || this.dohNode) return;
1106
+ this.directUnverified = false;
1107
+ const { hostname } = new URL(this.opts.baseUrl);
1108
+ writeCache(hostname, {
1109
+ mode: "direct",
1110
+ node: null,
1111
+ failedNodes: [],
1112
+ updatedAt: Date.now()
1113
+ });
1114
+ if (this.opts.verbose) {
1115
+ vlog("DoH: direct connection succeeded, cached mode=direct");
1116
+ }
1117
+ }
1118
+ /** User-Agent for DoH proxy requests: OKX/@okx_ai/{packageName}/{version} */
1119
+ get dohUserAgent() {
1120
+ return `OKX/@okx_ai/${this.opts.packageUserAgent ?? "unknown"}`;
1121
+ }
1122
+ /**
1123
+ * Apply a DoH node: set up the custom Agent + base URL.
1124
+ *
1125
+ * node.ip may be a real IP or a domain (CNAME like *.aliyunddos1021.com).
1126
+ * - Real IP → use directly in lookup callback
1127
+ * - Domain → dns.lookup on every connection to get a fresh IP
1128
+ */
1129
+ applyNode(node, protocol) {
1130
+ this.dohNode = node;
1131
+ this.dohBaseUrl = `${protocol}//${node.host}`;
1132
+ const nodeIpIsRealIp = !!isIP(node.ip);
1133
+ this.dohAgent = new Agent({
1134
+ connect: {
1135
+ lookup: (_hostname, options, callback) => {
1136
+ if (nodeIpIsRealIp) {
1137
+ if (options?.all) {
1138
+ callback(null, [{ address: node.ip, family: 4 }]);
1139
+ } else {
1140
+ callback(null, node.ip, 4);
1141
+ }
1142
+ } else {
1143
+ dnsLookup(node.ip, { family: 4 }, (err, address, family) => {
1144
+ if (err) {
1145
+ callback(err, "", 0);
1146
+ } else if (options?.all) {
1147
+ callback(null, [{ address, family }]);
1148
+ } else {
1149
+ callback(null, address, family);
1150
+ }
1151
+ });
1152
+ }
1153
+ }
1154
+ }
1155
+ });
1156
+ if (this.opts.verbose) {
1157
+ vlog(`DoH proxy active: \u2192 ${node.host} (${node.ip}), ttl=${node.ttl}s`);
1158
+ }
1159
+ }
1160
+ };
864
1161
  function getNow() {
865
1162
  return (/* @__PURE__ */ new Date()).toISOString();
866
1163
  }
@@ -1071,7 +1368,7 @@ function maskKey(key) {
1071
1368
  if (key.length <= 8) return "***";
1072
1369
  return `${key.slice(0, 3)}***${key.slice(-3)}`;
1073
1370
  }
1074
- function vlog(message) {
1371
+ function vlog2(message) {
1075
1372
  process.stderr.write(`[verbose] ${message}
1076
1373
  `);
1077
1374
  }
@@ -1079,25 +1376,32 @@ var OkxRestClient = class _OkxRestClient {
1079
1376
  config;
1080
1377
  rateLimiter;
1081
1378
  dispatcher;
1379
+ doh;
1082
1380
  constructor(config) {
1083
1381
  this.config = config;
1084
1382
  this.rateLimiter = new RateLimiter(3e4, config.verbose);
1085
1383
  if (config.proxyUrl) {
1086
1384
  this.dispatcher = new ProxyAgent(config.proxyUrl);
1087
1385
  }
1386
+ this.doh = new DohManager({
1387
+ baseUrl: config.baseUrl,
1388
+ packageUserAgent: config.userAgent,
1389
+ verbose: config.verbose,
1390
+ hasCustomProxy: !!config.proxyUrl
1391
+ });
1088
1392
  }
1089
1393
  logRequest(method, url, auth) {
1090
1394
  if (!this.config.verbose) return;
1091
- vlog(`\u2192 ${method} ${url}`);
1395
+ vlog2(`\u2192 ${method} ${url}`);
1092
1396
  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`);
1397
+ vlog2(` ${authInfo} demo=${this.config.demo} timeout=${this.config.timeoutMs}ms`);
1094
1398
  }
1095
1399
  logResponse(status, rawLen, elapsed, traceId, code, msg) {
1096
1400
  if (!this.config.verbose) return;
1097
1401
  if (code && code !== "0" && code !== "1") {
1098
- vlog(`\u2717 ${status} | code=${code} | msg=${msg ?? "-"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1402
+ vlog2(`\u2717 ${status} | code=${code} | msg=${msg ?? "-"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1099
1403
  } else {
1100
- vlog(`\u2190 ${status} | code=${code ?? "0"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1404
+ vlog2(`\u2190 ${status} | code=${code ?? "0"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1101
1405
  }
1102
1406
  }
1103
1407
  async publicGet(path42, query, rateLimit, simulatedTrading) {
@@ -1256,13 +1560,18 @@ var OkxRestClient = class _OkxRestClient {
1256
1560
  * Security: validates Content-Type and enforces maxBytes limit.
1257
1561
  */
1258
1562
  async privatePostBinary(path42, body, opts) {
1563
+ this.doh.prepareDoh();
1259
1564
  const maxBytes = opts?.maxBytes ?? _OkxRestClient.DEFAULT_MAX_BYTES;
1260
1565
  const expectedCT = opts?.expectedContentType ?? "application/octet-stream";
1261
1566
  const bodyJson = body ? JSON.stringify(body) : "";
1262
1567
  const endpoint = `POST ${path42}`;
1263
- this.logRequest("POST", `${this.config.baseUrl}${path42}`, "private");
1568
+ const conn = this.doh.getConnectionParams();
1569
+ this.logRequest("POST", `${conn.baseUrl}${path42}`, "private");
1264
1570
  const reqConfig = { method: "POST", path: path42, auth: "private" };
1265
1571
  const headers = this.buildHeaders(reqConfig, path42, bodyJson, getNow());
1572
+ if (conn.userAgent) {
1573
+ headers.set("User-Agent", conn.userAgent);
1574
+ }
1266
1575
  const t0 = Date.now();
1267
1576
  const response = await this.fetchBinary(path42, endpoint, headers, bodyJson, t0);
1268
1577
  const elapsed = Date.now() - t0;
@@ -1285,24 +1594,25 @@ var OkxRestClient = class _OkxRestClient {
1285
1594
  throw new OkxApiError(`Response size ${buffer.length} bytes exceeds limit of ${maxBytes} bytes.`, { code: "RESPONSE_TOO_LARGE", endpoint, traceId });
1286
1595
  }
1287
1596
  if (this.config.verbose) {
1288
- vlog(`\u2190 ${response.status} | binary ${buffer.length}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1597
+ vlog2(`\u2190 ${response.status} | binary ${buffer.length}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1289
1598
  }
1290
1599
  return { endpoint, requestTime: (/* @__PURE__ */ new Date()).toISOString(), data: buffer, contentType: ct, contentLength: buffer.length, traceId };
1291
1600
  }
1292
1601
  /** Execute fetch for binary endpoint, wrapping network errors. */
1293
1602
  async fetchBinary(path42, endpoint, headers, bodyJson, t0) {
1603
+ const conn = this.doh.getConnectionParams();
1294
1604
  try {
1295
1605
  const fetchOptions = {
1296
1606
  method: "POST",
1297
1607
  headers,
1298
1608
  body: bodyJson || void 0,
1299
- signal: AbortSignal.timeout(this.config.timeoutMs)
1609
+ signal: AbortSignal.timeout(this.config.timeoutMs),
1610
+ dispatcher: this.dispatcher ?? conn.dispatcher
1300
1611
  };
1301
- if (this.dispatcher) fetchOptions.dispatcher = this.dispatcher;
1302
- return await fetch(`${this.config.baseUrl}${path42}`, fetchOptions);
1612
+ return await fetch(`${conn.baseUrl}${path42}`, fetchOptions);
1303
1613
  } catch (error) {
1304
1614
  if (this.config.verbose) {
1305
- vlog(`\u2717 NetworkError after ${Date.now() - t0}ms: ${error instanceof Error ? error.message : String(error)}`);
1615
+ vlog2(`\u2717 NetworkError after ${Date.now() - t0}ms: ${error instanceof Error ? error.message : String(error)}`);
1306
1616
  }
1307
1617
  throw new NetworkError(`Failed to call OKX endpoint ${endpoint}.`, endpoint, error);
1308
1618
  }
@@ -1330,10 +1640,38 @@ var OkxRestClient = class _OkxRestClient {
1330
1640
  // ---------------------------------------------------------------------------
1331
1641
  // JSON request
1332
1642
  // ---------------------------------------------------------------------------
1643
+ /**
1644
+ * Handle network error during a JSON request: refresh DoH and maybe retry.
1645
+ * Always either returns a retry result or throws NetworkError.
1646
+ */
1647
+ async handleRequestNetworkError(error, reqConfig, requestPath, t0) {
1648
+ if (!this.doh.hasRetried) {
1649
+ if (this.config.verbose) {
1650
+ const cause = error instanceof Error ? error.message : String(error);
1651
+ vlog2(`Network failure, refreshing DoH: ${cause}`);
1652
+ }
1653
+ const shouldRetry = await this.doh.handleNetworkFailure();
1654
+ if (shouldRetry && reqConfig.method === "GET") {
1655
+ return this.request(reqConfig);
1656
+ }
1657
+ }
1658
+ if (this.config.verbose) {
1659
+ const elapsed = Date.now() - t0;
1660
+ const cause = error instanceof Error ? error.message : String(error);
1661
+ vlog2(`\u2717 NetworkError after ${elapsed}ms: ${cause}`);
1662
+ }
1663
+ throw new NetworkError(
1664
+ `Failed to call OKX endpoint ${reqConfig.method} ${requestPath}.`,
1665
+ `${reqConfig.method} ${requestPath}`,
1666
+ error
1667
+ );
1668
+ }
1333
1669
  async request(reqConfig) {
1670
+ this.doh.prepareDoh();
1334
1671
  const queryString = buildQueryString(reqConfig.query);
1335
1672
  const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
1336
- const url = `${this.config.baseUrl}${requestPath}`;
1673
+ const conn = this.doh.getConnectionParams();
1674
+ const url = `${conn.baseUrl}${requestPath}`;
1337
1675
  const bodyJson = reqConfig.body ? JSON.stringify(reqConfig.body) : "";
1338
1676
  const timestamp = getNow();
1339
1677
  this.logRequest(reqConfig.method, url, reqConfig.auth);
@@ -1341,6 +1679,9 @@ var OkxRestClient = class _OkxRestClient {
1341
1679
  await this.rateLimiter.consume(reqConfig.rateLimit);
1342
1680
  }
1343
1681
  const headers = this.buildHeaders(reqConfig, requestPath, bodyJson, timestamp);
1682
+ if (conn.userAgent) {
1683
+ headers.set("User-Agent", conn.userAgent);
1684
+ }
1344
1685
  const t0 = Date.now();
1345
1686
  let response;
1346
1687
  try {
@@ -1348,27 +1689,17 @@ var OkxRestClient = class _OkxRestClient {
1348
1689
  method: reqConfig.method,
1349
1690
  headers,
1350
1691
  body: reqConfig.method === "POST" ? bodyJson : void 0,
1351
- signal: AbortSignal.timeout(this.config.timeoutMs)
1692
+ signal: AbortSignal.timeout(this.config.timeoutMs),
1693
+ dispatcher: this.dispatcher ?? conn.dispatcher
1352
1694
  };
1353
- if (this.dispatcher) {
1354
- fetchOptions.dispatcher = this.dispatcher;
1355
- }
1356
1695
  response = await fetch(url, fetchOptions);
1357
1696
  } 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
- );
1697
+ return await this.handleRequestNetworkError(error, reqConfig, requestPath, t0);
1368
1698
  }
1369
1699
  const rawText = await response.text();
1370
1700
  const elapsed = Date.now() - t0;
1371
1701
  const traceId = extractTraceId(response.headers);
1702
+ this.doh.cacheDirectIfNeeded();
1372
1703
  return this.processResponse(rawText, response, elapsed, traceId, reqConfig, requestPath);
1373
1704
  }
1374
1705
  };
@@ -1393,9 +1724,6 @@ function readNumber(args, key) {
1393
1724
  if (value === void 0 || value === null) {
1394
1725
  return void 0;
1395
1726
  }
1396
- if (typeof value === "string" && /^-?\d+(\.\d+)?$/.test(value)) {
1397
- return parseFloat(value);
1398
- }
1399
1727
  if (typeof value !== "number" || Number.isNaN(value)) {
1400
1728
  throw new ValidationError(`Parameter "${key}" must be a number.`);
1401
1729
  }
@@ -1475,8 +1803,7 @@ var OKX_INST_TYPES = [
1475
1803
  "SWAP",
1476
1804
  "FUTURES",
1477
1805
  "OPTION",
1478
- "MARGIN",
1479
- "EVENTS"
1806
+ "MARGIN"
1480
1807
  ];
1481
1808
  function publicRateLimit(key, rps = 20) {
1482
1809
  return {
@@ -1774,7 +2101,6 @@ var MODULES = [
1774
2101
  "futures",
1775
2102
  "option",
1776
2103
  "account",
1777
- "event",
1778
2104
  ...EARN_SUB_MODULE_IDS,
1779
2105
  ...BOT_SUB_MODULE_IDS,
1780
2106
  "skills"
@@ -1968,7 +2294,7 @@ function registerAccountTools() {
1968
2294
  properties: {
1969
2295
  instType: {
1970
2296
  type: "string",
1971
- enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION", "EVENTS"]
2297
+ enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION"]
1972
2298
  },
1973
2299
  ccy: {
1974
2300
  type: "string",
@@ -2034,7 +2360,7 @@ function registerAccountTools() {
2034
2360
  properties: {
2035
2361
  instType: {
2036
2362
  type: "string",
2037
- enum: ["SWAP", "FUTURES", "MARGIN", "OPTION", "EVENTS"],
2363
+ enum: ["SWAP", "FUTURES", "MARGIN", "OPTION"],
2038
2364
  description: "Default SWAP"
2039
2365
  },
2040
2366
  instId: {
@@ -2095,7 +2421,7 @@ function registerAccountTools() {
2095
2421
  properties: {
2096
2422
  instType: {
2097
2423
  type: "string",
2098
- enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION", "EVENTS"]
2424
+ enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION"]
2099
2425
  },
2100
2426
  instId: {
2101
2427
  type: "string",
@@ -2206,14 +2532,14 @@ function registerAccountTools() {
2206
2532
  {
2207
2533
  name: "account_get_positions",
2208
2534
  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.",
2535
+ description: "Get current open positions across all instrument types (MARGIN, SWAP, FUTURES, OPTION). Use swap_get_positions for SWAP/FUTURES-only queries.",
2210
2536
  isWrite: false,
2211
2537
  inputSchema: {
2212
2538
  type: "object",
2213
2539
  properties: {
2214
2540
  instType: {
2215
2541
  type: "string",
2216
- enum: ["MARGIN", "SWAP", "FUTURES", "OPTION", "EVENTS"]
2542
+ enum: ["MARGIN", "SWAP", "FUTURES", "OPTION"]
2217
2543
  },
2218
2544
  instId: {
2219
2545
  type: "string",
@@ -3252,19 +3578,19 @@ function safeWriteFile(targetDir, fileName, data) {
3252
3578
  throw new Error(`Invalid file name: "${fileName}"`);
3253
3579
  }
3254
3580
  const resolvedDir = resolve(targetDir);
3255
- const filePath = join(resolvedDir, safeName);
3581
+ const filePath = join3(resolvedDir, safeName);
3256
3582
  const resolvedPath = resolve(filePath);
3257
3583
  if (!resolvedPath.startsWith(resolvedDir + sep)) {
3258
3584
  throw new Error(`Path traversal detected: "${fileName}" resolves outside target directory`);
3259
3585
  }
3260
- mkdirSync(resolvedDir, { recursive: true });
3586
+ mkdirSync2(resolvedDir, { recursive: true });
3261
3587
  const tmpPath = `${resolvedPath}.${randomUUID()}.tmp`;
3262
3588
  try {
3263
- writeFileSync(tmpPath, data);
3264
- renameSync(tmpPath, resolvedPath);
3589
+ writeFileSync2(tmpPath, data);
3590
+ renameSync2(tmpPath, resolvedPath);
3265
3591
  } catch (err) {
3266
3592
  try {
3267
- unlinkSync(tmpPath);
3593
+ unlinkSync2(tmpPath);
3268
3594
  } catch {
3269
3595
  }
3270
3596
  throw err;
@@ -3330,7 +3656,7 @@ async function extractSkillZip(zipPath, targetDir, limits) {
3330
3656
  const maxFiles = limits?.maxFiles ?? DEFAULT_MAX_FILES;
3331
3657
  const maxCompressionRatio = limits?.maxCompressionRatio ?? DEFAULT_MAX_COMPRESSION_RATIO;
3332
3658
  const resolvedTarget = resolve2(targetDir);
3333
- mkdirSync2(resolvedTarget, { recursive: true });
3659
+ mkdirSync3(resolvedTarget, { recursive: true });
3334
3660
  return new Promise((resolvePromise, reject) => {
3335
3661
  yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
3336
3662
  if (err) return reject(err);
@@ -3353,7 +3679,7 @@ async function extractSkillZip(zipPath, targetDir, limits) {
3353
3679
  zipfile.close();
3354
3680
  return reject(streamErr);
3355
3681
  }
3356
- mkdirSync2(dirname(resolvedPath), { recursive: true });
3682
+ mkdirSync3(dirname2(resolvedPath), { recursive: true });
3357
3683
  const writeStream = createWriteStream(resolvedPath);
3358
3684
  readStream.pipe(writeStream);
3359
3685
  writeStream.on("close", () => zipfile.readEntry());
@@ -3369,11 +3695,11 @@ async function extractSkillZip(zipPath, targetDir, limits) {
3369
3695
  });
3370
3696
  }
3371
3697
  function readMetaJson(contentDir) {
3372
- const metaPath = join2(contentDir, "_meta.json");
3698
+ const metaPath = join4(contentDir, "_meta.json");
3373
3699
  if (!existsSync(metaPath)) {
3374
3700
  throw new Error(`_meta.json not found in ${contentDir}. Invalid skill package.`);
3375
3701
  }
3376
- const raw = readFileSync(metaPath, "utf-8");
3702
+ const raw = readFileSync2(metaPath, "utf-8");
3377
3703
  let parsed;
3378
3704
  try {
3379
3705
  parsed = JSON.parse(raw);
@@ -3395,26 +3721,26 @@ function readMetaJson(contentDir) {
3395
3721
  };
3396
3722
  }
3397
3723
  function validateSkillMdExists(contentDir) {
3398
- const skillMdPath = join2(contentDir, "SKILL.md");
3724
+ const skillMdPath = join4(contentDir, "SKILL.md");
3399
3725
  if (!existsSync(skillMdPath)) {
3400
3726
  throw new Error(`SKILL.md not found in ${contentDir}. Invalid skill package.`);
3401
3727
  }
3402
3728
  }
3403
- var DEFAULT_REGISTRY_PATH = join3(homedir(), ".okx", "skills", "registry.json");
3729
+ var DEFAULT_REGISTRY_PATH = join5(homedir3(), ".okx", "skills", "registry.json");
3404
3730
  function readRegistry(registryPath = DEFAULT_REGISTRY_PATH) {
3405
3731
  if (!existsSync2(registryPath)) {
3406
3732
  return { version: 1, skills: {} };
3407
3733
  }
3408
3734
  try {
3409
- const raw = readFileSync2(registryPath, "utf-8");
3735
+ const raw = readFileSync3(registryPath, "utf-8");
3410
3736
  return JSON.parse(raw);
3411
3737
  } catch {
3412
3738
  return { version: 1, skills: {} };
3413
3739
  }
3414
3740
  }
3415
3741
  function writeRegistry(registry, registryPath = DEFAULT_REGISTRY_PATH) {
3416
- mkdirSync3(dirname2(registryPath), { recursive: true });
3417
- writeFileSync2(registryPath, JSON.stringify(registry, null, 2) + "\n", "utf-8");
3742
+ mkdirSync4(dirname3(registryPath), { recursive: true });
3743
+ writeFileSync3(registryPath, JSON.stringify(registry, null, 2) + "\n", "utf-8");
3418
3744
  }
3419
3745
  function upsertSkillRecord(meta, registryPath = DEFAULT_REGISTRY_PATH) {
3420
3746
  const registry = readRegistry(registryPath);
@@ -5049,838 +5375,79 @@ function registerDcdTools() {
5049
5375
  termRate: newQuote["termRate"]
5050
5376
  }
5051
5377
  };
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.publicGet(
5347
- "/api/v5/public/event-contract/markets",
5348
- compactObject({ seriesId, state: "live" }),
5349
- publicRateLimit("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.publicGet(
5476
- "/api/v5/public/event-contract/series",
5477
- compactObject({}),
5478
- publicRateLimit("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.publicGet(
5510
- "/api/v5/public/event-contract/series",
5511
- compactObject({ seriesId: readString(args, "seriesId") }),
5512
- publicRateLimit("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.publicGet(
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
- publicRateLimit("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.publicGet(
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
- publicRateLimit("event_get_markets", 20)
5628
- ),
5629
- knownUnderlying ? Promise.resolve(null) : context.client.publicGet(
5630
- "/api/v5/public/event-contract/series",
5631
- compactObject({ seriesId }),
5632
- publicRateLimit("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."
5378
+ }
5379
+ throw error;
5770
5380
  }
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);
5381
+ });
5841
5382
  }
5842
- },
5383
+ }
5384
+ ];
5385
+ }
5386
+ function registerAutoEarnTools() {
5387
+ return [
5843
5388
  {
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.",
5389
+ name: "earn_auto_set",
5390
+ module: "earn.autoearn",
5391
+ 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
5392
  isWrite: true,
5848
5393
  inputSchema: {
5849
5394
  type: "object",
5850
5395
  properties: {
5851
- instId: {
5396
+ ccy: {
5852
5397
  type: "string",
5853
- description: "Event contract instrument ID"
5398
+ description: "Currency, e.g. SOL, USDG"
5854
5399
  },
5855
- ordId: {
5400
+ action: {
5401
+ type: "string",
5402
+ description: "turn_on or turn_off"
5403
+ },
5404
+ earnType: {
5856
5405
  type: "string",
5857
- description: "Order ID to cancel"
5406
+ description: "0=auto-lend+stake (default), 1=USDG earn. Omit to use default."
5858
5407
  }
5859
5408
  },
5860
- required: ["instId", "ordId"]
5409
+ required: ["ccy", "action"]
5861
5410
  },
5862
5411
  handler: async (rawArgs, context) => {
5863
5412
  const args = asRecord(rawArgs);
5864
- const instId = requireString(args, "instId");
5865
5413
  const response = await context.client.privatePost(
5866
- "/api/v5/trade/cancel-order",
5867
- { instId, ordId: requireString(args, "ordId") },
5868
- privateRateLimit("event_cancel_order", 60)
5414
+ "/api/v5/account/set-auto-earn",
5415
+ compactObject({
5416
+ ccy: requireString(args, "ccy"),
5417
+ action: requireString(args, "action"),
5418
+ earnType: readString(args, "earnType") ?? "0"
5419
+ }),
5420
+ privateRateLimit("earn_auto_set", 10)
5869
5421
  );
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);
5422
+ return normalizeResponse(response);
5423
+ }
5424
+ }
5425
+ ];
5426
+ }
5427
+ var EARN_DEMO_MESSAGE = "Earn features (savings, DCD, on-chain staking, auto-earn) are not available in simulated trading mode.";
5428
+ var EARN_DEMO_SUGGESTION = "Switch to a live account to use Earn features.";
5429
+ var DEMO_GUARD_SKIP = /* @__PURE__ */ new Set(["dcd_redeem", "earn_fixed_purchase"]);
5430
+ function withDemoGuard(tool) {
5431
+ if (!tool.isWrite || DEMO_GUARD_SKIP.has(tool.name)) return tool;
5432
+ const originalHandler = tool.handler;
5433
+ return {
5434
+ ...tool,
5435
+ handler: async (args, context) => {
5436
+ if (context.config.demo) {
5437
+ throw new ConfigError(EARN_DEMO_MESSAGE, EARN_DEMO_SUGGESTION);
5881
5438
  }
5439
+ return originalHandler(args, context);
5882
5440
  }
5441
+ };
5442
+ }
5443
+ function registerAllEarnTools() {
5444
+ const tools = [
5445
+ ...registerEarnTools(),
5446
+ ...registerOnchainEarnTools(),
5447
+ ...registerDcdTools(),
5448
+ ...registerAutoEarnTools()
5883
5449
  ];
5450
+ return tools.map(withDemoGuard);
5884
5451
  }
5885
5452
  function buildContractTradeTools(cfg) {
5886
5453
  const { prefix, module, label, instTypes, instIdExample } = cfg;
@@ -8520,7 +8087,6 @@ function allToolSpecs() {
8520
8087
  ...registerOptionAlgoTools(),
8521
8088
  ...registerAlgoTradeTools(),
8522
8089
  ...registerAccountTools(),
8523
- ...registerEventContractTools(),
8524
8090
  ...registerBotTools(),
8525
8091
  ...registerAllEarnTools(),
8526
8092
  ...registerAuditTools(),
@@ -8539,12 +8105,12 @@ function createToolRunner(client, config) {
8539
8105
  };
8540
8106
  }
8541
8107
  function configFilePath() {
8542
- return join4(homedir2(), ".okx", "config.toml");
8108
+ return join6(homedir4(), ".okx", "config.toml");
8543
8109
  }
8544
8110
  function readFullConfig() {
8545
8111
  const path42 = configFilePath();
8546
8112
  if (!existsSync3(path42)) return { profiles: {} };
8547
- const raw = readFileSync3(path42, "utf-8");
8113
+ const raw = readFileSync4(path42, "utf-8");
8548
8114
  try {
8549
8115
  return parse(raw);
8550
8116
  } catch (err) {
@@ -8572,11 +8138,11 @@ var CONFIG_HEADER = `# OKX Trade Kit Configuration
8572
8138
  `;
8573
8139
  function writeFullConfig(config) {
8574
8140
  const path42 = configFilePath();
8575
- const dir = dirname3(path42);
8141
+ const dir = dirname4(path42);
8576
8142
  if (!existsSync3(dir)) {
8577
- mkdirSync4(dir, { recursive: true });
8143
+ mkdirSync5(dir, { recursive: true });
8578
8144
  }
8579
- writeFileSync3(path42, CONFIG_HEADER + stringify(config), "utf-8");
8145
+ writeFileSync4(path42, CONFIG_HEADER + stringify(config), "utf-8");
8580
8146
  }
8581
8147
  function expandShorthand(moduleId) {
8582
8148
  if (moduleId === "all") return [...MODULES];
@@ -8690,21 +8256,21 @@ function loadConfig(cli) {
8690
8256
  verbose: cli.verbose ?? false
8691
8257
  };
8692
8258
  }
8693
- var CACHE_FILE = join5(homedir3(), ".okx", "update-check.json");
8259
+ var CACHE_FILE = join7(homedir5(), ".okx", "update-check.json");
8694
8260
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
8695
- function readCache() {
8261
+ function readCache2() {
8696
8262
  try {
8697
8263
  if (existsSync4(CACHE_FILE)) {
8698
- return JSON.parse(readFileSync4(CACHE_FILE, "utf-8"));
8264
+ return JSON.parse(readFileSync5(CACHE_FILE, "utf-8"));
8699
8265
  }
8700
8266
  } catch {
8701
8267
  }
8702
8268
  return {};
8703
8269
  }
8704
- function writeCache(cache) {
8270
+ function writeCache2(cache) {
8705
8271
  try {
8706
- mkdirSync5(join5(homedir3(), ".okx"), { recursive: true });
8707
- writeFileSync4(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
8272
+ mkdirSync6(join7(homedir5(), ".okx"), { recursive: true });
8273
+ writeFileSync5(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
8708
8274
  } catch {
8709
8275
  }
8710
8276
  }
@@ -8751,14 +8317,14 @@ async function fetchLatestVersion(packageName) {
8751
8317
  function refreshCacheInBackground(packageName) {
8752
8318
  fetchLatestVersion(packageName).then((latest) => {
8753
8319
  if (!latest) return;
8754
- const cache = readCache();
8320
+ const cache = readCache2();
8755
8321
  cache[packageName] = { latestVersion: latest, checkedAt: Date.now() };
8756
- writeCache(cache);
8322
+ writeCache2(cache);
8757
8323
  }).catch(() => {
8758
8324
  });
8759
8325
  }
8760
8326
  function checkForUpdates(packageName, currentVersion) {
8761
- const cache = readCache();
8327
+ const cache = readCache2();
8762
8328
  const entry = cache[packageName];
8763
8329
  if (entry && isNewerVersion(currentVersion, entry.latestVersion)) {
8764
8330
  process.stderr.write(
@@ -9613,7 +9179,7 @@ async function cmdDiagnoseMcp(options = {}) {
9613
9179
 
9614
9180
  // src/commands/diagnose.ts
9615
9181
  var CLI_VERSION = readCliVersion();
9616
- var GIT_HASH = true ? "244dce7" : "dev";
9182
+ var GIT_HASH = true ? "8ae72a0" : "dev";
9617
9183
  function maskKey2(key) {
9618
9184
  if (!key) return "(not set)";
9619
9185
  if (key.length <= 8) return "****";
@@ -9910,24 +9476,24 @@ async function runCliChecks(config, profile, outputPath) {
9910
9476
 
9911
9477
  // src/commands/upgrade.ts
9912
9478
  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";
9479
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8 } from "fs";
9480
+ import { dirname as dirname6, join as join9 } from "path";
9481
+ import { homedir as homedir7 } from "os";
9916
9482
  var PACKAGES = ["@okx_ai/okx-trade-mcp", "@okx_ai/okx-trade-cli"];
9917
- var CACHE_FILE2 = join7(homedir5(), ".okx", "last_check");
9483
+ var CACHE_FILE2 = join9(homedir7(), ".okx", "last_check");
9918
9484
  var THROTTLE_MS = 12 * 60 * 60 * 1e3;
9919
- var NPM_BIN = join7(dirname5(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
9485
+ var NPM_BIN = join9(dirname6(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
9920
9486
  function readLastCheck() {
9921
9487
  try {
9922
- return parseInt(readFileSync6(CACHE_FILE2, "utf-8").trim(), 10) || 0;
9488
+ return parseInt(readFileSync7(CACHE_FILE2, "utf-8").trim(), 10) || 0;
9923
9489
  } catch {
9924
9490
  return 0;
9925
9491
  }
9926
9492
  }
9927
9493
  function writeLastCheck() {
9928
9494
  try {
9929
- mkdirSync7(join7(homedir5(), ".okx"), { recursive: true });
9930
- writeFileSync6(CACHE_FILE2, String(Math.floor(Date.now() / 1e3)), "utf-8");
9495
+ mkdirSync8(join9(homedir7(), ".okx"), { recursive: true });
9496
+ writeFileSync7(CACHE_FILE2, String(Math.floor(Date.now() / 1e3)), "utf-8");
9931
9497
  } catch {
9932
9498
  }
9933
9499
  }
@@ -10602,47 +10168,6 @@ var HELP_TREE = {
10602
10168
  }
10603
10169
  }
10604
10170
  },
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
10171
  config: {
10647
10172
  description: "Manage CLI configuration profiles",
10648
10173
  commands: {
@@ -11001,11 +10526,6 @@ var CLI_OPTIONS = {
11001
10526
  dir: { type: "string" },
11002
10527
  page: { type: "string" },
11003
10528
  format: { type: "string" },
11004
- // event contract
11005
- underlying: { type: "string" },
11006
- seriesId: { type: "string" },
11007
- eventId: { type: "string" },
11008
- outcome: { type: "string" },
11009
10529
  // diagnostics — cli/mcp/all/output are diagnose-specific; verbose is shared
11010
10530
  verbose: { type: "boolean", default: false },
11011
10531
  mcp: { type: "boolean", default: false },
@@ -13836,13 +13356,13 @@ async function cmdDcdQuoteAndBuy(run, opts) {
13836
13356
  }
13837
13357
 
13838
13358
  // 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";
13359
+ import { tmpdir, homedir as homedir9 } from "os";
13360
+ import { join as join11, dirname as dirname7 } from "path";
13361
+ import { mkdirSync as mkdirSync9, rmSync, existsSync as existsSync7, copyFileSync as copyFileSync2 } from "fs";
13842
13362
  import { execFileSync as execFileSync2 } from "child_process";
13843
13363
  import { randomUUID as randomUUID2 } from "crypto";
13844
13364
  function resolveNpx() {
13845
- const sibling = join9(dirname6(process.execPath), "npx");
13365
+ const sibling = join11(dirname7(process.execPath), "npx");
13846
13366
  if (existsSync7(sibling)) return sibling;
13847
13367
  return "npx";
13848
13368
  }
@@ -13896,13 +13416,13 @@ async function cmdSkillCategories(run, json) {
13896
13416
  outputLine("");
13897
13417
  }
13898
13418
  async function cmdSkillAdd(name, config, json) {
13899
- const tmpBase = join9(tmpdir(), `okx-skill-${randomUUID2()}`);
13900
- mkdirSync8(tmpBase, { recursive: true });
13419
+ const tmpBase = join11(tmpdir(), `okx-skill-${randomUUID2()}`);
13420
+ mkdirSync9(tmpBase, { recursive: true });
13901
13421
  try {
13902
13422
  outputLine(`Downloading ${name}...`);
13903
13423
  const client = new OkxRestClient(config);
13904
13424
  const zipPath = await downloadSkillZip(client, name, tmpBase);
13905
- const contentDir = await extractSkillZip(zipPath, join9(tmpBase, "content"));
13425
+ const contentDir = await extractSkillZip(zipPath, join11(tmpBase, "content"));
13906
13426
  const meta = readMetaJson(contentDir);
13907
13427
  validateSkillMdExists(contentDir);
13908
13428
  outputLine("Installing to detected agents...");
@@ -13912,7 +13432,7 @@ async function cmdSkillAdd(name, config, json) {
13912
13432
  timeout: 6e4
13913
13433
  });
13914
13434
  } catch (e) {
13915
- const savedZip = join9(process.cwd(), `${name}.zip`);
13435
+ const savedZip = join11(process.cwd(), `${name}.zip`);
13916
13436
  try {
13917
13437
  copyFileSync2(zipPath, savedZip);
13918
13438
  } catch {
@@ -13951,7 +13471,7 @@ function cmdSkillRemove(name, json) {
13951
13471
  timeout: 6e4
13952
13472
  });
13953
13473
  } catch {
13954
- const agentsPath = join9(homedir7(), ".agents", "skills", name);
13474
+ const agentsPath = join11(homedir9(), ".agents", "skills", name);
13955
13475
  try {
13956
13476
  rmSync(agentsPath, { recursive: true, force: true });
13957
13477
  } catch {
@@ -14024,462 +13544,10 @@ function printSkillInstallResult(meta, json) {
14024
13544
  }
14025
13545
  }
14026
13546
 
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
13547
  // src/index.ts
14480
13548
  var _require3 = createRequire3(import.meta.url);
14481
13549
  var CLI_VERSION2 = _require3("../package.json").version;
14482
- var GIT_HASH2 = true ? "244dce7" : "dev";
13550
+ var GIT_HASH2 = true ? "8ae72a0" : "dev";
14483
13551
  function handleConfigCommand(action, rest, json, lang, force) {
14484
13552
  if (action === "init") return cmdConfigInit(lang === "zh" ? "zh" : "en");
14485
13553
  if (action === "show") return cmdConfigShow(json);
@@ -15289,33 +14357,6 @@ function handleSkillCommand(run, action, rest, v, json, config) {
15289
14357
  errorLine("Valid: search, categories, add, download, remove, check, list");
15290
14358
  process.exitCode = 1;
15291
14359
  }
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
14360
  function outputResult(result, json) {
15320
14361
  if (json) {
15321
14362
  outputLine(JSON.stringify(result, null, 2));
@@ -15394,7 +14435,6 @@ async function main() {
15394
14435
  swap: () => handleSwapCommand(run, action, rest, v, json),
15395
14436
  futures: () => handleFuturesCommand(run, action, rest, v, json),
15396
14437
  option: () => handleOptionCommand(run, action, rest, v, json),
15397
- event: () => handleEventCommand(run, action, rest, v, json),
15398
14438
  bot: () => handleBotCommand(run, action, rest, v, json),
15399
14439
  earn: () => handleEarnCommand(run, action, rest, v, json),
15400
14440
  skill: () => handleSkillCommand(run, action, rest, v, json, config)
@@ -15419,7 +14459,6 @@ export {
15419
14459
  handleBotGridCommand,
15420
14460
  handleConfigCommand,
15421
14461
  handleEarnCommand,
15422
- handleEventCommand,
15423
14462
  handleFuturesAlgoCommand,
15424
14463
  handleFuturesCommand,
15425
14464
  handleMarketCommand,