@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 +478 -1439
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +193 -2
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
|
|
17
|
-
import { resolve as resolve2, dirname } from "path";
|
|
18
|
-
import { readFileSync, existsSync } from "fs";
|
|
19
|
-
import { join as
|
|
20
|
-
import { readFileSync as
|
|
21
|
-
import { join as
|
|
22
|
-
import { homedir } from "os";
|
|
23
|
-
import { readFileSync as
|
|
24
|
-
import { join as
|
|
25
|
-
import { homedir as
|
|
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
|
|
855
|
-
import { join as
|
|
856
|
-
import { homedir as
|
|
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
|
|
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
|
-
|
|
1395
|
+
vlog2(`\u2192 ${method} ${url}`);
|
|
1092
1396
|
const authInfo = auth === "private" && this.config.apiKey ? `auth=\u2713(${maskKey(this.config.apiKey)})` : `auth=${auth}`;
|
|
1093
|
-
|
|
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
|
-
|
|
1402
|
+
vlog2(`\u2717 ${status} | code=${code} | msg=${msg ?? "-"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
|
|
1099
1403
|
} else {
|
|
1100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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"
|
|
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"
|
|
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"
|
|
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
|
|
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"
|
|
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 =
|
|
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
|
-
|
|
3586
|
+
mkdirSync2(resolvedDir, { recursive: true });
|
|
3261
3587
|
const tmpPath = `${resolvedPath}.${randomUUID()}.tmp`;
|
|
3262
3588
|
try {
|
|
3263
|
-
|
|
3264
|
-
|
|
3589
|
+
writeFileSync2(tmpPath, data);
|
|
3590
|
+
renameSync2(tmpPath, resolvedPath);
|
|
3265
3591
|
} catch (err) {
|
|
3266
3592
|
try {
|
|
3267
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
3417
|
-
|
|
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: "
|
|
5845
|
-
module: "
|
|
5846
|
-
description: "
|
|
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
|
-
|
|
5396
|
+
ccy: {
|
|
5852
5397
|
type: "string",
|
|
5853
|
-
description: "
|
|
5398
|
+
description: "Currency, e.g. SOL, USDG"
|
|
5854
5399
|
},
|
|
5855
|
-
|
|
5400
|
+
action: {
|
|
5401
|
+
type: "string",
|
|
5402
|
+
description: "turn_on or turn_off"
|
|
5403
|
+
},
|
|
5404
|
+
earnType: {
|
|
5856
5405
|
type: "string",
|
|
5857
|
-
description: "
|
|
5406
|
+
description: "0=auto-lend+stake (default), 1=USDG earn. Omit to use default."
|
|
5858
5407
|
}
|
|
5859
5408
|
},
|
|
5860
|
-
required: ["
|
|
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/
|
|
5867
|
-
{
|
|
5868
|
-
|
|
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
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
8141
|
+
const dir = dirname4(path42);
|
|
8576
8142
|
if (!existsSync3(dir)) {
|
|
8577
|
-
|
|
8143
|
+
mkdirSync5(dir, { recursive: true });
|
|
8578
8144
|
}
|
|
8579
|
-
|
|
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 =
|
|
8259
|
+
var CACHE_FILE = join7(homedir5(), ".okx", "update-check.json");
|
|
8694
8260
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
8695
|
-
function
|
|
8261
|
+
function readCache2() {
|
|
8696
8262
|
try {
|
|
8697
8263
|
if (existsSync4(CACHE_FILE)) {
|
|
8698
|
-
return JSON.parse(
|
|
8264
|
+
return JSON.parse(readFileSync5(CACHE_FILE, "utf-8"));
|
|
8699
8265
|
}
|
|
8700
8266
|
} catch {
|
|
8701
8267
|
}
|
|
8702
8268
|
return {};
|
|
8703
8269
|
}
|
|
8704
|
-
function
|
|
8270
|
+
function writeCache2(cache) {
|
|
8705
8271
|
try {
|
|
8706
|
-
|
|
8707
|
-
|
|
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 =
|
|
8320
|
+
const cache = readCache2();
|
|
8755
8321
|
cache[packageName] = { latestVersion: latest, checkedAt: Date.now() };
|
|
8756
|
-
|
|
8322
|
+
writeCache2(cache);
|
|
8757
8323
|
}).catch(() => {
|
|
8758
8324
|
});
|
|
8759
8325
|
}
|
|
8760
8326
|
function checkForUpdates(packageName, currentVersion) {
|
|
8761
|
-
const cache =
|
|
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 ? "
|
|
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
|
|
9914
|
-
import { dirname as
|
|
9915
|
-
import { homedir as
|
|
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 =
|
|
9483
|
+
var CACHE_FILE2 = join9(homedir7(), ".okx", "last_check");
|
|
9918
9484
|
var THROTTLE_MS = 12 * 60 * 60 * 1e3;
|
|
9919
|
-
var NPM_BIN =
|
|
9485
|
+
var NPM_BIN = join9(dirname6(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
|
|
9920
9486
|
function readLastCheck() {
|
|
9921
9487
|
try {
|
|
9922
|
-
return parseInt(
|
|
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
|
-
|
|
9930
|
-
|
|
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
|
|
13840
|
-
import { join as
|
|
13841
|
-
import { mkdirSync as
|
|
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 =
|
|
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 =
|
|
13900
|
-
|
|
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,
|
|
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 =
|
|
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 =
|
|
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 ? "
|
|
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,
|