@okx_ai/okx-trade-cli 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { createRequire } from "module";
4
+ import { createRequire as createRequire2 } from "module";
5
5
 
6
6
  // ../core/dist/index.js
7
+ import { ProxyAgent } from "undici";
7
8
  import { createHmac } from "crypto";
8
9
  import fs from "fs";
9
10
  import path from "path";
@@ -933,8 +934,10 @@ function sleep(ms) {
933
934
  var RateLimiter = class {
934
935
  buckets = /* @__PURE__ */ new Map();
935
936
  maxWaitMs;
936
- constructor(maxWaitMs = 3e4) {
937
+ verbose;
938
+ constructor(maxWaitMs = 3e4, verbose = false) {
937
939
  this.maxWaitMs = maxWaitMs;
940
+ this.verbose = verbose;
938
941
  }
939
942
  async consume(config, amount = 1) {
940
943
  const bucket = this.getBucket(config);
@@ -952,6 +955,10 @@ var RateLimiter = class {
952
955
  "Reduce tool call frequency or retry later."
953
956
  );
954
957
  }
958
+ if (this.verbose) {
959
+ process.stderr.write(`[verbose] \u23F3 rate-limit: waiting ${waitMs}ms for "${config.key}"
960
+ `);
961
+ }
955
962
  await sleep(waitMs);
956
963
  this.refill(bucket);
957
964
  if (bucket.tokens < amount) {
@@ -1046,11 +1053,38 @@ function buildQueryString(query) {
1046
1053
  }
1047
1054
  return params.toString();
1048
1055
  }
1056
+ function maskKey(key) {
1057
+ if (key.length <= 8) return "***";
1058
+ return `${key.slice(0, 3)}***${key.slice(-3)}`;
1059
+ }
1060
+ function vlog(message) {
1061
+ process.stderr.write(`[verbose] ${message}
1062
+ `);
1063
+ }
1049
1064
  var OkxRestClient = class {
1050
1065
  config;
1051
- rateLimiter = new RateLimiter();
1066
+ rateLimiter;
1067
+ dispatcher;
1052
1068
  constructor(config) {
1053
1069
  this.config = config;
1070
+ this.rateLimiter = new RateLimiter(3e4, config.verbose);
1071
+ if (config.proxyUrl) {
1072
+ this.dispatcher = new ProxyAgent(config.proxyUrl);
1073
+ }
1074
+ }
1075
+ logRequest(method, url, auth) {
1076
+ if (!this.config.verbose) return;
1077
+ vlog(`\u2192 ${method} ${url}`);
1078
+ const authInfo = auth === "private" && this.config.apiKey ? `auth=\u2713(${maskKey(this.config.apiKey)})` : `auth=${auth}`;
1079
+ vlog(` ${authInfo} demo=${this.config.demo} timeout=${this.config.timeoutMs}ms`);
1080
+ }
1081
+ logResponse(status, rawLen, elapsed, traceId, code, msg) {
1082
+ if (!this.config.verbose) return;
1083
+ if (code && code !== "0" && code !== "1") {
1084
+ vlog(`\u2717 ${status} | code=${code} | msg=${msg ?? "-"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1085
+ } else {
1086
+ vlog(`\u2190 ${status} | code=${code ?? "0"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1087
+ }
1054
1088
  }
1055
1089
  async publicGet(path4, query, rateLimit) {
1056
1090
  return this.request({
@@ -1079,126 +1113,150 @@ var OkxRestClient = class {
1079
1113
  rateLimit
1080
1114
  });
1081
1115
  }
1082
- async request(config) {
1083
- const queryString = buildQueryString(config.query);
1084
- const requestPath = queryString.length > 0 ? `${config.path}?${queryString}` : config.path;
1085
- const url = `${this.config.baseUrl}${requestPath}`;
1086
- const bodyJson = config.body ? JSON.stringify(config.body) : "";
1087
- const timestamp = getNow();
1088
- if (config.rateLimit) {
1089
- await this.rateLimiter.consume(config.rateLimit);
1090
- }
1091
- const headers = new Headers({
1092
- "Content-Type": "application/json",
1093
- Accept: "application/json"
1094
- });
1095
- if (this.config.userAgent) {
1096
- headers.set("User-Agent", this.config.userAgent);
1097
- }
1098
- if (config.auth === "private") {
1099
- if (!this.config.hasAuth) {
1100
- throw new ConfigError(
1101
- "Private endpoint requires API credentials.",
1102
- "Configure OKX_API_KEY, OKX_SECRET_KEY and OKX_PASSPHRASE."
1103
- );
1104
- }
1105
- if (!this.config.apiKey || !this.config.secretKey || !this.config.passphrase) {
1106
- throw new ConfigError(
1107
- "Invalid private API credentials state.",
1108
- "Ensure all OKX credentials are set."
1109
- );
1110
- }
1111
- const payload = `${timestamp}${config.method.toUpperCase()}${requestPath}${bodyJson}`;
1112
- const signature = signOkxPayload(payload, this.config.secretKey);
1113
- headers.set("OK-ACCESS-KEY", this.config.apiKey);
1114
- headers.set("OK-ACCESS-SIGN", signature);
1115
- headers.set("OK-ACCESS-PASSPHRASE", this.config.passphrase);
1116
- headers.set("OK-ACCESS-TIMESTAMP", timestamp);
1116
+ setAuthHeaders(headers, method, requestPath, bodyJson, timestamp) {
1117
+ if (!this.config.hasAuth) {
1118
+ throw new ConfigError(
1119
+ "Private endpoint requires API credentials.",
1120
+ "Configure OKX_API_KEY, OKX_SECRET_KEY and OKX_PASSPHRASE."
1121
+ );
1117
1122
  }
1118
- if (this.config.demo) {
1119
- headers.set("x-simulated-trading", "1");
1123
+ if (!this.config.apiKey || !this.config.secretKey || !this.config.passphrase) {
1124
+ throw new ConfigError(
1125
+ "Invalid private API credentials state.",
1126
+ "Ensure all OKX credentials are set."
1127
+ );
1120
1128
  }
1121
- let response;
1122
- try {
1123
- response = await fetch(url, {
1124
- method: config.method,
1125
- headers,
1126
- body: config.method === "POST" ? bodyJson : void 0,
1127
- signal: AbortSignal.timeout(this.config.timeoutMs)
1128
- });
1129
- } catch (error) {
1130
- throw new NetworkError(
1131
- `Failed to call OKX endpoint ${config.method} ${requestPath}.`,
1132
- `${config.method} ${requestPath}`,
1133
- error
1129
+ const payload = `${timestamp}${method.toUpperCase()}${requestPath}${bodyJson}`;
1130
+ const signature = signOkxPayload(payload, this.config.secretKey);
1131
+ headers.set("OK-ACCESS-KEY", this.config.apiKey);
1132
+ headers.set("OK-ACCESS-SIGN", signature);
1133
+ headers.set("OK-ACCESS-PASSPHRASE", this.config.passphrase);
1134
+ headers.set("OK-ACCESS-TIMESTAMP", timestamp);
1135
+ }
1136
+ throwOkxError(code, msg, reqConfig, traceId) {
1137
+ const message = msg || "OKX API request failed.";
1138
+ const endpoint = `${reqConfig.method} ${reqConfig.path}`;
1139
+ if (code === "50111" || code === "50112" || code === "50113") {
1140
+ throw new AuthenticationError(
1141
+ message,
1142
+ "Check API key, secret, passphrase and permissions.",
1143
+ endpoint,
1144
+ traceId
1134
1145
  );
1135
1146
  }
1136
- const rawText = await response.text();
1137
- const traceId = extractTraceId(response.headers);
1147
+ const behavior = OKX_CODE_BEHAVIORS[code];
1148
+ const suggestion = behavior?.suggestion?.replace("{site}", this.config.site);
1149
+ if (code === "50011" || code === "50061") {
1150
+ throw new RateLimitError(message, suggestion, endpoint, traceId);
1151
+ }
1152
+ throw new OkxApiError(message, {
1153
+ code,
1154
+ endpoint,
1155
+ suggestion,
1156
+ traceId
1157
+ });
1158
+ }
1159
+ processResponse(rawText, response, elapsed, traceId, reqConfig, requestPath) {
1138
1160
  let parsed;
1139
1161
  try {
1140
1162
  parsed = rawText ? JSON.parse(rawText) : {};
1141
1163
  } catch (error) {
1164
+ this.logResponse(response.status, rawText.length, elapsed, traceId, "non-JSON");
1142
1165
  if (!response.ok) {
1143
1166
  const messagePreview = rawText.slice(0, 160).replace(/\s+/g, " ").trim();
1144
1167
  throw new OkxApiError(
1145
1168
  `HTTP ${response.status} from OKX: ${messagePreview || "Non-JSON response body"}`,
1146
1169
  {
1147
1170
  code: String(response.status),
1148
- endpoint: `${config.method} ${config.path}`,
1171
+ endpoint: `${reqConfig.method} ${reqConfig.path}`,
1149
1172
  suggestion: "Verify endpoint path and request parameters.",
1150
1173
  traceId
1151
1174
  }
1152
1175
  );
1153
1176
  }
1154
1177
  throw new NetworkError(
1155
- `OKX returned non-JSON response for ${config.method} ${requestPath}.`,
1156
- `${config.method} ${requestPath}`,
1178
+ `OKX returned non-JSON response for ${reqConfig.method} ${requestPath}.`,
1179
+ `${reqConfig.method} ${requestPath}`,
1157
1180
  error
1158
1181
  );
1159
1182
  }
1160
1183
  if (!response.ok) {
1184
+ this.logResponse(response.status, rawText.length, elapsed, traceId, parsed.code ?? "-", parsed.msg);
1161
1185
  throw new OkxApiError(
1162
1186
  `HTTP ${response.status} from OKX: ${parsed.msg ?? "Unknown error"}`,
1163
1187
  {
1164
1188
  code: String(response.status),
1165
- endpoint: `${config.method} ${config.path}`,
1189
+ endpoint: `${reqConfig.method} ${reqConfig.path}`,
1166
1190
  suggestion: "Retry later or verify endpoint parameters.",
1167
1191
  traceId
1168
1192
  }
1169
1193
  );
1170
1194
  }
1171
1195
  const responseCode = parsed.code;
1196
+ this.logResponse(response.status, rawText.length, elapsed, traceId, responseCode, parsed.msg);
1172
1197
  if (responseCode && responseCode !== "0" && responseCode !== "1") {
1173
- const message = parsed.msg || "OKX API request failed.";
1174
- const endpoint = `${config.method} ${config.path}`;
1175
- if (responseCode === "50111" || responseCode === "50112" || responseCode === "50113") {
1176
- throw new AuthenticationError(
1177
- message,
1178
- "Check API key, secret, passphrase and permissions.",
1179
- endpoint,
1180
- traceId
1181
- );
1182
- }
1183
- const behavior = OKX_CODE_BEHAVIORS[responseCode];
1184
- const suggestion = behavior?.suggestion?.replace("{site}", this.config.site);
1185
- if (responseCode === "50011" || responseCode === "50061") {
1186
- throw new RateLimitError(message, suggestion, endpoint, traceId);
1187
- }
1188
- throw new OkxApiError(message, {
1189
- code: responseCode,
1190
- endpoint,
1191
- suggestion,
1192
- traceId
1193
- });
1198
+ this.throwOkxError(responseCode, parsed.msg, reqConfig, traceId);
1194
1199
  }
1195
1200
  return {
1196
- endpoint: `${config.method} ${config.path}`,
1201
+ endpoint: `${reqConfig.method} ${reqConfig.path}`,
1197
1202
  requestTime: (/* @__PURE__ */ new Date()).toISOString(),
1198
1203
  data: parsed.data ?? null,
1199
1204
  raw: parsed
1200
1205
  };
1201
1206
  }
1207
+ async request(reqConfig) {
1208
+ const queryString = buildQueryString(reqConfig.query);
1209
+ const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
1210
+ const url = `${this.config.baseUrl}${requestPath}`;
1211
+ const bodyJson = reqConfig.body ? JSON.stringify(reqConfig.body) : "";
1212
+ const timestamp = getNow();
1213
+ this.logRequest(reqConfig.method, url, reqConfig.auth);
1214
+ if (reqConfig.rateLimit) {
1215
+ await this.rateLimiter.consume(reqConfig.rateLimit);
1216
+ }
1217
+ const headers = new Headers({
1218
+ "Content-Type": "application/json",
1219
+ Accept: "application/json"
1220
+ });
1221
+ if (this.config.userAgent) {
1222
+ headers.set("User-Agent", this.config.userAgent);
1223
+ }
1224
+ if (reqConfig.auth === "private") {
1225
+ this.setAuthHeaders(headers, reqConfig.method, requestPath, bodyJson, timestamp);
1226
+ }
1227
+ if (this.config.demo) {
1228
+ headers.set("x-simulated-trading", "1");
1229
+ }
1230
+ const t0 = Date.now();
1231
+ let response;
1232
+ try {
1233
+ const fetchOptions = {
1234
+ method: reqConfig.method,
1235
+ headers,
1236
+ body: reqConfig.method === "POST" ? bodyJson : void 0,
1237
+ signal: AbortSignal.timeout(this.config.timeoutMs)
1238
+ };
1239
+ if (this.dispatcher) {
1240
+ fetchOptions.dispatcher = this.dispatcher;
1241
+ }
1242
+ response = await fetch(url, fetchOptions);
1243
+ } catch (error) {
1244
+ if (this.config.verbose) {
1245
+ const elapsed2 = Date.now() - t0;
1246
+ const cause = error instanceof Error ? error.message : String(error);
1247
+ vlog(`\u2717 NetworkError after ${elapsed2}ms: ${cause}`);
1248
+ }
1249
+ throw new NetworkError(
1250
+ `Failed to call OKX endpoint ${reqConfig.method} ${requestPath}.`,
1251
+ `${reqConfig.method} ${requestPath}`,
1252
+ error
1253
+ );
1254
+ }
1255
+ const rawText = await response.text();
1256
+ const elapsed = Date.now() - t0;
1257
+ const traceId = extractTraceId(response.headers);
1258
+ return this.processResponse(rawText, response, elapsed, traceId, reqConfig, requestPath);
1259
+ }
1202
1260
  };
1203
1261
  var DEFAULT_SOURCE_TAG = "MCP";
1204
1262
  var OKX_SITES = {
@@ -1224,6 +1282,10 @@ var BOT_SUB_MODULE_IDS = [
1224
1282
  "bot.dca"
1225
1283
  ];
1226
1284
  var BOT_DEFAULT_SUB_MODULES = ["bot.grid"];
1285
+ var EARN_SUB_MODULE_IDS = [
1286
+ "earn.savings",
1287
+ "earn.onchain"
1288
+ ];
1227
1289
  var MODULES = [
1228
1290
  "market",
1229
1291
  "spot",
@@ -1231,6 +1293,7 @@ var MODULES = [
1231
1293
  "futures",
1232
1294
  "option",
1233
1295
  "account",
1296
+ ...EARN_SUB_MODULE_IDS,
1234
1297
  ...BOT_SUB_MODULE_IDS
1235
1298
  ];
1236
1299
  var DEFAULT_MODULES = ["spot", "swap", "option", "account", ...BOT_DEFAULT_SUB_MODULES];
@@ -1296,6 +1359,13 @@ function compactObject(object) {
1296
1359
  }
1297
1360
  return next;
1298
1361
  }
1362
+ function normalizeResponse(response) {
1363
+ return {
1364
+ endpoint: response.endpoint,
1365
+ requestTime: response.requestTime,
1366
+ data: response.data
1367
+ };
1368
+ }
1299
1369
  var OKX_CANDLE_BARS = [
1300
1370
  "1m",
1301
1371
  "3m",
@@ -1335,6 +1405,14 @@ function privateRateLimit(key, rps = 10) {
1335
1405
  refillPerSecond: rps
1336
1406
  };
1337
1407
  }
1408
+ function assertNotDemo(config, endpoint) {
1409
+ if (config.demo) {
1410
+ throw new ConfigError(
1411
+ `"${endpoint}" is not supported in simulated trading mode.`,
1412
+ "Disable demo mode (remove OKX_DEMO=1 or --demo flag) to use this endpoint."
1413
+ );
1414
+ }
1415
+ }
1338
1416
  function normalize(response) {
1339
1417
  return {
1340
1418
  endpoint: response.endpoint,
@@ -1913,7 +1991,7 @@ function registerAlgoTradeTools() {
1913
1991
  },
1914
1992
  sz: {
1915
1993
  type: "string",
1916
- description: "Contracts to close"
1994
+ description: "Number of contracts to close (NOT USDT amount). Use market_get_instruments to get ctVal for conversion."
1917
1995
  },
1918
1996
  tpTriggerPx: {
1919
1997
  type: "string",
@@ -2008,7 +2086,7 @@ function registerAlgoTradeTools() {
2008
2086
  },
2009
2087
  sz: {
2010
2088
  type: "string",
2011
- description: "Contracts"
2089
+ description: "Number of contracts (NOT USDT amount). Use market_get_instruments to get ctVal for conversion."
2012
2090
  },
2013
2091
  callbackRatio: {
2014
2092
  type: "string",
@@ -2821,6 +2899,246 @@ function registerBotTools() {
2821
2899
  ...registerDcaTools()
2822
2900
  ];
2823
2901
  }
2902
+ function registerEarnTools() {
2903
+ return [
2904
+ {
2905
+ name: "earn_get_savings_balance",
2906
+ module: "earn.savings",
2907
+ description: "Get Simple Earn (savings/flexible earn) balance. Returns current holdings for all currencies or a specific one. Private endpoint. Rate limit: 6 req/s.",
2908
+ isWrite: false,
2909
+ inputSchema: {
2910
+ type: "object",
2911
+ properties: {
2912
+ ccy: {
2913
+ type: "string",
2914
+ description: "e.g. USDT or BTC. Omit for all."
2915
+ }
2916
+ }
2917
+ },
2918
+ handler: async (rawArgs, context) => {
2919
+ const args = asRecord(rawArgs);
2920
+ const response = await context.client.privateGet(
2921
+ "/api/v5/finance/savings/balance",
2922
+ compactObject({ ccy: readString(args, "ccy") }),
2923
+ privateRateLimit("earn_get_savings_balance", 6)
2924
+ );
2925
+ return normalizeResponse(response);
2926
+ }
2927
+ },
2928
+ {
2929
+ name: "earn_savings_purchase",
2930
+ module: "earn.savings",
2931
+ description: "Purchase Simple Earn (savings/flexible earn). [CAUTION] Moves real funds into earn product. Not supported in demo/simulated trading mode. Private endpoint. Rate limit: 6 req/s.",
2932
+ isWrite: true,
2933
+ inputSchema: {
2934
+ type: "object",
2935
+ properties: {
2936
+ ccy: {
2937
+ type: "string",
2938
+ description: "Currency to purchase, e.g. USDT"
2939
+ },
2940
+ amt: {
2941
+ type: "string",
2942
+ description: "Purchase amount"
2943
+ },
2944
+ rate: {
2945
+ type: "string",
2946
+ description: "Lending rate. Annual rate in decimal, e.g. 0.01 = 1%. Defaults to 0.01 (1%, minimum rate, easiest to match)."
2947
+ }
2948
+ },
2949
+ required: ["ccy", "amt"]
2950
+ },
2951
+ handler: async (rawArgs, context) => {
2952
+ assertNotDemo(context.config, "earn_savings_purchase");
2953
+ const args = asRecord(rawArgs);
2954
+ const response = await context.client.privatePost(
2955
+ "/api/v5/finance/savings/purchase-redempt",
2956
+ compactObject({
2957
+ ccy: requireString(args, "ccy"),
2958
+ amt: requireString(args, "amt"),
2959
+ side: "purchase",
2960
+ rate: readString(args, "rate") ?? "0.01"
2961
+ }),
2962
+ privateRateLimit("earn_savings_purchase", 6)
2963
+ );
2964
+ return normalizeResponse(response);
2965
+ }
2966
+ },
2967
+ {
2968
+ name: "earn_savings_redeem",
2969
+ module: "earn.savings",
2970
+ description: "Redeem Simple Earn (savings/flexible earn). [CAUTION] Withdraws funds from earn product. Not supported in demo/simulated trading mode. Private endpoint. Rate limit: 6 req/s.",
2971
+ isWrite: true,
2972
+ inputSchema: {
2973
+ type: "object",
2974
+ properties: {
2975
+ ccy: {
2976
+ type: "string",
2977
+ description: "Currency to redeem, e.g. USDT"
2978
+ },
2979
+ amt: {
2980
+ type: "string",
2981
+ description: "Redemption amount"
2982
+ }
2983
+ },
2984
+ required: ["ccy", "amt"]
2985
+ },
2986
+ handler: async (rawArgs, context) => {
2987
+ assertNotDemo(context.config, "earn_savings_redeem");
2988
+ const args = asRecord(rawArgs);
2989
+ const response = await context.client.privatePost(
2990
+ "/api/v5/finance/savings/purchase-redempt",
2991
+ compactObject({
2992
+ ccy: requireString(args, "ccy"),
2993
+ amt: requireString(args, "amt"),
2994
+ side: "redempt"
2995
+ }),
2996
+ privateRateLimit("earn_savings_redeem", 6)
2997
+ );
2998
+ return normalizeResponse(response);
2999
+ }
3000
+ },
3001
+ {
3002
+ name: "earn_set_lending_rate",
3003
+ module: "earn.savings",
3004
+ description: "Set lending rate for Simple Earn. [CAUTION] Changes your lending rate preference. Not supported in demo/simulated trading mode. Private endpoint. Rate limit: 6 req/s.",
3005
+ isWrite: true,
3006
+ inputSchema: {
3007
+ type: "object",
3008
+ properties: {
3009
+ ccy: {
3010
+ type: "string",
3011
+ description: "Currency, e.g. USDT"
3012
+ },
3013
+ rate: {
3014
+ type: "string",
3015
+ description: "Lending rate. Annual rate in decimal, e.g. 0.01 = 1%"
3016
+ }
3017
+ },
3018
+ required: ["ccy", "rate"]
3019
+ },
3020
+ handler: async (rawArgs, context) => {
3021
+ assertNotDemo(context.config, "earn_set_lending_rate");
3022
+ const args = asRecord(rawArgs);
3023
+ const response = await context.client.privatePost(
3024
+ "/api/v5/finance/savings/set-lending-rate",
3025
+ {
3026
+ ccy: requireString(args, "ccy"),
3027
+ rate: requireString(args, "rate")
3028
+ },
3029
+ privateRateLimit("earn_set_lending_rate", 6)
3030
+ );
3031
+ return normalizeResponse(response);
3032
+ }
3033
+ },
3034
+ {
3035
+ name: "earn_get_lending_history",
3036
+ module: "earn.savings",
3037
+ description: "Get lending history for Simple Earn. Returns lending records with details like amount, rate, and earnings. Private endpoint. Rate limit: 6 req/s.",
3038
+ isWrite: false,
3039
+ inputSchema: {
3040
+ type: "object",
3041
+ properties: {
3042
+ ccy: {
3043
+ type: "string",
3044
+ description: "e.g. USDT. Omit for all."
3045
+ },
3046
+ after: {
3047
+ type: "string",
3048
+ description: "Pagination: before this record ID"
3049
+ },
3050
+ before: {
3051
+ type: "string",
3052
+ description: "Pagination: after this record ID"
3053
+ },
3054
+ limit: {
3055
+ type: "number",
3056
+ description: "Max results (default 100)"
3057
+ }
3058
+ }
3059
+ },
3060
+ handler: async (rawArgs, context) => {
3061
+ const args = asRecord(rawArgs);
3062
+ const response = await context.client.privateGet(
3063
+ "/api/v5/finance/savings/lending-history",
3064
+ compactObject({
3065
+ ccy: readString(args, "ccy"),
3066
+ after: readString(args, "after"),
3067
+ before: readString(args, "before"),
3068
+ limit: readNumber(args, "limit")
3069
+ }),
3070
+ privateRateLimit("earn_get_lending_history", 6)
3071
+ );
3072
+ return normalizeResponse(response);
3073
+ }
3074
+ },
3075
+ {
3076
+ name: "earn_get_lending_rate_summary",
3077
+ module: "earn.savings",
3078
+ description: "Get market lending rate summary for Simple Earn. Public endpoint (no API key required). Returns current lending rates, estimated APY, and available amounts. Rate limit: 6 req/s.",
3079
+ isWrite: false,
3080
+ inputSchema: {
3081
+ type: "object",
3082
+ properties: {
3083
+ ccy: {
3084
+ type: "string",
3085
+ description: "e.g. USDT. Omit for all."
3086
+ }
3087
+ }
3088
+ },
3089
+ handler: async (rawArgs, context) => {
3090
+ const args = asRecord(rawArgs);
3091
+ const response = await context.client.publicGet(
3092
+ "/api/v5/finance/savings/lending-rate-summary",
3093
+ compactObject({ ccy: readString(args, "ccy") }),
3094
+ publicRateLimit("earn_get_lending_rate_summary", 6)
3095
+ );
3096
+ return normalizeResponse(response);
3097
+ }
3098
+ },
3099
+ {
3100
+ name: "earn_get_lending_rate_history",
3101
+ module: "earn.savings",
3102
+ description: "Get historical lending rates for Simple Earn. Public endpoint (no API key required). Returns past lending rate data for trend analysis. Rate limit: 6 req/s.",
3103
+ isWrite: false,
3104
+ inputSchema: {
3105
+ type: "object",
3106
+ properties: {
3107
+ ccy: {
3108
+ type: "string",
3109
+ description: "e.g. USDT. Omit for all."
3110
+ },
3111
+ after: {
3112
+ type: "string",
3113
+ description: "Pagination: before this timestamp (ms)"
3114
+ },
3115
+ before: {
3116
+ type: "string",
3117
+ description: "Pagination: after this timestamp (ms)"
3118
+ },
3119
+ limit: {
3120
+ type: "number",
3121
+ description: "Max results (default 100)"
3122
+ }
3123
+ }
3124
+ },
3125
+ handler: async (rawArgs, context) => {
3126
+ const args = asRecord(rawArgs);
3127
+ const response = await context.client.publicGet(
3128
+ "/api/v5/finance/savings/lending-rate-history",
3129
+ compactObject({
3130
+ ccy: readString(args, "ccy"),
3131
+ after: readString(args, "after"),
3132
+ before: readString(args, "before"),
3133
+ limit: readNumber(args, "limit")
3134
+ }),
3135
+ publicRateLimit("earn_get_lending_rate_history", 6)
3136
+ );
3137
+ return normalizeResponse(response);
3138
+ }
3139
+ }
3140
+ ];
3141
+ }
2824
3142
  var FUTURES_INST_TYPES = ["FUTURES", "SWAP"];
2825
3143
  function normalize5(response) {
2826
3144
  return {
@@ -2865,7 +3183,7 @@ function registerFuturesTools() {
2865
3183
  },
2866
3184
  sz: {
2867
3185
  type: "string",
2868
- description: "Contracts"
3186
+ description: "Number of contracts (NOT USDT amount). Use market_get_instruments to get ctVal for conversion."
2869
3187
  },
2870
3188
  px: {
2871
3189
  type: "string",
@@ -3089,99 +3407,371 @@ function registerFuturesTools() {
3089
3407
  enum: [...FUTURES_INST_TYPES],
3090
3408
  description: "FUTURES (default) or SWAP"
3091
3409
  },
3092
- instId: {
3410
+ instId: {
3411
+ type: "string",
3412
+ description: "e.g. BTC-USDT-240329"
3413
+ },
3414
+ posId: {
3415
+ type: "string"
3416
+ }
3417
+ }
3418
+ },
3419
+ handler: async (rawArgs, context) => {
3420
+ const args = asRecord(rawArgs);
3421
+ const instType = readString(args, "instType") ?? "FUTURES";
3422
+ assertEnum(instType, "instType", FUTURES_INST_TYPES);
3423
+ const response = await context.client.privateGet(
3424
+ "/api/v5/account/positions",
3425
+ compactObject({
3426
+ instType,
3427
+ instId: readString(args, "instId"),
3428
+ posId: readString(args, "posId")
3429
+ }),
3430
+ privateRateLimit("futures_get_positions", 10)
3431
+ );
3432
+ return normalize5(response);
3433
+ }
3434
+ },
3435
+ {
3436
+ name: "futures_get_fills",
3437
+ module: "futures",
3438
+ description: "Get FUTURES fill details. archive=false: last 3 days. archive=true: up to 3 months. Private. Rate limit: 20 req/s.",
3439
+ isWrite: false,
3440
+ inputSchema: {
3441
+ type: "object",
3442
+ properties: {
3443
+ archive: {
3444
+ type: "boolean",
3445
+ description: "true=up to 3 months; false=last 3 days (default)"
3446
+ },
3447
+ instType: {
3448
+ type: "string",
3449
+ enum: [...FUTURES_INST_TYPES],
3450
+ description: "FUTURES (default) or SWAP"
3451
+ },
3452
+ instId: {
3453
+ type: "string",
3454
+ description: "Instrument ID filter"
3455
+ },
3456
+ ordId: {
3457
+ type: "string",
3458
+ description: "Order ID filter"
3459
+ },
3460
+ after: {
3461
+ type: "string",
3462
+ description: "Pagination: before this bill ID"
3463
+ },
3464
+ before: {
3465
+ type: "string",
3466
+ description: "Pagination: after this bill ID"
3467
+ },
3468
+ begin: {
3469
+ type: "string",
3470
+ description: "Start time (ms)"
3471
+ },
3472
+ end: {
3473
+ type: "string",
3474
+ description: "End time (ms)"
3475
+ },
3476
+ limit: {
3477
+ type: "number",
3478
+ description: "Max results (default 100 or 20 for archive)"
3479
+ }
3480
+ }
3481
+ },
3482
+ handler: async (rawArgs, context) => {
3483
+ const args = asRecord(rawArgs);
3484
+ const archive = readBoolean(args, "archive") ?? false;
3485
+ const instType = readString(args, "instType") ?? "FUTURES";
3486
+ assertEnum(instType, "instType", FUTURES_INST_TYPES);
3487
+ const path4 = archive ? "/api/v5/trade/fills-history" : "/api/v5/trade/fills";
3488
+ const response = await context.client.privateGet(
3489
+ path4,
3490
+ compactObject({
3491
+ instType,
3492
+ instId: readString(args, "instId"),
3493
+ ordId: readString(args, "ordId"),
3494
+ after: readString(args, "after"),
3495
+ before: readString(args, "before"),
3496
+ begin: readString(args, "begin"),
3497
+ end: readString(args, "end"),
3498
+ limit: readNumber(args, "limit") ?? (archive ? 20 : void 0)
3499
+ }),
3500
+ privateRateLimit("futures_get_fills", 20)
3501
+ );
3502
+ return normalize5(response);
3503
+ }
3504
+ }
3505
+ ];
3506
+ }
3507
+ function registerOnchainEarnTools() {
3508
+ return [
3509
+ // -------------------------------------------------------------------------
3510
+ // Get Offers
3511
+ // -------------------------------------------------------------------------
3512
+ {
3513
+ name: "onchain_earn_get_offers",
3514
+ module: "earn.onchain",
3515
+ description: "Get available on-chain earn (staking/DeFi) offers. Returns investment products with APY, terms, and limits. Private endpoint. Rate limit: 3 req/s.",
3516
+ isWrite: false,
3517
+ inputSchema: {
3518
+ type: "object",
3519
+ properties: {
3520
+ productId: {
3521
+ type: "string",
3522
+ description: "Specific product ID to query. Omit for all offers."
3523
+ },
3524
+ protocolType: {
3525
+ type: "string",
3526
+ description: "Protocol type filter: staking, defi. Omit for all types."
3527
+ },
3528
+ ccy: {
3529
+ type: "string",
3530
+ description: "Currency filter, e.g. ETH. Omit for all currencies."
3531
+ }
3532
+ }
3533
+ },
3534
+ handler: async (rawArgs, context) => {
3535
+ const args = asRecord(rawArgs);
3536
+ const response = await context.client.privateGet(
3537
+ "/api/v5/finance/staking-defi/offers",
3538
+ compactObject({
3539
+ productId: readString(args, "productId"),
3540
+ protocolType: readString(args, "protocolType"),
3541
+ ccy: readString(args, "ccy")
3542
+ }),
3543
+ privateRateLimit("onchain_earn_get_offers", 3)
3544
+ );
3545
+ return normalizeResponse(response);
3546
+ }
3547
+ },
3548
+ // -------------------------------------------------------------------------
3549
+ // Purchase
3550
+ // -------------------------------------------------------------------------
3551
+ {
3552
+ name: "onchain_earn_purchase",
3553
+ module: "earn.onchain",
3554
+ description: "Purchase on-chain earn (staking/DeFi) product. [CAUTION] Moves real funds into staking/DeFi product. Not supported in demo/simulated trading mode. Private endpoint. Rate limit: 2 req/s.",
3555
+ isWrite: true,
3556
+ inputSchema: {
3557
+ type: "object",
3558
+ properties: {
3559
+ productId: {
3560
+ type: "string",
3561
+ description: "Product ID to purchase"
3562
+ },
3563
+ investData: {
3564
+ type: "array",
3565
+ description: "Investment data array: [{ccy, amt}]. Each item specifies currency and amount.",
3566
+ items: {
3567
+ type: "object",
3568
+ properties: {
3569
+ ccy: { type: "string", description: "Currency, e.g. ETH" },
3570
+ amt: { type: "string", description: "Amount to invest" }
3571
+ },
3572
+ required: ["ccy", "amt"]
3573
+ }
3574
+ },
3575
+ term: {
3576
+ type: "string",
3577
+ description: "Investment term in days. Required for fixed-term products."
3578
+ },
3579
+ tag: {
3580
+ type: "string",
3581
+ description: "Order tag for tracking (optional)."
3582
+ }
3583
+ },
3584
+ required: ["productId", "investData"]
3585
+ },
3586
+ handler: async (rawArgs, context) => {
3587
+ assertNotDemo(context.config, "onchain_earn_purchase");
3588
+ const args = asRecord(rawArgs);
3589
+ const response = await context.client.privatePost(
3590
+ "/api/v5/finance/staking-defi/purchase",
3591
+ compactObject({
3592
+ productId: requireString(args, "productId"),
3593
+ investData: args.investData,
3594
+ term: readString(args, "term"),
3595
+ tag: readString(args, "tag")
3596
+ }),
3597
+ privateRateLimit("onchain_earn_purchase", 2)
3598
+ );
3599
+ return normalizeResponse(response);
3600
+ }
3601
+ },
3602
+ // -------------------------------------------------------------------------
3603
+ // Redeem
3604
+ // -------------------------------------------------------------------------
3605
+ {
3606
+ name: "onchain_earn_redeem",
3607
+ module: "earn.onchain",
3608
+ description: "Redeem on-chain earn (staking/DeFi) investment. [CAUTION] Withdraws funds from staking/DeFi product. Some products may have lock periods. Not supported in demo mode. Private endpoint. Rate limit: 2 req/s.",
3609
+ isWrite: true,
3610
+ inputSchema: {
3611
+ type: "object",
3612
+ properties: {
3613
+ ordId: {
3614
+ type: "string",
3615
+ description: "Order ID to redeem"
3616
+ },
3617
+ protocolType: {
3618
+ type: "string",
3619
+ description: "Protocol type: staking, defi"
3620
+ },
3621
+ allowEarlyRedeem: {
3622
+ type: "boolean",
3623
+ description: "Allow early redemption for fixed-term products (may incur penalties). Default false."
3624
+ }
3625
+ },
3626
+ required: ["ordId", "protocolType"]
3627
+ },
3628
+ handler: async (rawArgs, context) => {
3629
+ assertNotDemo(context.config, "onchain_earn_redeem");
3630
+ const args = asRecord(rawArgs);
3631
+ const response = await context.client.privatePost(
3632
+ "/api/v5/finance/staking-defi/redeem",
3633
+ compactObject({
3634
+ ordId: requireString(args, "ordId"),
3635
+ protocolType: requireString(args, "protocolType"),
3636
+ allowEarlyRedeem: readBoolean(args, "allowEarlyRedeem")
3637
+ }),
3638
+ privateRateLimit("onchain_earn_redeem", 2)
3639
+ );
3640
+ return normalizeResponse(response);
3641
+ }
3642
+ },
3643
+ // -------------------------------------------------------------------------
3644
+ // Cancel
3645
+ // -------------------------------------------------------------------------
3646
+ {
3647
+ name: "onchain_earn_cancel",
3648
+ module: "earn.onchain",
3649
+ description: "Cancel pending on-chain earn purchase. [CAUTION] Cancels a pending investment order. Not supported in demo mode. Private endpoint. Rate limit: 2 req/s.",
3650
+ isWrite: true,
3651
+ inputSchema: {
3652
+ type: "object",
3653
+ properties: {
3654
+ ordId: {
3655
+ type: "string",
3656
+ description: "Order ID to cancel"
3657
+ },
3658
+ protocolType: {
3659
+ type: "string",
3660
+ description: "Protocol type: staking, defi"
3661
+ }
3662
+ },
3663
+ required: ["ordId", "protocolType"]
3664
+ },
3665
+ handler: async (rawArgs, context) => {
3666
+ assertNotDemo(context.config, "onchain_earn_cancel");
3667
+ const args = asRecord(rawArgs);
3668
+ const response = await context.client.privatePost(
3669
+ "/api/v5/finance/staking-defi/cancel",
3670
+ {
3671
+ ordId: requireString(args, "ordId"),
3672
+ protocolType: requireString(args, "protocolType")
3673
+ },
3674
+ privateRateLimit("onchain_earn_cancel", 2)
3675
+ );
3676
+ return normalizeResponse(response);
3677
+ }
3678
+ },
3679
+ // -------------------------------------------------------------------------
3680
+ // Get Active Orders
3681
+ // -------------------------------------------------------------------------
3682
+ {
3683
+ name: "onchain_earn_get_active_orders",
3684
+ module: "earn.onchain",
3685
+ description: "Get active on-chain earn orders. Returns current staking/DeFi investments. Private endpoint. Rate limit: 3 req/s.",
3686
+ isWrite: false,
3687
+ inputSchema: {
3688
+ type: "object",
3689
+ properties: {
3690
+ productId: {
3691
+ type: "string",
3692
+ description: "Filter by product ID. Omit for all."
3693
+ },
3694
+ protocolType: {
3695
+ type: "string",
3696
+ description: "Filter by protocol type: staking, defi. Omit for all."
3697
+ },
3698
+ ccy: {
3093
3699
  type: "string",
3094
- description: "e.g. BTC-USDT-240329"
3700
+ description: "Filter by currency, e.g. ETH. Omit for all."
3095
3701
  },
3096
- posId: {
3097
- type: "string"
3702
+ state: {
3703
+ type: "string",
3704
+ description: "Filter by state: 8 (pending), 13 (cancelling), 9 (onchain), 1 (earning), 2 (redeeming). Omit for all."
3098
3705
  }
3099
3706
  }
3100
3707
  },
3101
3708
  handler: async (rawArgs, context) => {
3102
3709
  const args = asRecord(rawArgs);
3103
- const instType = readString(args, "instType") ?? "FUTURES";
3104
- assertEnum(instType, "instType", FUTURES_INST_TYPES);
3105
3710
  const response = await context.client.privateGet(
3106
- "/api/v5/account/positions",
3711
+ "/api/v5/finance/staking-defi/orders-active",
3107
3712
  compactObject({
3108
- instType,
3109
- instId: readString(args, "instId"),
3110
- posId: readString(args, "posId")
3713
+ productId: readString(args, "productId"),
3714
+ protocolType: readString(args, "protocolType"),
3715
+ ccy: readString(args, "ccy"),
3716
+ state: readString(args, "state")
3111
3717
  }),
3112
- privateRateLimit("futures_get_positions", 10)
3718
+ privateRateLimit("onchain_earn_get_active_orders", 3)
3113
3719
  );
3114
- return normalize5(response);
3720
+ return normalizeResponse(response);
3115
3721
  }
3116
3722
  },
3723
+ // -------------------------------------------------------------------------
3724
+ // Get Order History
3725
+ // -------------------------------------------------------------------------
3117
3726
  {
3118
- name: "futures_get_fills",
3119
- module: "futures",
3120
- description: "Get FUTURES fill details. archive=false: last 3 days. archive=true: up to 3 months. Private. Rate limit: 20 req/s.",
3727
+ name: "onchain_earn_get_order_history",
3728
+ module: "earn.onchain",
3729
+ description: "Get on-chain earn order history. Returns past staking/DeFi investments including redeemed orders. Private endpoint. Rate limit: 3 req/s.",
3121
3730
  isWrite: false,
3122
3731
  inputSchema: {
3123
3732
  type: "object",
3124
3733
  properties: {
3125
- archive: {
3126
- type: "boolean",
3127
- description: "true=up to 3 months; false=last 3 days (default)"
3128
- },
3129
- instType: {
3734
+ productId: {
3130
3735
  type: "string",
3131
- enum: [...FUTURES_INST_TYPES],
3132
- description: "FUTURES (default) or SWAP"
3736
+ description: "Filter by product ID. Omit for all."
3133
3737
  },
3134
- instId: {
3738
+ protocolType: {
3135
3739
  type: "string",
3136
- description: "Instrument ID filter"
3740
+ description: "Filter by protocol type: staking, defi. Omit for all."
3137
3741
  },
3138
- ordId: {
3742
+ ccy: {
3139
3743
  type: "string",
3140
- description: "Order ID filter"
3744
+ description: "Filter by currency, e.g. ETH. Omit for all."
3141
3745
  },
3142
3746
  after: {
3143
3747
  type: "string",
3144
- description: "Pagination: before this bill ID"
3748
+ description: "Pagination: return results before this order ID"
3145
3749
  },
3146
3750
  before: {
3147
3751
  type: "string",
3148
- description: "Pagination: after this bill ID"
3149
- },
3150
- begin: {
3151
- type: "string",
3152
- description: "Start time (ms)"
3153
- },
3154
- end: {
3155
- type: "string",
3156
- description: "End time (ms)"
3752
+ description: "Pagination: return results after this order ID"
3157
3753
  },
3158
3754
  limit: {
3159
- type: "number",
3160
- description: "Max results (default 100 or 20 for archive)"
3755
+ type: "string",
3756
+ description: "Max results to return (default 100, max 100)"
3161
3757
  }
3162
3758
  }
3163
3759
  },
3164
3760
  handler: async (rawArgs, context) => {
3165
3761
  const args = asRecord(rawArgs);
3166
- const archive = readBoolean(args, "archive") ?? false;
3167
- const instType = readString(args, "instType") ?? "FUTURES";
3168
- assertEnum(instType, "instType", FUTURES_INST_TYPES);
3169
- const path4 = archive ? "/api/v5/trade/fills-history" : "/api/v5/trade/fills";
3170
3762
  const response = await context.client.privateGet(
3171
- path4,
3763
+ "/api/v5/finance/staking-defi/orders-history",
3172
3764
  compactObject({
3173
- instType,
3174
- instId: readString(args, "instId"),
3175
- ordId: readString(args, "ordId"),
3765
+ productId: readString(args, "productId"),
3766
+ protocolType: readString(args, "protocolType"),
3767
+ ccy: readString(args, "ccy"),
3176
3768
  after: readString(args, "after"),
3177
3769
  before: readString(args, "before"),
3178
- begin: readString(args, "begin"),
3179
- end: readString(args, "end"),
3180
- limit: readNumber(args, "limit") ?? (archive ? 20 : void 0)
3770
+ limit: readString(args, "limit")
3181
3771
  }),
3182
- privateRateLimit("futures_get_fills", 20)
3772
+ privateRateLimit("onchain_earn_get_order_history", 3)
3183
3773
  );
3184
- return normalize5(response);
3774
+ return normalizeResponse(response);
3185
3775
  }
3186
3776
  }
3187
3777
  ];
@@ -3703,7 +4293,7 @@ function registerOptionTools() {
3703
4293
  },
3704
4294
  sz: {
3705
4295
  type: "string",
3706
- description: "Number of contracts"
4296
+ description: "Number of contracts (NOT USDT amount). Use market_get_instruments to get ctVal for conversion."
3707
4297
  },
3708
4298
  px: {
3709
4299
  type: "string",
@@ -3810,7 +4400,7 @@ function registerOptionTools() {
3810
4400
  instId: { type: "string", description: "e.g. BTC-USD-241227-50000-C" },
3811
4401
  ordId: { type: "string" },
3812
4402
  clOrdId: { type: "string" },
3813
- newSz: { type: "string", description: "New quantity (contracts)" },
4403
+ newSz: { type: "string", description: "New number of contracts (NOT USDT amount)" },
3814
4404
  newPx: { type: "string", description: "New price" }
3815
4405
  },
3816
4406
  required: ["instId"]
@@ -4772,7 +5362,7 @@ function registerSwapTradeTools() {
4772
5362
  },
4773
5363
  sz: {
4774
5364
  type: "string",
4775
- description: "Contracts (e.g. '1'; BTC-USDT-SWAP: 1ct=0.01 BTC)"
5365
+ description: "Number of contracts (NOT USDT amount). Use market_get_instruments to get ctVal for conversion."
4776
5366
  },
4777
5367
  px: {
4778
5368
  type: "string",
@@ -5037,7 +5627,7 @@ function registerSwapTradeTools() {
5037
5627
  properties: {
5038
5628
  instId: { type: "string", description: "e.g. BTC-USDT-SWAP" },
5039
5629
  algoId: { type: "string", description: "Algo order ID" },
5040
- newSz: { type: "string", description: "New quantity (contracts)" },
5630
+ newSz: { type: "string", description: "New number of contracts (NOT USDT amount)" },
5041
5631
  newTpTriggerPx: { type: "string", description: "New TP trigger price" },
5042
5632
  newTpOrdPx: { type: "string", description: "New TP order price; -1=market" },
5043
5633
  newSlTriggerPx: { type: "string", description: "New SL trigger price" },
@@ -5390,6 +5980,8 @@ function allToolSpecs() {
5390
5980
  ...registerAlgoTradeTools(),
5391
5981
  ...registerAccountTools(),
5392
5982
  ...registerBotTools(),
5983
+ ...registerEarnTools(),
5984
+ ...registerOnchainEarnTools(),
5393
5985
  ...registerAuditTools()
5394
5986
  ];
5395
5987
  }
@@ -5427,8 +6019,15 @@ function writeFullConfig(config) {
5427
6019
  writeFileSync(path4, stringify(config), "utf-8");
5428
6020
  }
5429
6021
  var BASE_MODULES = MODULES.filter(
5430
- (m) => !BOT_SUB_MODULE_IDS.includes(m)
6022
+ (m) => !BOT_SUB_MODULE_IDS.includes(m) && !EARN_SUB_MODULE_IDS.includes(m)
5431
6023
  );
6024
+ function expandShorthand(moduleId) {
6025
+ if (moduleId === "all") return [...BASE_MODULES, ...BOT_SUB_MODULE_IDS];
6026
+ if (moduleId === "earn" || moduleId === "earn.all") return [...EARN_SUB_MODULE_IDS];
6027
+ if (moduleId === "bot") return [...BOT_DEFAULT_SUB_MODULES];
6028
+ if (moduleId === "bot.all") return [...BOT_SUB_MODULE_IDS];
6029
+ return null;
6030
+ }
5432
6031
  function parseModuleList(rawModules) {
5433
6032
  if (!rawModules || rawModules.trim().length === 0) {
5434
6033
  return [...DEFAULT_MODULES];
@@ -5437,32 +6036,28 @@ function parseModuleList(rawModules) {
5437
6036
  if (trimmed === "all") {
5438
6037
  return [...BASE_MODULES, ...BOT_SUB_MODULE_IDS];
5439
6038
  }
5440
- const requested = trimmed.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
6039
+ const requested = trimmed.split(",").map((s) => s.trim()).filter(Boolean);
5441
6040
  if (requested.length === 0) {
5442
6041
  return [...DEFAULT_MODULES];
5443
6042
  }
5444
6043
  const deduped = /* @__PURE__ */ new Set();
5445
6044
  for (const moduleId of requested) {
5446
- if (moduleId === "bot") {
5447
- for (const sub of BOT_DEFAULT_SUB_MODULES) deduped.add(sub);
5448
- continue;
5449
- }
5450
- if (moduleId === "bot.all") {
5451
- for (const sub of BOT_SUB_MODULE_IDS) deduped.add(sub);
6045
+ const expanded = expandShorthand(moduleId);
6046
+ if (expanded) {
6047
+ expanded.forEach((sub) => deduped.add(sub));
5452
6048
  continue;
5453
6049
  }
5454
6050
  if (!MODULES.includes(moduleId)) {
5455
6051
  throw new ConfigError(
5456
6052
  `Unknown module "${moduleId}".`,
5457
- `Use one of: ${MODULES.join(", ")}, "bot", "bot.all", or "all".`
6053
+ `Use one of: ${MODULES.join(", ")}, "earn", "earn.all", "bot", "bot.all", or "all".`
5458
6054
  );
5459
6055
  }
5460
6056
  deduped.add(moduleId);
5461
6057
  }
5462
6058
  return Array.from(deduped);
5463
6059
  }
5464
- function loadConfig(cli) {
5465
- const toml = readTomlProfile(cli.profile);
6060
+ function loadCredentials(toml) {
5466
6061
  const apiKey = process.env.OKX_API_KEY?.trim() ?? toml.api_key;
5467
6062
  const secretKey = process.env.OKX_SECRET_KEY?.trim() ?? toml.secret_key;
5468
6063
  const passphrase = process.env.OKX_PASSPHRASE?.trim() ?? toml.passphrase;
@@ -5474,23 +6069,34 @@ function loadConfig(cli) {
5474
6069
  "Set OKX_API_KEY, OKX_SECRET_KEY and OKX_PASSPHRASE together (env vars or config.toml profile)."
5475
6070
  );
5476
6071
  }
5477
- const demo = cli.demo || process.env.OKX_DEMO === "1" || process.env.OKX_DEMO === "true" || (toml.demo ?? false);
5478
- const rawSite = cli.site?.trim() ?? process.env.OKX_SITE?.trim() ?? toml.site ?? "global";
6072
+ return { apiKey, secretKey, passphrase, hasAuth };
6073
+ }
6074
+ function resolveSite(cliSite, tomlSite) {
6075
+ const rawSite = cliSite?.trim() ?? process.env.OKX_SITE?.trim() ?? tomlSite ?? "global";
5479
6076
  if (!SITE_IDS.includes(rawSite)) {
5480
6077
  throw new ConfigError(
5481
6078
  `Unknown site "${rawSite}".`,
5482
6079
  `Use one of: ${SITE_IDS.join(", ")}.`
5483
6080
  );
5484
6081
  }
5485
- const site = rawSite;
5486
- const rawBaseUrl = process.env.OKX_API_BASE_URL?.trim() ?? toml.base_url ?? OKX_SITES[site].apiBaseUrl;
6082
+ return rawSite;
6083
+ }
6084
+ function resolveBaseUrl(site, tomlBaseUrl) {
6085
+ const rawBaseUrl = process.env.OKX_API_BASE_URL?.trim() ?? tomlBaseUrl ?? OKX_SITES[site].apiBaseUrl;
5487
6086
  if (!rawBaseUrl.startsWith("http://") && !rawBaseUrl.startsWith("https://")) {
5488
6087
  throw new ConfigError(
5489
6088
  `Invalid base URL "${rawBaseUrl}".`,
5490
6089
  "OKX_API_BASE_URL must start with http:// or https://"
5491
6090
  );
5492
6091
  }
5493
- const baseUrl = rawBaseUrl.replace(/\/+$/, "");
6092
+ return rawBaseUrl.replace(/\/+$/, "");
6093
+ }
6094
+ function loadConfig(cli) {
6095
+ const toml = readTomlProfile(cli.profile);
6096
+ const creds = loadCredentials(toml);
6097
+ const demo = cli.demo || process.env.OKX_DEMO === "1" || process.env.OKX_DEMO === "true" || (toml.demo ?? false);
6098
+ const site = resolveSite(cli.site, toml.site);
6099
+ const baseUrl = resolveBaseUrl(site, toml.base_url);
5494
6100
  const rawTimeout = process.env.OKX_TIMEOUT_MS ? Number(process.env.OKX_TIMEOUT_MS) : toml.timeout_ms ?? 15e3;
5495
6101
  if (!Number.isFinite(rawTimeout) || rawTimeout <= 0) {
5496
6102
  throw new ConfigError(
@@ -5498,11 +6104,15 @@ function loadConfig(cli) {
5498
6104
  "Set OKX_TIMEOUT_MS as a positive integer in milliseconds."
5499
6105
  );
5500
6106
  }
6107
+ const rawProxyUrl = toml.proxy_url?.trim();
6108
+ if (rawProxyUrl && !rawProxyUrl.startsWith("http://") && !rawProxyUrl.startsWith("https://")) {
6109
+ throw new ConfigError(
6110
+ `Invalid proxy URL "${rawProxyUrl}".`,
6111
+ "proxy_url must start with http:// or https://. SOCKS proxies are not supported."
6112
+ );
6113
+ }
5501
6114
  return {
5502
- apiKey,
5503
- secretKey,
5504
- passphrase,
5505
- hasAuth,
6115
+ ...creds,
5506
6116
  baseUrl,
5507
6117
  timeoutMs: Math.floor(rawTimeout),
5508
6118
  modules: parseModuleList(cli.modules),
@@ -5510,7 +6120,9 @@ function loadConfig(cli) {
5510
6120
  demo,
5511
6121
  site,
5512
6122
  userAgent: cli.userAgent,
5513
- sourceTag: cli.sourceTag ?? DEFAULT_SOURCE_TAG
6123
+ sourceTag: cli.sourceTag ?? DEFAULT_SOURCE_TAG,
6124
+ proxyUrl: rawProxyUrl || void 0,
6125
+ verbose: cli.verbose ?? false
5514
6126
  };
5515
6127
  }
5516
6128
  var CACHE_FILE = join2(homedir2(), ".okx", "update-check.json");
@@ -5726,6 +6338,313 @@ function runSetup(options) {
5726
6338
  }
5727
6339
  }
5728
6340
 
6341
+ // src/commands/diagnose.ts
6342
+ import dns from "dns/promises";
6343
+ import net from "net";
6344
+ import os2 from "os";
6345
+ import tls from "tls";
6346
+ import { createRequire } from "module";
6347
+ var _require = createRequire(import.meta.url);
6348
+ function readCliVersion() {
6349
+ for (const rel of ["../package.json", "../../package.json"]) {
6350
+ try {
6351
+ return _require(rel).version;
6352
+ } catch (_err) {
6353
+ }
6354
+ }
6355
+ return "0.0.0";
6356
+ }
6357
+ var CLI_VERSION = readCliVersion();
6358
+ var GIT_HASH = true ? "4427916" : "dev";
6359
+ var Report = class {
6360
+ lines = [];
6361
+ add(key, value) {
6362
+ this.lines.push({ key, value });
6363
+ }
6364
+ print() {
6365
+ const w = process.stdout.write.bind(process.stdout);
6366
+ const sep = "\u2500".repeat(52);
6367
+ w(`
6368
+ \u2500\u2500 Diagnostic Report (copy & share) ${sep.slice(35)}
6369
+ `);
6370
+ for (const { key, value } of this.lines) {
6371
+ w(` ${key.padEnd(14)} ${value}
6372
+ `);
6373
+ }
6374
+ w(` ${sep}
6375
+
6376
+ `);
6377
+ }
6378
+ };
6379
+ function ok(label, detail) {
6380
+ process.stdout.write(` \u2713 ${label.padEnd(14)} ${detail}
6381
+ `);
6382
+ }
6383
+ function fail(label, detail, hints) {
6384
+ process.stdout.write(` \u2717 ${label.padEnd(14)} ${detail}
6385
+ `);
6386
+ for (const hint of hints) {
6387
+ process.stdout.write(` \u2192 ${hint}
6388
+ `);
6389
+ }
6390
+ }
6391
+ function section(title) {
6392
+ process.stdout.write(`
6393
+ ${title}
6394
+ `);
6395
+ }
6396
+ function maskKey2(key) {
6397
+ if (!key) return "(not set)";
6398
+ if (key.length <= 8) return "****";
6399
+ return `${key.slice(0, 2)}****${key.slice(-2)}`;
6400
+ }
6401
+ async function checkDns(hostname) {
6402
+ const t0 = Date.now();
6403
+ try {
6404
+ const addresses = await dns.resolve4(hostname);
6405
+ return { ok: true, ip: addresses[0], ms: Date.now() - t0 };
6406
+ } catch (e) {
6407
+ return { ok: false, ms: Date.now() - t0, error: e instanceof Error ? e.message : String(e) };
6408
+ }
6409
+ }
6410
+ async function checkSocket(createFn, successEvent, timeoutMs) {
6411
+ const t0 = Date.now();
6412
+ return new Promise((resolve) => {
6413
+ const socket = createFn();
6414
+ const cleanup = () => {
6415
+ socket.removeAllListeners();
6416
+ socket.destroy();
6417
+ };
6418
+ socket.once(successEvent, () => {
6419
+ cleanup();
6420
+ resolve({ ok: true, ms: Date.now() - t0 });
6421
+ });
6422
+ socket.once("timeout", () => {
6423
+ cleanup();
6424
+ resolve({ ok: false, ms: Date.now() - t0, error: `timed out after ${timeoutMs}ms` });
6425
+ });
6426
+ socket.once("error", (err) => {
6427
+ cleanup();
6428
+ resolve({ ok: false, ms: Date.now() - t0, error: err.message });
6429
+ });
6430
+ });
6431
+ }
6432
+ async function checkTcp(hostname, port, timeoutMs = 5e3) {
6433
+ return checkSocket(
6434
+ () => net.createConnection({ host: hostname, port, timeout: timeoutMs }),
6435
+ "connect",
6436
+ timeoutMs
6437
+ );
6438
+ }
6439
+ async function checkTls(hostname, port, timeoutMs = 5e3) {
6440
+ return checkSocket(
6441
+ () => tls.connect({ host: hostname, port, timeout: timeoutMs, servername: hostname }),
6442
+ "secureConnect",
6443
+ timeoutMs
6444
+ );
6445
+ }
6446
+ function checkProxyEnv(report) {
6447
+ const httpProxy = process.env.HTTP_PROXY ?? process.env.http_proxy;
6448
+ const httpsProxy = process.env.HTTPS_PROXY ?? process.env.https_proxy;
6449
+ const noProxy = process.env.NO_PROXY ?? process.env.no_proxy;
6450
+ if (httpProxy || httpsProxy) {
6451
+ ok("HTTP_PROXY", httpProxy ?? "(not set)");
6452
+ ok("HTTPS_PROXY", httpsProxy ?? "(not set)");
6453
+ if (noProxy) ok("NO_PROXY", noProxy);
6454
+ report.add("http_proxy", httpProxy ?? "-");
6455
+ report.add("https_proxy", httpsProxy ?? "-");
6456
+ if (noProxy) report.add("no_proxy", noProxy);
6457
+ } else {
6458
+ ok("Proxy", "(none)");
6459
+ report.add("proxy", "none");
6460
+ }
6461
+ }
6462
+ function checkEnvironment(report) {
6463
+ let passed = true;
6464
+ section("Environment");
6465
+ const nodeVersion = process.version;
6466
+ const nodeMajor = parseInt(nodeVersion.slice(1), 10);
6467
+ if (nodeMajor >= 18) {
6468
+ ok("Node.js", `${nodeVersion} (>= 18 required)`);
6469
+ } else {
6470
+ fail("Node.js", `${nodeVersion} (>= 18 required)`, ["Upgrade Node.js to v18 or later"]);
6471
+ passed = false;
6472
+ }
6473
+ ok("CLI", `v${CLI_VERSION} (${GIT_HASH})`);
6474
+ ok("OS", `${process.platform} ${process.arch}`);
6475
+ ok("OS release", os2.release());
6476
+ ok("Shell", process.env.SHELL ?? "(unknown)");
6477
+ ok("Locale", `${process.env.LANG ?? process.env.LC_ALL ?? "(unknown)"}`);
6478
+ ok("Timezone", Intl.DateTimeFormat().resolvedOptions().timeZone);
6479
+ report.add("cli", `${CLI_VERSION} (${GIT_HASH})`);
6480
+ report.add("node", `${nodeVersion} ${process.platform} ${process.arch}`);
6481
+ const machine = typeof os2.machine === "function" ? os2.machine() : process.arch;
6482
+ report.add("os", `${os2.type()} ${os2.release()} ${machine}`);
6483
+ report.add("shell", process.env.SHELL ?? "-");
6484
+ report.add("locale", process.env.LANG ?? process.env.LC_ALL ?? "-");
6485
+ report.add("tz", Intl.DateTimeFormat().resolvedOptions().timeZone);
6486
+ checkProxyEnv(report);
6487
+ return passed;
6488
+ }
6489
+ function checkConfig(config, profile, report) {
6490
+ let passed = true;
6491
+ section(`Config (profile: ${profile})`);
6492
+ if (config.hasAuth) {
6493
+ ok("API key", maskKey2(config.apiKey));
6494
+ ok("Secret", "****");
6495
+ ok("Passphrase", "****");
6496
+ } else {
6497
+ fail("Credentials", "not configured", [
6498
+ "Set OKX_API_KEY, OKX_SECRET_KEY, OKX_PASSPHRASE env vars",
6499
+ "Or run: okx config init"
6500
+ ]);
6501
+ passed = false;
6502
+ }
6503
+ ok("Demo mode", String(config.demo));
6504
+ ok("Site", config.site);
6505
+ ok("Base URL", config.baseUrl);
6506
+ ok("Timeout", `${config.timeoutMs}ms`);
6507
+ report.add("profile", profile);
6508
+ report.add("site", config.site);
6509
+ report.add("base", config.baseUrl);
6510
+ report.add("auth", config.hasAuth ? `true (key=${maskKey2(config.apiKey)})` : "false");
6511
+ report.add("demo", String(config.demo));
6512
+ report.add("timeout", `${config.timeoutMs}ms`);
6513
+ return passed;
6514
+ }
6515
+ async function checkTcpTls(hostname, port, protocol, report) {
6516
+ let passed = true;
6517
+ const tcpResult = await checkTcp(hostname, port);
6518
+ if (tcpResult.ok) {
6519
+ ok("TCP connect", `port ${port} (${tcpResult.ms}ms)`);
6520
+ report.add("tcp", `${port} OK (${tcpResult.ms}ms)`);
6521
+ } else {
6522
+ fail("TCP connect", `port ${port} \u2014 ${tcpResult.error}`, [
6523
+ "Check firewall/proxy/VPN settings",
6524
+ `Try: nc -zv ${hostname} ${port}`
6525
+ ]);
6526
+ report.add("tcp", `FAIL ${port} ${tcpResult.error} (${tcpResult.ms}ms)`);
6527
+ return false;
6528
+ }
6529
+ if (protocol === "https:") {
6530
+ const tlsResult = await checkTls(hostname, port);
6531
+ if (tlsResult.ok) {
6532
+ ok("TLS handshake", `(${tlsResult.ms}ms)`);
6533
+ report.add("tls", `OK (${tlsResult.ms}ms)`);
6534
+ } else {
6535
+ fail("TLS handshake", tlsResult.error ?? "failed", [
6536
+ "Check system certificates or proxy MITM settings"
6537
+ ]);
6538
+ passed = false;
6539
+ report.add("tls", `FAIL ${tlsResult.error} (${tlsResult.ms}ms)`);
6540
+ }
6541
+ }
6542
+ return passed;
6543
+ }
6544
+ async function checkNetwork(config, client, report) {
6545
+ let passed = true;
6546
+ section("Network");
6547
+ const url = new URL(config.baseUrl);
6548
+ const hostname = url.hostname;
6549
+ const defaultPort = url.protocol === "https:" ? 443 : 80;
6550
+ const port = url.port ? parseInt(url.port, 10) : defaultPort;
6551
+ const dnsResult = await checkDns(hostname);
6552
+ if (dnsResult.ok) {
6553
+ ok("DNS resolve", `${hostname} \u2192 ${dnsResult.ip} (${dnsResult.ms}ms)`);
6554
+ report.add("dns", `${hostname} \u2192 ${dnsResult.ip} (${dnsResult.ms}ms)`);
6555
+ } else {
6556
+ fail("DNS resolve", `${hostname} \u2014 ${dnsResult.error}`, [
6557
+ "Check DNS settings or network connection",
6558
+ `Try: nslookup ${hostname}`
6559
+ ]);
6560
+ passed = false;
6561
+ report.add("dns", `FAIL ${hostname} ${dnsResult.error} (${dnsResult.ms}ms)`);
6562
+ }
6563
+ if (dnsResult.ok) {
6564
+ const tcpTlsPassed = await checkTcpTls(hostname, port, url.protocol, report);
6565
+ if (!tcpTlsPassed) passed = false;
6566
+ }
6567
+ const t0 = Date.now();
6568
+ try {
6569
+ await client.publicGet("/api/v5/public/time");
6570
+ const ms = Date.now() - t0;
6571
+ ok("API /public/time", `200 (${ms}ms)`);
6572
+ report.add("api", `/public/time 200 (${ms}ms)`);
6573
+ } catch (e) {
6574
+ const ms = Date.now() - t0;
6575
+ const msg = e instanceof Error ? e.message : String(e);
6576
+ fail("API /public/time", msg, [
6577
+ "OKX API may be down or blocked in your network",
6578
+ `Try: curl ${config.baseUrl}/api/v5/public/time`
6579
+ ]);
6580
+ passed = false;
6581
+ report.add("api", `FAIL /public/time ${msg} (${ms}ms)`);
6582
+ }
6583
+ return passed;
6584
+ }
6585
+ function getAuthHints(msg) {
6586
+ if (msg.includes("50111") || msg.includes("Invalid OK-ACCESS-KEY")) {
6587
+ return ["API key is invalid or expired", "Regenerate at https://www.okx.com/account/my-api"];
6588
+ }
6589
+ if (msg.includes("50112") || msg.includes("Invalid Sign")) {
6590
+ return ["Secret key or passphrase may be wrong", "Regenerate API key at https://www.okx.com/account/my-api"];
6591
+ }
6592
+ if (msg.includes("50113")) {
6593
+ return ["Passphrase is incorrect"];
6594
+ }
6595
+ if (msg.includes("50100")) {
6596
+ return ["API key lacks required permissions", "Update permissions at https://www.okx.com/account/my-api"];
6597
+ }
6598
+ return ["Check API credentials and permissions"];
6599
+ }
6600
+ async function checkAuth(client, config, report) {
6601
+ if (!config.hasAuth) {
6602
+ report.add("auth_api", "skipped (no credentials)");
6603
+ return true;
6604
+ }
6605
+ let passed = true;
6606
+ section("Authentication");
6607
+ const t1 = Date.now();
6608
+ try {
6609
+ await client.privateGet("/api/v5/account/balance");
6610
+ const ms = Date.now() - t1;
6611
+ ok("Account balance", `200 (${ms}ms)`);
6612
+ if (config.demo) {
6613
+ ok("Demo header", "x-simulated-trading: 1");
6614
+ }
6615
+ report.add("auth_api", `/account/balance 200 (${ms}ms)`);
6616
+ } catch (e) {
6617
+ const ms = Date.now() - t1;
6618
+ const msg = e instanceof Error ? e.message : String(e);
6619
+ const hints = getAuthHints(msg);
6620
+ fail("Account balance", msg, hints);
6621
+ passed = false;
6622
+ report.add("auth_api", `FAIL /account/balance ${msg} (${ms}ms)`);
6623
+ }
6624
+ return passed;
6625
+ }
6626
+ async function cmdDiagnose(config, profile) {
6627
+ process.stdout.write("\n OKX Trade CLI Diagnostics\n");
6628
+ process.stdout.write(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
6629
+ const report = new Report();
6630
+ report.add("ts", (/* @__PURE__ */ new Date()).toISOString());
6631
+ const envPassed = checkEnvironment(report);
6632
+ const cfgPassed = checkConfig(config, profile, report);
6633
+ const client = new OkxRestClient(config);
6634
+ const netPassed = await checkNetwork(config, client, report);
6635
+ const authPassed = await checkAuth(client, config, report);
6636
+ const allPassed = envPassed && cfgPassed && netPassed && authPassed;
6637
+ process.stdout.write("\n");
6638
+ if (allPassed) {
6639
+ process.stdout.write(" Result: All checks passed \u2713\n");
6640
+ } else {
6641
+ process.stdout.write(" Result: Some checks failed \u2717\n");
6642
+ process.exitCode = 1;
6643
+ }
6644
+ report.add("result", allPassed ? "PASS" : "FAIL");
6645
+ report.print();
6646
+ }
6647
+
5729
6648
  // src/config/loader.ts
5730
6649
  function loadProfileConfig(opts) {
5731
6650
  return loadConfig({
@@ -5735,32 +6654,11 @@ function loadProfileConfig(opts) {
5735
6654
  demo: opts.demo ?? false,
5736
6655
  site: opts.site,
5737
6656
  userAgent: opts.userAgent,
5738
- sourceTag: opts.sourceTag
6657
+ sourceTag: opts.sourceTag,
6658
+ verbose: opts.verbose
5739
6659
  });
5740
6660
  }
5741
6661
 
5742
- // src/tips.ts
5743
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
5744
- import { join as join4 } from "path";
5745
- import { homedir as homedir4 } from "os";
5746
- function showFirstRunTips(version) {
5747
- const okxDir = join4(homedir4(), ".okx");
5748
- const marker = join4(okxDir, ".tips-shown");
5749
- if (existsSync4(marker)) return;
5750
- const w = (s) => process.stderr.write(s);
5751
- w("\n");
5752
- w(` @okx_ai/okx-trade-cli v${version}
5753
- `);
5754
- w(" \u26A0\uFE0F Security Tips: NEVER send API keys in agent chat. Create a dedicated sub-account for your agent. Test on demo before going live.\n");
5755
- w(" \u26A0\uFE0F \u5B89\u5168\u63D0\u793A\uFF1A\u5207\u52FF\u5728Agent\u5BF9\u8BDD\u4E2D\u53D1\u9001API Key\u3002\u8BF7\u521B\u5EFAAgent\u4E13\u7528\u5B50\u8D26\u6237\u63A5\u5165\u3002\u5148\u5728\u6A21\u62DF\u76D8\u5145\u5206\u6D4B\u8BD5\uFF0C\u518D\u63A5\u5165\u5B9E\u76D8\u3002\n");
5756
- w("\n");
5757
- try {
5758
- mkdirSync4(okxDir, { recursive: true });
5759
- writeFileSync4(marker, (/* @__PURE__ */ new Date()).toISOString() + "\n");
5760
- } catch {
5761
- }
5762
- }
5763
-
5764
6662
  // src/commands/client-setup.ts
5765
6663
  import * as fs2 from "fs";
5766
6664
  var DETECTABLE_CLIENTS = ["claude-desktop", "cursor", "windsurf"];
@@ -5916,8 +6814,8 @@ var HELP_TREE = {
5916
6814
  description: "Get trade fill history for spot orders"
5917
6815
  },
5918
6816
  place: {
5919
- usage: "okx spot place --instId <id> --side <buy|sell> --ordType <type> --sz <n> [--px <price>] [--tdMode <cash|cross|isolated>]",
5920
- description: "Place a new spot order"
6817
+ usage: "okx spot place --instId <id> --side <buy|sell> --ordType <type> --sz <n> [--px <price>] [--tdMode <cash|cross|isolated>] [--tpTriggerPx <price>] [--tpOrdPx <price|-1>] [--slTriggerPx <price>] [--slOrdPx <price|-1>]",
6818
+ description: "Place a new spot order (supports attached TP/SL)"
5921
6819
  },
5922
6820
  amend: {
5923
6821
  usage: "okx spot amend --instId <id> --ordId <id> [--newSz <n>] [--newPx <price>]",
@@ -5976,8 +6874,8 @@ var HELP_TREE = {
5976
6874
  description: "Get trade fill history for swap orders"
5977
6875
  },
5978
6876
  place: {
5979
- usage: "okx swap place --instId <id> --side <buy|sell> --ordType <type> --sz <n> [--posSide <side>] [--px <price>] [--tdMode <cross|isolated>]",
5980
- description: "Place a new perpetual swap order"
6877
+ usage: "okx swap place --instId <id> --side <buy|sell> --ordType <type> --sz <n> [--posSide <side>] [--px <price>] [--tdMode <cross|isolated>] [--tpTriggerPx <price>] [--tpOrdPx <price|-1>] [--slTriggerPx <price>] [--slOrdPx <price|-1>]",
6878
+ description: "Place a new perpetual swap order (supports attached TP/SL)"
5981
6879
  },
5982
6880
  cancel: {
5983
6881
  usage: "okx swap cancel <instId> --ordId <id>",
@@ -6048,8 +6946,8 @@ var HELP_TREE = {
6048
6946
  description: "Get trade fill history for futures orders"
6049
6947
  },
6050
6948
  place: {
6051
- usage: "okx futures place --instId <id> --side <buy|sell> --ordType <type> --sz <n>\n [--tdMode <cross|isolated>] [--posSide <net|long|short>] [--px <price>] [--reduceOnly]",
6052
- description: "Place a new futures order"
6949
+ usage: "okx futures place --instId <id> --side <buy|sell> --ordType <type> --sz <n>\n [--tdMode <cross|isolated>] [--posSide <net|long|short>] [--px <price>] [--reduceOnly]\n [--tpTriggerPx <price>] [--tpOrdPx <price|-1>] [--slTriggerPx <price>] [--slOrdPx <price|-1>]",
6950
+ description: "Place a new futures order (supports attached TP/SL)"
6053
6951
  },
6054
6952
  cancel: {
6055
6953
  usage: "okx futures cancel <instId> --ordId <id>",
@@ -6106,6 +7004,73 @@ var HELP_TREE = {
6106
7004
  }
6107
7005
  }
6108
7006
  },
7007
+ earn: {
7008
+ description: "Earn products \u2014 Simple Earn (savings/lending) and On-chain Earn (staking/DeFi)",
7009
+ subgroups: {
7010
+ savings: {
7011
+ description: "Simple Earn \u2014 flexible savings and lending",
7012
+ commands: {
7013
+ balance: {
7014
+ usage: "okx earn savings balance [<ccy>]",
7015
+ description: "Get savings balance (optionally filter by currency)"
7016
+ },
7017
+ purchase: {
7018
+ usage: "okx earn savings purchase --ccy <ccy> --amt <n> [--rate <rate>]",
7019
+ description: "Purchase Simple Earn (flexible savings). Rate defaults to 0.01 (1%)"
7020
+ },
7021
+ redeem: {
7022
+ usage: "okx earn savings redeem --ccy <ccy> --amt <n>",
7023
+ description: "Redeem Simple Earn (flexible savings)"
7024
+ },
7025
+ "set-rate": {
7026
+ usage: "okx earn savings set-rate --ccy <ccy> --rate <rate>",
7027
+ description: "Set lending rate for a currency"
7028
+ },
7029
+ "lending-history": {
7030
+ usage: "okx earn savings lending-history [--ccy <ccy>] [--limit <n>]",
7031
+ description: "Get lending history"
7032
+ },
7033
+ "rate-summary": {
7034
+ usage: "okx earn savings rate-summary [<ccy>]",
7035
+ description: "Get market lending rate summary (public, no auth needed)"
7036
+ },
7037
+ "rate-history": {
7038
+ usage: "okx earn savings rate-history [--ccy <ccy>] [--limit <n>]",
7039
+ description: "Get historical lending rates (public, no auth needed)"
7040
+ }
7041
+ }
7042
+ },
7043
+ onchain: {
7044
+ description: "On-chain Earn \u2014 staking and DeFi products",
7045
+ commands: {
7046
+ offers: {
7047
+ usage: "okx earn onchain offers [--productId <id>] [--protocolType <type>] [--ccy <ccy>]",
7048
+ description: "Browse available on-chain earn products (staking, DeFi)"
7049
+ },
7050
+ purchase: {
7051
+ usage: "okx earn onchain purchase --productId <id> --ccy <ccy> --amt <n> [--term <term>] [--tag <tag>]",
7052
+ description: "Purchase an on-chain earn product (stake/deposit)"
7053
+ },
7054
+ redeem: {
7055
+ usage: "okx earn onchain redeem --ordId <id> --protocolType <type> [--allowEarlyRedeem]",
7056
+ description: "Redeem an on-chain earn position"
7057
+ },
7058
+ cancel: {
7059
+ usage: "okx earn onchain cancel --ordId <id> --protocolType <type>",
7060
+ description: "Cancel a pending on-chain earn order"
7061
+ },
7062
+ orders: {
7063
+ usage: "okx earn onchain orders [--productId <id>] [--protocolType <type>] [--ccy <ccy>] [--state <state>]",
7064
+ description: "List active on-chain earn orders"
7065
+ },
7066
+ history: {
7067
+ usage: "okx earn onchain history [--productId <id>] [--protocolType <type>] [--ccy <ccy>]",
7068
+ description: "Get on-chain earn order history"
7069
+ }
7070
+ }
7071
+ }
7072
+ }
7073
+ },
6109
7074
  bot: {
6110
7075
  description: "Trading bot strategies (grid, dca)",
6111
7076
  subgroups: {
@@ -6185,6 +7150,10 @@ var HELP_TREE = {
6185
7150
  setup: {
6186
7151
  description: "Set up client integrations (Cursor, Windsurf, Claude, etc.)",
6187
7152
  usage: `okx setup --client <${SUPPORTED_CLIENTS.join("|")}> [--profile <name>] [--modules <list>]`
7153
+ },
7154
+ diagnose: {
7155
+ description: "Run network diagnostics (DNS, TCP, TLS, API, auth)",
7156
+ usage: "okx diagnose [--profile <name>] [--demo]"
6188
7157
  }
6189
7158
  };
6190
7159
  function printGlobalHelp() {
@@ -6196,6 +7165,7 @@ function printGlobalHelp() {
6196
7165
  ` --profile <name> Use a named profile from ${configFilePath()}`,
6197
7166
  " --demo Use simulated trading (demo) mode",
6198
7167
  " --json Output raw JSON",
7168
+ " --verbose Show detailed network request/response info (stderr)",
6199
7169
  " --version, -v Show version",
6200
7170
  " --help Show this help",
6201
7171
  "",
@@ -6406,11 +7376,22 @@ var CLI_OPTIONS = {
6406
7376
  // batch
6407
7377
  action: { type: "string" },
6408
7378
  orders: { type: "string" },
7379
+ // earn
7380
+ rate: { type: "string" },
6409
7381
  // audit
6410
7382
  since: { type: "string" },
6411
7383
  tool: { type: "string" },
6412
7384
  // config profile
6413
- force: { type: "boolean", default: false }
7385
+ force: { type: "boolean", default: false },
7386
+ // onchain-earn
7387
+ productId: { type: "string" },
7388
+ protocolType: { type: "string" },
7389
+ term: { type: "string" },
7390
+ tag: { type: "string" },
7391
+ allowEarlyRedeem: { type: "boolean", default: false },
7392
+ state: { type: "string" },
7393
+ // diagnostics
7394
+ verbose: { type: "boolean", default: false }
6414
7395
  };
6415
7396
  function parseCli(argv) {
6416
7397
  const negated = /* @__PURE__ */ new Set();
@@ -6672,7 +7653,7 @@ async function cmdMarketCandles(run, instId, opts) {
6672
7653
  // src/commands/account.ts
6673
7654
  import * as fs4 from "fs";
6674
7655
  import * as path2 from "path";
6675
- import * as os2 from "os";
7656
+ import * as os4 from "os";
6676
7657
  function getData2(result) {
6677
7658
  return result.data;
6678
7659
  }
@@ -6878,7 +7859,7 @@ function readAuditLogs(logDir, days = 7) {
6878
7859
  return entries;
6879
7860
  }
6880
7861
  function cmdAccountAudit(opts) {
6881
- const logDir = path2.join(os2.homedir(), ".okx", "logs");
7862
+ const logDir = path2.join(os4.homedir(), ".okx", "logs");
6882
7863
  const limit = Math.min(Number(opts.limit) || 20, 100);
6883
7864
  let entries = readAuditLogs(logDir);
6884
7865
  if (opts.tool) entries = entries.filter((e) => e.tool === opts.tool);
@@ -6932,7 +7913,11 @@ async function cmdSpotPlace(run, opts) {
6932
7913
  side: opts.side,
6933
7914
  ordType: opts.ordType,
6934
7915
  sz: opts.sz,
6935
- px: opts.px
7916
+ px: opts.px,
7917
+ tpTriggerPx: opts.tpTriggerPx,
7918
+ tpOrdPx: opts.tpOrdPx,
7919
+ slTriggerPx: opts.slTriggerPx,
7920
+ slOrdPx: opts.slOrdPx
6936
7921
  });
6937
7922
  const data = getData3(result);
6938
7923
  if (opts.json) return printJson(data);
@@ -7157,7 +8142,11 @@ async function cmdSwapPlace(run, opts) {
7157
8142
  ordType: opts.ordType,
7158
8143
  sz: opts.sz,
7159
8144
  posSide: opts.posSide,
7160
- px: opts.px
8145
+ px: opts.px,
8146
+ tpTriggerPx: opts.tpTriggerPx,
8147
+ tpOrdPx: opts.tpOrdPx,
8148
+ slTriggerPx: opts.slTriggerPx,
8149
+ slOrdPx: opts.slOrdPx
7161
8150
  });
7162
8151
  const data = getData4(result);
7163
8152
  if (opts.json) return printJson(data);
@@ -7459,7 +8448,11 @@ async function cmdFuturesPlace(run, opts) {
7459
8448
  sz: opts.sz,
7460
8449
  posSide: opts.posSide,
7461
8450
  px: opts.px,
7462
- reduceOnly: opts.reduceOnly
8451
+ reduceOnly: opts.reduceOnly,
8452
+ tpTriggerPx: opts.tpTriggerPx,
8453
+ tpOrdPx: opts.tpOrdPx,
8454
+ slTriggerPx: opts.slTriggerPx,
8455
+ slOrdPx: opts.slOrdPx
7463
8456
  });
7464
8457
  const data = getData5(result);
7465
8458
  if (opts.json) return printJson(data);
@@ -8029,6 +9022,101 @@ function cmdConfigUse(profileName) {
8029
9022
  `);
8030
9023
  }
8031
9024
 
9025
+ // src/commands/earn.ts
9026
+ function extractData(result) {
9027
+ if (result && typeof result === "object") {
9028
+ const data = result["data"];
9029
+ if (Array.isArray(data)) return data;
9030
+ }
9031
+ return [];
9032
+ }
9033
+ function printDataList(data, json, emptyMsg, mapper) {
9034
+ if (json) {
9035
+ printJson(data);
9036
+ return;
9037
+ }
9038
+ if (!data.length) {
9039
+ process.stdout.write(emptyMsg + "\n");
9040
+ return;
9041
+ }
9042
+ printTable(data.map(mapper));
9043
+ }
9044
+ async function cmdEarnSavingsBalance(run, ccy, json) {
9045
+ const data = extractData(await run("earn_get_savings_balance", { ccy }));
9046
+ printDataList(data, json, "No savings balance", (r) => ({
9047
+ ccy: r["ccy"],
9048
+ amt: r["amt"],
9049
+ earnings: r["earnings"],
9050
+ rate: r["rate"],
9051
+ loanAmt: r["loanAmt"],
9052
+ pendingAmt: r["pendingAmt"]
9053
+ }));
9054
+ }
9055
+ async function cmdEarnSavingsPurchase(run, opts) {
9056
+ const data = extractData(await run("earn_savings_purchase", { ccy: opts.ccy, amt: opts.amt, rate: opts.rate }));
9057
+ if (opts.json) {
9058
+ printJson(data);
9059
+ return;
9060
+ }
9061
+ const r = data[0];
9062
+ if (!r) {
9063
+ process.stdout.write("No response data\n");
9064
+ return;
9065
+ }
9066
+ printKv({ ccy: r["ccy"], amt: r["amt"], side: r["side"], rate: r["rate"] });
9067
+ }
9068
+ async function cmdEarnSavingsRedeem(run, opts) {
9069
+ const data = extractData(await run("earn_savings_redeem", { ccy: opts.ccy, amt: opts.amt }));
9070
+ if (opts.json) {
9071
+ printJson(data);
9072
+ return;
9073
+ }
9074
+ const r = data[0];
9075
+ if (!r) {
9076
+ process.stdout.write("No response data\n");
9077
+ return;
9078
+ }
9079
+ printKv({ ccy: r["ccy"], amt: r["amt"], side: r["side"] });
9080
+ }
9081
+ async function cmdEarnSetLendingRate(run, opts) {
9082
+ const data = extractData(await run("earn_set_lending_rate", { ccy: opts.ccy, rate: opts.rate }));
9083
+ if (opts.json) {
9084
+ printJson(data);
9085
+ return;
9086
+ }
9087
+ const r = data[0];
9088
+ process.stdout.write(`Lending rate set: ${r?.["ccy"]} \u2192 ${r?.["rate"]}
9089
+ `);
9090
+ }
9091
+ async function cmdEarnLendingHistory(run, opts) {
9092
+ const data = extractData(await run("earn_get_lending_history", { ccy: opts.ccy, limit: opts.limit }));
9093
+ printDataList(data, opts.json, "No lending history", (r) => ({
9094
+ ccy: r["ccy"],
9095
+ amt: r["amt"],
9096
+ earnings: r["earnings"],
9097
+ rate: r["rate"],
9098
+ ts: new Date(Number(r["ts"])).toLocaleString()
9099
+ }));
9100
+ }
9101
+ async function cmdEarnLendingRateSummary(run, ccy, json) {
9102
+ const data = extractData(await run("earn_get_lending_rate_summary", { ccy }));
9103
+ printDataList(data, json, "No rate summary data", (r) => ({
9104
+ ccy: r["ccy"],
9105
+ avgRate: r["avgRate"],
9106
+ estRate: r["estRate"],
9107
+ avgAmt: r["avgAmt"]
9108
+ }));
9109
+ }
9110
+ async function cmdEarnLendingRateHistory(run, opts) {
9111
+ const data = extractData(await run("earn_get_lending_rate_history", { ccy: opts.ccy, limit: opts.limit }));
9112
+ printDataList(data, opts.json, "No rate history data", (r) => ({
9113
+ ccy: r["ccy"],
9114
+ lendingRate: r["lendingRate"],
9115
+ rate: r["rate"],
9116
+ ts: new Date(Number(r["ts"])).toLocaleString()
9117
+ }));
9118
+ }
9119
+
8032
9120
  // src/commands/bot.ts
8033
9121
  function getData7(result) {
8034
9122
  return result.data;
@@ -8260,10 +9348,56 @@ async function cmdDcaSubOrders(run, opts) {
8260
9348
  );
8261
9349
  }
8262
9350
 
9351
+ // src/commands/onchain-earn.ts
9352
+ function cmdOnchainEarnOffers(run, v) {
9353
+ return run("onchain_earn_get_offers", {
9354
+ productId: v.productId,
9355
+ protocolType: v.protocolType,
9356
+ ccy: v.ccy
9357
+ });
9358
+ }
9359
+ function cmdOnchainEarnPurchase(run, v) {
9360
+ const investData = v.ccy && v.amt ? [{ ccy: v.ccy, amt: v.amt }] : void 0;
9361
+ return run("onchain_earn_purchase", {
9362
+ productId: v.productId,
9363
+ investData,
9364
+ term: v.term,
9365
+ tag: v.tag
9366
+ });
9367
+ }
9368
+ function cmdOnchainEarnRedeem(run, v) {
9369
+ return run("onchain_earn_redeem", {
9370
+ ordId: v.ordId,
9371
+ protocolType: v.protocolType,
9372
+ allowEarlyRedeem: v.allowEarlyRedeem
9373
+ });
9374
+ }
9375
+ function cmdOnchainEarnCancel(run, v) {
9376
+ return run("onchain_earn_cancel", {
9377
+ ordId: v.ordId,
9378
+ protocolType: v.protocolType
9379
+ });
9380
+ }
9381
+ function cmdOnchainEarnActiveOrders(run, v) {
9382
+ return run("onchain_earn_get_active_orders", {
9383
+ productId: v.productId,
9384
+ protocolType: v.protocolType,
9385
+ ccy: v.ccy,
9386
+ state: v.state
9387
+ });
9388
+ }
9389
+ function cmdOnchainEarnOrderHistory(run, v) {
9390
+ return run("onchain_earn_get_order_history", {
9391
+ productId: v.productId,
9392
+ protocolType: v.protocolType,
9393
+ ccy: v.ccy
9394
+ });
9395
+ }
9396
+
8263
9397
  // src/index.ts
8264
- var _require = createRequire(import.meta.url);
8265
- var CLI_VERSION = _require("../package.json").version;
8266
- var GIT_HASH = true ? "c49054f" : "dev";
9398
+ var _require2 = createRequire2(import.meta.url);
9399
+ var CLI_VERSION2 = _require2("../package.json").version;
9400
+ var GIT_HASH2 = true ? "4427916" : "dev";
8267
9401
  function handleConfigCommand(action, rest, json, lang, force) {
8268
9402
  if (action === "init") return cmdConfigInit(lang === "zh" ? "zh" : "en");
8269
9403
  if (action === "show") return cmdConfigShow(json);
@@ -8435,6 +9569,10 @@ function handleSpotCommand(run, action, rest, v, json) {
8435
9569
  ordType: v.ordType,
8436
9570
  sz: v.sz,
8437
9571
  px: v.px,
9572
+ tpTriggerPx: v.tpTriggerPx,
9573
+ tpOrdPx: v.tpOrdPx,
9574
+ slTriggerPx: v.slTriggerPx,
9575
+ slOrdPx: v.slOrdPx,
8438
9576
  json
8439
9577
  });
8440
9578
  if (action === "cancel")
@@ -8531,6 +9669,10 @@ function handleSwapCommand(run, action, rest, v, json) {
8531
9669
  posSide: v.posSide,
8532
9670
  px: v.px,
8533
9671
  tdMode: v.tdMode ?? "cross",
9672
+ tpTriggerPx: v.tpTriggerPx,
9673
+ tpOrdPx: v.tpOrdPx,
9674
+ slTriggerPx: v.slTriggerPx,
9675
+ slOrdPx: v.slOrdPx,
8534
9676
  json
8535
9677
  });
8536
9678
  if (action === "cancel")
@@ -8625,6 +9767,10 @@ function handleFuturesCommand(run, action, rest, v, json) {
8625
9767
  posSide: v.posSide,
8626
9768
  px: v.px,
8627
9769
  reduceOnly: v.reduceOnly,
9770
+ tpTriggerPx: v.tpTriggerPx,
9771
+ tpOrdPx: v.tpOrdPx,
9772
+ slTriggerPx: v.slTriggerPx,
9773
+ slOrdPx: v.slOrdPx,
8628
9774
  json
8629
9775
  });
8630
9776
  if (action === "cancel")
@@ -8713,24 +9859,75 @@ function handleBotCommand(run, action, rest, v, json) {
8713
9859
  if (action === "grid") return handleBotGridCommand(run, v, rest, json);
8714
9860
  if (action === "dca") return handleBotDcaCommand(run, rest[0], v, json);
8715
9861
  }
9862
+ function handleEarnCommand(run, submodule, rest, v, json) {
9863
+ const action = rest[0];
9864
+ const innerRest = rest.slice(1);
9865
+ if (submodule === "savings") return handleEarnSavingsCommand(run, action, innerRest, v, json);
9866
+ if (submodule === "onchain") return handleEarnOnchainCommand(run, action, v, json);
9867
+ process.stderr.write(`Unknown earn sub-module: ${submodule}
9868
+ Valid: savings, onchain
9869
+ `);
9870
+ process.exitCode = 1;
9871
+ }
9872
+ function handleEarnSavingsCommand(run, action, rest, v, json) {
9873
+ const limit = v.limit !== void 0 ? Number(v.limit) : void 0;
9874
+ if (action === "balance") return cmdEarnSavingsBalance(run, rest[0] ?? v.ccy, json);
9875
+ if (action === "purchase") return cmdEarnSavingsPurchase(run, { ccy: v.ccy, amt: v.amt, rate: v.rate, json });
9876
+ if (action === "redeem") return cmdEarnSavingsRedeem(run, { ccy: v.ccy, amt: v.amt, json });
9877
+ if (action === "set-rate") return cmdEarnSetLendingRate(run, { ccy: v.ccy, rate: v.rate, json });
9878
+ if (action === "lending-history") return cmdEarnLendingHistory(run, { ccy: v.ccy, limit, json });
9879
+ if (action === "rate-summary") return cmdEarnLendingRateSummary(run, rest[0] ?? v.ccy, json);
9880
+ if (action === "rate-history") return cmdEarnLendingRateHistory(run, { ccy: v.ccy, limit, json });
9881
+ process.stderr.write(`Unknown earn savings command: ${action}
9882
+ `);
9883
+ process.exitCode = 1;
9884
+ }
9885
+ function handleEarnOnchainCommand(run, action, v, json) {
9886
+ if (action === "offers") return cmdOnchainEarnOffers(run, v).then((r) => outputResult(r, json));
9887
+ if (action === "purchase") return cmdOnchainEarnPurchase(run, v).then((r) => outputResult(r, json));
9888
+ if (action === "redeem") return cmdOnchainEarnRedeem(run, v).then((r) => outputResult(r, json));
9889
+ if (action === "cancel") return cmdOnchainEarnCancel(run, v).then((r) => outputResult(r, json));
9890
+ if (action === "orders") return cmdOnchainEarnActiveOrders(run, v).then((r) => outputResult(r, json));
9891
+ if (action === "history") return cmdOnchainEarnOrderHistory(run, v).then((r) => outputResult(r, json));
9892
+ process.stderr.write(`Unknown earn onchain command: ${action}
9893
+ `);
9894
+ process.exitCode = 1;
9895
+ }
9896
+ function outputResult(result, json) {
9897
+ if (json) {
9898
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
9899
+ } else {
9900
+ process.stdout.write(JSON.stringify(result.data, null, 2) + "\n");
9901
+ }
9902
+ }
9903
+ function printHelpForLevel(positionals) {
9904
+ const [module, subgroup] = positionals;
9905
+ if (!module) printHelp();
9906
+ else if (!subgroup) printHelp(module);
9907
+ else printHelp(module, subgroup);
9908
+ }
9909
+ function printVerboseConfigSummary(config, profile) {
9910
+ let authLabel = "\u2717";
9911
+ if (config.hasAuth && config.apiKey) {
9912
+ authLabel = `\u2713(${config.apiKey.slice(0, 3)}***${config.apiKey.slice(-3)})`;
9913
+ } else if (config.hasAuth) {
9914
+ authLabel = "\u2713";
9915
+ }
9916
+ process.stderr.write(
9917
+ `[verbose] config: profile=${profile ?? "default"} site=${config.site} base=${config.baseUrl} auth=${authLabel} demo=${config.demo ? "on" : "off"} modules=${config.modules.join(",")}
9918
+ `
9919
+ );
9920
+ }
8716
9921
  async function main() {
8717
- showFirstRunTips(CLI_VERSION);
8718
- checkForUpdates("@okx_ai/okx-trade-cli", CLI_VERSION);
9922
+ checkForUpdates("@okx_ai/okx-trade-cli", CLI_VERSION2);
8719
9923
  const { values, positionals } = parseCli(process.argv.slice(2));
8720
9924
  if (values.version) {
8721
- process.stdout.write(`${CLI_VERSION} (${GIT_HASH})
9925
+ process.stdout.write(`${CLI_VERSION2} (${GIT_HASH2})
8722
9926
  `);
8723
9927
  return;
8724
9928
  }
8725
9929
  if (values.help || positionals.length === 0) {
8726
- const [module2, subgroup] = positionals;
8727
- if (!module2) {
8728
- printHelp();
8729
- } else if (!subgroup) {
8730
- printHelp(module2);
8731
- } else {
8732
- printHelp(module2, subgroup);
8733
- }
9930
+ printHelpForLevel(positionals);
8734
9931
  return;
8735
9932
  }
8736
9933
  const [module, action, ...rest] = positionals;
@@ -8738,16 +9935,23 @@ async function main() {
8738
9935
  const json = v.json ?? false;
8739
9936
  if (module === "config") return handleConfigCommand(action, rest, json, v.lang, v.force);
8740
9937
  if (module === "setup") return handleSetupCommand(v);
8741
- const config = loadProfileConfig({ profile: v.profile, demo: v.demo, userAgent: `okx-trade-cli/${CLI_VERSION}`, sourceTag: "CLI" });
9938
+ const config = loadProfileConfig({ profile: v.profile, demo: v.demo, verbose: v.verbose, userAgent: `okx-trade-cli/${CLI_VERSION2}`, sourceTag: "CLI" });
9939
+ if (config.verbose) printVerboseConfigSummary(config, v.profile);
9940
+ if (module === "diagnose") return cmdDiagnose(config, v.profile ?? "default");
8742
9941
  const client = new OkxRestClient(config);
8743
9942
  const run = createToolRunner(client, config);
8744
- if (module === "market") return handleMarketCommand(run, action, rest, v, json);
8745
- if (module === "account") return handleAccountCommand(run, action, rest, v, json);
8746
- if (module === "spot") return handleSpotCommand(run, action, rest, v, json);
8747
- if (module === "swap") return handleSwapCommand(run, action, rest, v, json);
8748
- if (module === "futures") return handleFuturesCommand(run, action, rest, v, json);
8749
- if (module === "option") return handleOptionCommand(run, action, rest, v, json);
8750
- if (module === "bot") return handleBotCommand(run, action, rest, v, json);
9943
+ const moduleHandlers = {
9944
+ market: () => handleMarketCommand(run, action, rest, v, json),
9945
+ account: () => handleAccountCommand(run, action, rest, v, json),
9946
+ spot: () => handleSpotCommand(run, action, rest, v, json),
9947
+ swap: () => handleSwapCommand(run, action, rest, v, json),
9948
+ futures: () => handleFuturesCommand(run, action, rest, v, json),
9949
+ option: () => handleOptionCommand(run, action, rest, v, json),
9950
+ bot: () => handleBotCommand(run, action, rest, v, json),
9951
+ earn: () => handleEarnCommand(run, action, rest, v, json)
9952
+ };
9953
+ const handler = moduleHandlers[module];
9954
+ if (handler) return handler();
8751
9955
  process.stderr.write(`Unknown command: ${module} ${action ?? ""}
8752
9956
  `);
8753
9957
  process.exitCode = 1;
@@ -8760,7 +9964,7 @@ main().catch((error) => {
8760
9964
  `);
8761
9965
  if (payload.suggestion) process.stderr.write(`Hint: ${payload.suggestion}
8762
9966
  `);
8763
- process.stderr.write(`Version: @okx_ai/okx-trade-cli@${CLI_VERSION}
9967
+ process.stderr.write(`Version: @okx_ai/okx-trade-cli@${CLI_VERSION2}
8764
9968
  `);
8765
9969
  process.exitCode = 1;
8766
9970
  });
@@ -8770,6 +9974,7 @@ export {
8770
9974
  handleBotDcaCommand,
8771
9975
  handleBotGridCommand,
8772
9976
  handleConfigCommand,
9977
+ handleEarnCommand,
8773
9978
  handleMarketCommand,
8774
9979
  handleMarketDataCommand,
8775
9980
  handleMarketPublicCommand,