@okx_ai/okx-trade-cli 1.2.1 → 1.2.3-beta.1

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,7 +1,7 @@
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
7
  import { createHmac } from "crypto";
@@ -933,8 +933,10 @@ function sleep(ms) {
933
933
  var RateLimiter = class {
934
934
  buckets = /* @__PURE__ */ new Map();
935
935
  maxWaitMs;
936
- constructor(maxWaitMs = 3e4) {
936
+ verbose;
937
+ constructor(maxWaitMs = 3e4, verbose = false) {
937
938
  this.maxWaitMs = maxWaitMs;
939
+ this.verbose = verbose;
938
940
  }
939
941
  async consume(config, amount = 1) {
940
942
  const bucket = this.getBucket(config);
@@ -952,6 +954,10 @@ var RateLimiter = class {
952
954
  "Reduce tool call frequency or retry later."
953
955
  );
954
956
  }
957
+ if (this.verbose) {
958
+ process.stderr.write(`[verbose] \u23F3 rate-limit: waiting ${waitMs}ms for "${config.key}"
959
+ `);
960
+ }
955
961
  await sleep(waitMs);
956
962
  this.refill(bucket);
957
963
  if (bucket.tokens < amount) {
@@ -1046,11 +1052,34 @@ function buildQueryString(query) {
1046
1052
  }
1047
1053
  return params.toString();
1048
1054
  }
1055
+ function maskKey(key) {
1056
+ if (key.length <= 8) return "***";
1057
+ return `${key.slice(0, 3)}***${key.slice(-3)}`;
1058
+ }
1059
+ function vlog(message) {
1060
+ process.stderr.write(`[verbose] ${message}
1061
+ `);
1062
+ }
1049
1063
  var OkxRestClient = class {
1050
1064
  config;
1051
- rateLimiter = new RateLimiter();
1065
+ rateLimiter;
1052
1066
  constructor(config) {
1053
1067
  this.config = config;
1068
+ this.rateLimiter = new RateLimiter(3e4, config.verbose);
1069
+ }
1070
+ logRequest(method, url, auth) {
1071
+ if (!this.config.verbose) return;
1072
+ vlog(`\u2192 ${method} ${url}`);
1073
+ const authInfo = auth === "private" && this.config.apiKey ? `auth=\u2713(${maskKey(this.config.apiKey)})` : `auth=${auth}`;
1074
+ vlog(` ${authInfo} demo=${this.config.demo} timeout=${this.config.timeoutMs}ms`);
1075
+ }
1076
+ logResponse(status, rawLen, elapsed, traceId, code, msg) {
1077
+ if (!this.config.verbose) return;
1078
+ if (code && code !== "0" && code !== "1") {
1079
+ vlog(`\u2717 ${status} | code=${code} | msg=${msg ?? "-"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1080
+ } else {
1081
+ vlog(`\u2190 ${status} | code=${code ?? "0"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1082
+ }
1054
1083
  }
1055
1084
  async publicGet(path4, query, rateLimit) {
1056
1085
  return this.request({
@@ -1079,126 +1108,146 @@ var OkxRestClient = class {
1079
1108
  rateLimit
1080
1109
  });
1081
1110
  }
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);
1111
+ setAuthHeaders(headers, method, requestPath, bodyJson, timestamp) {
1112
+ if (!this.config.hasAuth) {
1113
+ throw new ConfigError(
1114
+ "Private endpoint requires API credentials.",
1115
+ "Configure OKX_API_KEY, OKX_SECRET_KEY and OKX_PASSPHRASE."
1116
+ );
1117
1117
  }
1118
- if (this.config.demo) {
1119
- headers.set("x-simulated-trading", "1");
1118
+ if (!this.config.apiKey || !this.config.secretKey || !this.config.passphrase) {
1119
+ throw new ConfigError(
1120
+ "Invalid private API credentials state.",
1121
+ "Ensure all OKX credentials are set."
1122
+ );
1120
1123
  }
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
1124
+ const payload = `${timestamp}${method.toUpperCase()}${requestPath}${bodyJson}`;
1125
+ const signature = signOkxPayload(payload, this.config.secretKey);
1126
+ headers.set("OK-ACCESS-KEY", this.config.apiKey);
1127
+ headers.set("OK-ACCESS-SIGN", signature);
1128
+ headers.set("OK-ACCESS-PASSPHRASE", this.config.passphrase);
1129
+ headers.set("OK-ACCESS-TIMESTAMP", timestamp);
1130
+ }
1131
+ throwOkxError(code, msg, reqConfig, traceId) {
1132
+ const message = msg || "OKX API request failed.";
1133
+ const endpoint = `${reqConfig.method} ${reqConfig.path}`;
1134
+ if (code === "50111" || code === "50112" || code === "50113") {
1135
+ throw new AuthenticationError(
1136
+ message,
1137
+ "Check API key, secret, passphrase and permissions.",
1138
+ endpoint,
1139
+ traceId
1134
1140
  );
1135
1141
  }
1136
- const rawText = await response.text();
1137
- const traceId = extractTraceId(response.headers);
1142
+ const behavior = OKX_CODE_BEHAVIORS[code];
1143
+ const suggestion = behavior?.suggestion?.replace("{site}", this.config.site);
1144
+ if (code === "50011" || code === "50061") {
1145
+ throw new RateLimitError(message, suggestion, endpoint, traceId);
1146
+ }
1147
+ throw new OkxApiError(message, {
1148
+ code,
1149
+ endpoint,
1150
+ suggestion,
1151
+ traceId
1152
+ });
1153
+ }
1154
+ processResponse(rawText, response, elapsed, traceId, reqConfig, requestPath) {
1138
1155
  let parsed;
1139
1156
  try {
1140
1157
  parsed = rawText ? JSON.parse(rawText) : {};
1141
1158
  } catch (error) {
1159
+ this.logResponse(response.status, rawText.length, elapsed, traceId, "non-JSON");
1142
1160
  if (!response.ok) {
1143
1161
  const messagePreview = rawText.slice(0, 160).replace(/\s+/g, " ").trim();
1144
1162
  throw new OkxApiError(
1145
1163
  `HTTP ${response.status} from OKX: ${messagePreview || "Non-JSON response body"}`,
1146
1164
  {
1147
1165
  code: String(response.status),
1148
- endpoint: `${config.method} ${config.path}`,
1166
+ endpoint: `${reqConfig.method} ${reqConfig.path}`,
1149
1167
  suggestion: "Verify endpoint path and request parameters.",
1150
1168
  traceId
1151
1169
  }
1152
1170
  );
1153
1171
  }
1154
1172
  throw new NetworkError(
1155
- `OKX returned non-JSON response for ${config.method} ${requestPath}.`,
1156
- `${config.method} ${requestPath}`,
1173
+ `OKX returned non-JSON response for ${reqConfig.method} ${requestPath}.`,
1174
+ `${reqConfig.method} ${requestPath}`,
1157
1175
  error
1158
1176
  );
1159
1177
  }
1160
1178
  if (!response.ok) {
1179
+ this.logResponse(response.status, rawText.length, elapsed, traceId, parsed.code ?? "-", parsed.msg);
1161
1180
  throw new OkxApiError(
1162
1181
  `HTTP ${response.status} from OKX: ${parsed.msg ?? "Unknown error"}`,
1163
1182
  {
1164
1183
  code: String(response.status),
1165
- endpoint: `${config.method} ${config.path}`,
1184
+ endpoint: `${reqConfig.method} ${reqConfig.path}`,
1166
1185
  suggestion: "Retry later or verify endpoint parameters.",
1167
1186
  traceId
1168
1187
  }
1169
1188
  );
1170
1189
  }
1171
1190
  const responseCode = parsed.code;
1191
+ this.logResponse(response.status, rawText.length, elapsed, traceId, responseCode, parsed.msg);
1172
1192
  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
- });
1193
+ this.throwOkxError(responseCode, parsed.msg, reqConfig, traceId);
1194
1194
  }
1195
1195
  return {
1196
- endpoint: `${config.method} ${config.path}`,
1196
+ endpoint: `${reqConfig.method} ${reqConfig.path}`,
1197
1197
  requestTime: (/* @__PURE__ */ new Date()).toISOString(),
1198
1198
  data: parsed.data ?? null,
1199
1199
  raw: parsed
1200
1200
  };
1201
1201
  }
1202
+ async request(reqConfig) {
1203
+ const queryString = buildQueryString(reqConfig.query);
1204
+ const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
1205
+ const url = `${this.config.baseUrl}${requestPath}`;
1206
+ const bodyJson = reqConfig.body ? JSON.stringify(reqConfig.body) : "";
1207
+ const timestamp = getNow();
1208
+ this.logRequest(reqConfig.method, url, reqConfig.auth);
1209
+ if (reqConfig.rateLimit) {
1210
+ await this.rateLimiter.consume(reqConfig.rateLimit);
1211
+ }
1212
+ const headers = new Headers({
1213
+ "Content-Type": "application/json",
1214
+ Accept: "application/json"
1215
+ });
1216
+ if (this.config.userAgent) {
1217
+ headers.set("User-Agent", this.config.userAgent);
1218
+ }
1219
+ if (reqConfig.auth === "private") {
1220
+ this.setAuthHeaders(headers, reqConfig.method, requestPath, bodyJson, timestamp);
1221
+ }
1222
+ if (this.config.demo) {
1223
+ headers.set("x-simulated-trading", "1");
1224
+ }
1225
+ const t0 = Date.now();
1226
+ let response;
1227
+ try {
1228
+ response = await fetch(url, {
1229
+ method: reqConfig.method,
1230
+ headers,
1231
+ body: reqConfig.method === "POST" ? bodyJson : void 0,
1232
+ signal: AbortSignal.timeout(this.config.timeoutMs)
1233
+ });
1234
+ } catch (error) {
1235
+ if (this.config.verbose) {
1236
+ const elapsed2 = Date.now() - t0;
1237
+ const cause = error instanceof Error ? error.message : String(error);
1238
+ vlog(`\u2717 NetworkError after ${elapsed2}ms: ${cause}`);
1239
+ }
1240
+ throw new NetworkError(
1241
+ `Failed to call OKX endpoint ${reqConfig.method} ${requestPath}.`,
1242
+ `${reqConfig.method} ${requestPath}`,
1243
+ error
1244
+ );
1245
+ }
1246
+ const rawText = await response.text();
1247
+ const elapsed = Date.now() - t0;
1248
+ const traceId = extractTraceId(response.headers);
1249
+ return this.processResponse(rawText, response, elapsed, traceId, reqConfig, requestPath);
1250
+ }
1202
1251
  };
1203
1252
  var DEFAULT_SOURCE_TAG = "MCP";
1204
1253
  var OKX_SITES = {
@@ -1224,6 +1273,10 @@ var BOT_SUB_MODULE_IDS = [
1224
1273
  "bot.dca"
1225
1274
  ];
1226
1275
  var BOT_DEFAULT_SUB_MODULES = ["bot.grid"];
1276
+ var EARN_SUB_MODULE_IDS = [
1277
+ "earn.savings",
1278
+ "earn.onchain"
1279
+ ];
1227
1280
  var MODULES = [
1228
1281
  "market",
1229
1282
  "spot",
@@ -1231,6 +1284,7 @@ var MODULES = [
1231
1284
  "futures",
1232
1285
  "option",
1233
1286
  "account",
1287
+ ...EARN_SUB_MODULE_IDS,
1234
1288
  ...BOT_SUB_MODULE_IDS
1235
1289
  ];
1236
1290
  var DEFAULT_MODULES = ["spot", "swap", "option", "account", ...BOT_DEFAULT_SUB_MODULES];
@@ -1296,6 +1350,13 @@ function compactObject(object) {
1296
1350
  }
1297
1351
  return next;
1298
1352
  }
1353
+ function normalizeResponse(response) {
1354
+ return {
1355
+ endpoint: response.endpoint,
1356
+ requestTime: response.requestTime,
1357
+ data: response.data
1358
+ };
1359
+ }
1299
1360
  var OKX_CANDLE_BARS = [
1300
1361
  "1m",
1301
1362
  "3m",
@@ -1335,6 +1396,14 @@ function privateRateLimit(key, rps = 10) {
1335
1396
  refillPerSecond: rps
1336
1397
  };
1337
1398
  }
1399
+ function assertNotDemo(config, endpoint) {
1400
+ if (config.demo) {
1401
+ throw new ConfigError(
1402
+ `"${endpoint}" is not supported in simulated trading mode.`,
1403
+ "Disable demo mode (remove OKX_DEMO=1 or --demo flag) to use this endpoint."
1404
+ );
1405
+ }
1406
+ }
1338
1407
  function normalize(response) {
1339
1408
  return {
1340
1409
  endpoint: response.endpoint,
@@ -2821,6 +2890,246 @@ function registerBotTools() {
2821
2890
  ...registerDcaTools()
2822
2891
  ];
2823
2892
  }
2893
+ function registerEarnTools() {
2894
+ return [
2895
+ {
2896
+ name: "earn_get_savings_balance",
2897
+ module: "earn.savings",
2898
+ 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.",
2899
+ isWrite: false,
2900
+ inputSchema: {
2901
+ type: "object",
2902
+ properties: {
2903
+ ccy: {
2904
+ type: "string",
2905
+ description: "e.g. USDT or BTC. Omit for all."
2906
+ }
2907
+ }
2908
+ },
2909
+ handler: async (rawArgs, context) => {
2910
+ const args = asRecord(rawArgs);
2911
+ const response = await context.client.privateGet(
2912
+ "/api/v5/finance/savings/balance",
2913
+ compactObject({ ccy: readString(args, "ccy") }),
2914
+ privateRateLimit("earn_get_savings_balance", 6)
2915
+ );
2916
+ return normalizeResponse(response);
2917
+ }
2918
+ },
2919
+ {
2920
+ name: "earn_savings_purchase",
2921
+ module: "earn.savings",
2922
+ 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.",
2923
+ isWrite: true,
2924
+ inputSchema: {
2925
+ type: "object",
2926
+ properties: {
2927
+ ccy: {
2928
+ type: "string",
2929
+ description: "Currency to purchase, e.g. USDT"
2930
+ },
2931
+ amt: {
2932
+ type: "string",
2933
+ description: "Purchase amount"
2934
+ },
2935
+ rate: {
2936
+ type: "string",
2937
+ description: "Lending rate. Annual rate in decimal, e.g. 0.01 = 1%. Defaults to 0.01 (1%, minimum rate, easiest to match)."
2938
+ }
2939
+ },
2940
+ required: ["ccy", "amt"]
2941
+ },
2942
+ handler: async (rawArgs, context) => {
2943
+ assertNotDemo(context.config, "earn_savings_purchase");
2944
+ const args = asRecord(rawArgs);
2945
+ const response = await context.client.privatePost(
2946
+ "/api/v5/finance/savings/purchase-redempt",
2947
+ compactObject({
2948
+ ccy: requireString(args, "ccy"),
2949
+ amt: requireString(args, "amt"),
2950
+ side: "purchase",
2951
+ rate: readString(args, "rate") ?? "0.01"
2952
+ }),
2953
+ privateRateLimit("earn_savings_purchase", 6)
2954
+ );
2955
+ return normalizeResponse(response);
2956
+ }
2957
+ },
2958
+ {
2959
+ name: "earn_savings_redeem",
2960
+ module: "earn.savings",
2961
+ 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.",
2962
+ isWrite: true,
2963
+ inputSchema: {
2964
+ type: "object",
2965
+ properties: {
2966
+ ccy: {
2967
+ type: "string",
2968
+ description: "Currency to redeem, e.g. USDT"
2969
+ },
2970
+ amt: {
2971
+ type: "string",
2972
+ description: "Redemption amount"
2973
+ }
2974
+ },
2975
+ required: ["ccy", "amt"]
2976
+ },
2977
+ handler: async (rawArgs, context) => {
2978
+ assertNotDemo(context.config, "earn_savings_redeem");
2979
+ const args = asRecord(rawArgs);
2980
+ const response = await context.client.privatePost(
2981
+ "/api/v5/finance/savings/purchase-redempt",
2982
+ compactObject({
2983
+ ccy: requireString(args, "ccy"),
2984
+ amt: requireString(args, "amt"),
2985
+ side: "redempt"
2986
+ }),
2987
+ privateRateLimit("earn_savings_redeem", 6)
2988
+ );
2989
+ return normalizeResponse(response);
2990
+ }
2991
+ },
2992
+ {
2993
+ name: "earn_set_lending_rate",
2994
+ module: "earn.savings",
2995
+ 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.",
2996
+ isWrite: true,
2997
+ inputSchema: {
2998
+ type: "object",
2999
+ properties: {
3000
+ ccy: {
3001
+ type: "string",
3002
+ description: "Currency, e.g. USDT"
3003
+ },
3004
+ rate: {
3005
+ type: "string",
3006
+ description: "Lending rate. Annual rate in decimal, e.g. 0.01 = 1%"
3007
+ }
3008
+ },
3009
+ required: ["ccy", "rate"]
3010
+ },
3011
+ handler: async (rawArgs, context) => {
3012
+ assertNotDemo(context.config, "earn_set_lending_rate");
3013
+ const args = asRecord(rawArgs);
3014
+ const response = await context.client.privatePost(
3015
+ "/api/v5/finance/savings/set-lending-rate",
3016
+ {
3017
+ ccy: requireString(args, "ccy"),
3018
+ rate: requireString(args, "rate")
3019
+ },
3020
+ privateRateLimit("earn_set_lending_rate", 6)
3021
+ );
3022
+ return normalizeResponse(response);
3023
+ }
3024
+ },
3025
+ {
3026
+ name: "earn_get_lending_history",
3027
+ module: "earn.savings",
3028
+ description: "Get lending history for Simple Earn. Returns lending records with details like amount, rate, and earnings. Private endpoint. Rate limit: 6 req/s.",
3029
+ isWrite: false,
3030
+ inputSchema: {
3031
+ type: "object",
3032
+ properties: {
3033
+ ccy: {
3034
+ type: "string",
3035
+ description: "e.g. USDT. Omit for all."
3036
+ },
3037
+ after: {
3038
+ type: "string",
3039
+ description: "Pagination: before this record ID"
3040
+ },
3041
+ before: {
3042
+ type: "string",
3043
+ description: "Pagination: after this record ID"
3044
+ },
3045
+ limit: {
3046
+ type: "number",
3047
+ description: "Max results (default 100)"
3048
+ }
3049
+ }
3050
+ },
3051
+ handler: async (rawArgs, context) => {
3052
+ const args = asRecord(rawArgs);
3053
+ const response = await context.client.privateGet(
3054
+ "/api/v5/finance/savings/lending-history",
3055
+ compactObject({
3056
+ ccy: readString(args, "ccy"),
3057
+ after: readString(args, "after"),
3058
+ before: readString(args, "before"),
3059
+ limit: readNumber(args, "limit")
3060
+ }),
3061
+ privateRateLimit("earn_get_lending_history", 6)
3062
+ );
3063
+ return normalizeResponse(response);
3064
+ }
3065
+ },
3066
+ {
3067
+ name: "earn_get_lending_rate_summary",
3068
+ module: "earn.savings",
3069
+ 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.",
3070
+ isWrite: false,
3071
+ inputSchema: {
3072
+ type: "object",
3073
+ properties: {
3074
+ ccy: {
3075
+ type: "string",
3076
+ description: "e.g. USDT. Omit for all."
3077
+ }
3078
+ }
3079
+ },
3080
+ handler: async (rawArgs, context) => {
3081
+ const args = asRecord(rawArgs);
3082
+ const response = await context.client.publicGet(
3083
+ "/api/v5/finance/savings/lending-rate-summary",
3084
+ compactObject({ ccy: readString(args, "ccy") }),
3085
+ publicRateLimit("earn_get_lending_rate_summary", 6)
3086
+ );
3087
+ return normalizeResponse(response);
3088
+ }
3089
+ },
3090
+ {
3091
+ name: "earn_get_lending_rate_history",
3092
+ module: "earn.savings",
3093
+ 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.",
3094
+ isWrite: false,
3095
+ inputSchema: {
3096
+ type: "object",
3097
+ properties: {
3098
+ ccy: {
3099
+ type: "string",
3100
+ description: "e.g. USDT. Omit for all."
3101
+ },
3102
+ after: {
3103
+ type: "string",
3104
+ description: "Pagination: before this timestamp (ms)"
3105
+ },
3106
+ before: {
3107
+ type: "string",
3108
+ description: "Pagination: after this timestamp (ms)"
3109
+ },
3110
+ limit: {
3111
+ type: "number",
3112
+ description: "Max results (default 100)"
3113
+ }
3114
+ }
3115
+ },
3116
+ handler: async (rawArgs, context) => {
3117
+ const args = asRecord(rawArgs);
3118
+ const response = await context.client.publicGet(
3119
+ "/api/v5/finance/savings/lending-rate-history",
3120
+ compactObject({
3121
+ ccy: readString(args, "ccy"),
3122
+ after: readString(args, "after"),
3123
+ before: readString(args, "before"),
3124
+ limit: readNumber(args, "limit")
3125
+ }),
3126
+ publicRateLimit("earn_get_lending_rate_history", 6)
3127
+ );
3128
+ return normalizeResponse(response);
3129
+ }
3130
+ }
3131
+ ];
3132
+ }
2824
3133
  var FUTURES_INST_TYPES = ["FUTURES", "SWAP"];
2825
3134
  function normalize5(response) {
2826
3135
  return {
@@ -3073,115 +3382,387 @@ function registerFuturesTools() {
3073
3382
  }),
3074
3383
  privateRateLimit("futures_get_orders", 20)
3075
3384
  );
3076
- return normalize5(response);
3385
+ return normalize5(response);
3386
+ }
3387
+ },
3388
+ {
3389
+ name: "futures_get_positions",
3390
+ module: "futures",
3391
+ description: "Get current FUTURES delivery contract positions. Private endpoint. Rate limit: 10 req/s.",
3392
+ isWrite: false,
3393
+ inputSchema: {
3394
+ type: "object",
3395
+ properties: {
3396
+ instType: {
3397
+ type: "string",
3398
+ enum: [...FUTURES_INST_TYPES],
3399
+ description: "FUTURES (default) or SWAP"
3400
+ },
3401
+ instId: {
3402
+ type: "string",
3403
+ description: "e.g. BTC-USDT-240329"
3404
+ },
3405
+ posId: {
3406
+ type: "string"
3407
+ }
3408
+ }
3409
+ },
3410
+ handler: async (rawArgs, context) => {
3411
+ const args = asRecord(rawArgs);
3412
+ const instType = readString(args, "instType") ?? "FUTURES";
3413
+ assertEnum(instType, "instType", FUTURES_INST_TYPES);
3414
+ const response = await context.client.privateGet(
3415
+ "/api/v5/account/positions",
3416
+ compactObject({
3417
+ instType,
3418
+ instId: readString(args, "instId"),
3419
+ posId: readString(args, "posId")
3420
+ }),
3421
+ privateRateLimit("futures_get_positions", 10)
3422
+ );
3423
+ return normalize5(response);
3424
+ }
3425
+ },
3426
+ {
3427
+ name: "futures_get_fills",
3428
+ module: "futures",
3429
+ description: "Get FUTURES fill details. archive=false: last 3 days. archive=true: up to 3 months. Private. Rate limit: 20 req/s.",
3430
+ isWrite: false,
3431
+ inputSchema: {
3432
+ type: "object",
3433
+ properties: {
3434
+ archive: {
3435
+ type: "boolean",
3436
+ description: "true=up to 3 months; false=last 3 days (default)"
3437
+ },
3438
+ instType: {
3439
+ type: "string",
3440
+ enum: [...FUTURES_INST_TYPES],
3441
+ description: "FUTURES (default) or SWAP"
3442
+ },
3443
+ instId: {
3444
+ type: "string",
3445
+ description: "Instrument ID filter"
3446
+ },
3447
+ ordId: {
3448
+ type: "string",
3449
+ description: "Order ID filter"
3450
+ },
3451
+ after: {
3452
+ type: "string",
3453
+ description: "Pagination: before this bill ID"
3454
+ },
3455
+ before: {
3456
+ type: "string",
3457
+ description: "Pagination: after this bill ID"
3458
+ },
3459
+ begin: {
3460
+ type: "string",
3461
+ description: "Start time (ms)"
3462
+ },
3463
+ end: {
3464
+ type: "string",
3465
+ description: "End time (ms)"
3466
+ },
3467
+ limit: {
3468
+ type: "number",
3469
+ description: "Max results (default 100 or 20 for archive)"
3470
+ }
3471
+ }
3472
+ },
3473
+ handler: async (rawArgs, context) => {
3474
+ const args = asRecord(rawArgs);
3475
+ const archive = readBoolean(args, "archive") ?? false;
3476
+ const instType = readString(args, "instType") ?? "FUTURES";
3477
+ assertEnum(instType, "instType", FUTURES_INST_TYPES);
3478
+ const path4 = archive ? "/api/v5/trade/fills-history" : "/api/v5/trade/fills";
3479
+ const response = await context.client.privateGet(
3480
+ path4,
3481
+ compactObject({
3482
+ instType,
3483
+ instId: readString(args, "instId"),
3484
+ ordId: readString(args, "ordId"),
3485
+ after: readString(args, "after"),
3486
+ before: readString(args, "before"),
3487
+ begin: readString(args, "begin"),
3488
+ end: readString(args, "end"),
3489
+ limit: readNumber(args, "limit") ?? (archive ? 20 : void 0)
3490
+ }),
3491
+ privateRateLimit("futures_get_fills", 20)
3492
+ );
3493
+ return normalize5(response);
3494
+ }
3495
+ }
3496
+ ];
3497
+ }
3498
+ function registerOnchainEarnTools() {
3499
+ return [
3500
+ // -------------------------------------------------------------------------
3501
+ // Get Offers
3502
+ // -------------------------------------------------------------------------
3503
+ {
3504
+ name: "onchain_earn_get_offers",
3505
+ module: "earn.onchain",
3506
+ description: "Get available on-chain earn (staking/DeFi) offers. Returns investment products with APY, terms, and limits. Private endpoint. Rate limit: 3 req/s.",
3507
+ isWrite: false,
3508
+ inputSchema: {
3509
+ type: "object",
3510
+ properties: {
3511
+ productId: {
3512
+ type: "string",
3513
+ description: "Specific product ID to query. Omit for all offers."
3514
+ },
3515
+ protocolType: {
3516
+ type: "string",
3517
+ description: "Protocol type filter: staking, defi. Omit for all types."
3518
+ },
3519
+ ccy: {
3520
+ type: "string",
3521
+ description: "Currency filter, e.g. ETH. Omit for all currencies."
3522
+ }
3523
+ }
3524
+ },
3525
+ handler: async (rawArgs, context) => {
3526
+ const args = asRecord(rawArgs);
3527
+ const response = await context.client.privateGet(
3528
+ "/api/v5/finance/staking-defi/offers",
3529
+ compactObject({
3530
+ productId: readString(args, "productId"),
3531
+ protocolType: readString(args, "protocolType"),
3532
+ ccy: readString(args, "ccy")
3533
+ }),
3534
+ privateRateLimit("onchain_earn_get_offers", 3)
3535
+ );
3536
+ return normalizeResponse(response);
3537
+ }
3538
+ },
3539
+ // -------------------------------------------------------------------------
3540
+ // Purchase
3541
+ // -------------------------------------------------------------------------
3542
+ {
3543
+ name: "onchain_earn_purchase",
3544
+ module: "earn.onchain",
3545
+ 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.",
3546
+ isWrite: true,
3547
+ inputSchema: {
3548
+ type: "object",
3549
+ properties: {
3550
+ productId: {
3551
+ type: "string",
3552
+ description: "Product ID to purchase"
3553
+ },
3554
+ investData: {
3555
+ type: "array",
3556
+ description: "Investment data array: [{ccy, amt}]. Each item specifies currency and amount.",
3557
+ items: {
3558
+ type: "object",
3559
+ properties: {
3560
+ ccy: { type: "string", description: "Currency, e.g. ETH" },
3561
+ amt: { type: "string", description: "Amount to invest" }
3562
+ },
3563
+ required: ["ccy", "amt"]
3564
+ }
3565
+ },
3566
+ term: {
3567
+ type: "string",
3568
+ description: "Investment term in days. Required for fixed-term products."
3569
+ },
3570
+ tag: {
3571
+ type: "string",
3572
+ description: "Order tag for tracking (optional)."
3573
+ }
3574
+ },
3575
+ required: ["productId", "investData"]
3576
+ },
3577
+ handler: async (rawArgs, context) => {
3578
+ assertNotDemo(context.config, "onchain_earn_purchase");
3579
+ const args = asRecord(rawArgs);
3580
+ const response = await context.client.privatePost(
3581
+ "/api/v5/finance/staking-defi/purchase",
3582
+ compactObject({
3583
+ productId: requireString(args, "productId"),
3584
+ investData: args.investData,
3585
+ term: readString(args, "term"),
3586
+ tag: readString(args, "tag")
3587
+ }),
3588
+ privateRateLimit("onchain_earn_purchase", 2)
3589
+ );
3590
+ return normalizeResponse(response);
3591
+ }
3592
+ },
3593
+ // -------------------------------------------------------------------------
3594
+ // Redeem
3595
+ // -------------------------------------------------------------------------
3596
+ {
3597
+ name: "onchain_earn_redeem",
3598
+ module: "earn.onchain",
3599
+ 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.",
3600
+ isWrite: true,
3601
+ inputSchema: {
3602
+ type: "object",
3603
+ properties: {
3604
+ ordId: {
3605
+ type: "string",
3606
+ description: "Order ID to redeem"
3607
+ },
3608
+ protocolType: {
3609
+ type: "string",
3610
+ description: "Protocol type: staking, defi"
3611
+ },
3612
+ allowEarlyRedeem: {
3613
+ type: "boolean",
3614
+ description: "Allow early redemption for fixed-term products (may incur penalties). Default false."
3615
+ }
3616
+ },
3617
+ required: ["ordId", "protocolType"]
3618
+ },
3619
+ handler: async (rawArgs, context) => {
3620
+ assertNotDemo(context.config, "onchain_earn_redeem");
3621
+ const args = asRecord(rawArgs);
3622
+ const response = await context.client.privatePost(
3623
+ "/api/v5/finance/staking-defi/redeem",
3624
+ compactObject({
3625
+ ordId: requireString(args, "ordId"),
3626
+ protocolType: requireString(args, "protocolType"),
3627
+ allowEarlyRedeem: readBoolean(args, "allowEarlyRedeem")
3628
+ }),
3629
+ privateRateLimit("onchain_earn_redeem", 2)
3630
+ );
3631
+ return normalizeResponse(response);
3632
+ }
3633
+ },
3634
+ // -------------------------------------------------------------------------
3635
+ // Cancel
3636
+ // -------------------------------------------------------------------------
3637
+ {
3638
+ name: "onchain_earn_cancel",
3639
+ module: "earn.onchain",
3640
+ 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.",
3641
+ isWrite: true,
3642
+ inputSchema: {
3643
+ type: "object",
3644
+ properties: {
3645
+ ordId: {
3646
+ type: "string",
3647
+ description: "Order ID to cancel"
3648
+ },
3649
+ protocolType: {
3650
+ type: "string",
3651
+ description: "Protocol type: staking, defi"
3652
+ }
3653
+ },
3654
+ required: ["ordId", "protocolType"]
3655
+ },
3656
+ handler: async (rawArgs, context) => {
3657
+ assertNotDemo(context.config, "onchain_earn_cancel");
3658
+ const args = asRecord(rawArgs);
3659
+ const response = await context.client.privatePost(
3660
+ "/api/v5/finance/staking-defi/cancel",
3661
+ {
3662
+ ordId: requireString(args, "ordId"),
3663
+ protocolType: requireString(args, "protocolType")
3664
+ },
3665
+ privateRateLimit("onchain_earn_cancel", 2)
3666
+ );
3667
+ return normalizeResponse(response);
3077
3668
  }
3078
3669
  },
3670
+ // -------------------------------------------------------------------------
3671
+ // Get Active Orders
3672
+ // -------------------------------------------------------------------------
3079
3673
  {
3080
- name: "futures_get_positions",
3081
- module: "futures",
3082
- description: "Get current FUTURES delivery contract positions. Private endpoint. Rate limit: 10 req/s.",
3674
+ name: "onchain_earn_get_active_orders",
3675
+ module: "earn.onchain",
3676
+ description: "Get active on-chain earn orders. Returns current staking/DeFi investments. Private endpoint. Rate limit: 3 req/s.",
3083
3677
  isWrite: false,
3084
3678
  inputSchema: {
3085
3679
  type: "object",
3086
3680
  properties: {
3087
- instType: {
3681
+ productId: {
3088
3682
  type: "string",
3089
- enum: [...FUTURES_INST_TYPES],
3090
- description: "FUTURES (default) or SWAP"
3683
+ description: "Filter by product ID. Omit for all."
3091
3684
  },
3092
- instId: {
3685
+ protocolType: {
3093
3686
  type: "string",
3094
- description: "e.g. BTC-USDT-240329"
3687
+ description: "Filter by protocol type: staking, defi. Omit for all."
3095
3688
  },
3096
- posId: {
3097
- type: "string"
3689
+ ccy: {
3690
+ type: "string",
3691
+ description: "Filter by currency, e.g. ETH. Omit for all."
3692
+ },
3693
+ state: {
3694
+ type: "string",
3695
+ description: "Filter by state: 8 (pending), 13 (cancelling), 9 (onchain), 1 (earning), 2 (redeeming). Omit for all."
3098
3696
  }
3099
3697
  }
3100
3698
  },
3101
3699
  handler: async (rawArgs, context) => {
3102
3700
  const args = asRecord(rawArgs);
3103
- const instType = readString(args, "instType") ?? "FUTURES";
3104
- assertEnum(instType, "instType", FUTURES_INST_TYPES);
3105
3701
  const response = await context.client.privateGet(
3106
- "/api/v5/account/positions",
3702
+ "/api/v5/finance/staking-defi/orders-active",
3107
3703
  compactObject({
3108
- instType,
3109
- instId: readString(args, "instId"),
3110
- posId: readString(args, "posId")
3704
+ productId: readString(args, "productId"),
3705
+ protocolType: readString(args, "protocolType"),
3706
+ ccy: readString(args, "ccy"),
3707
+ state: readString(args, "state")
3111
3708
  }),
3112
- privateRateLimit("futures_get_positions", 10)
3709
+ privateRateLimit("onchain_earn_get_active_orders", 3)
3113
3710
  );
3114
- return normalize5(response);
3711
+ return normalizeResponse(response);
3115
3712
  }
3116
3713
  },
3714
+ // -------------------------------------------------------------------------
3715
+ // Get Order History
3716
+ // -------------------------------------------------------------------------
3117
3717
  {
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.",
3718
+ name: "onchain_earn_get_order_history",
3719
+ module: "earn.onchain",
3720
+ description: "Get on-chain earn order history. Returns past staking/DeFi investments including redeemed orders. Private endpoint. Rate limit: 3 req/s.",
3121
3721
  isWrite: false,
3122
3722
  inputSchema: {
3123
3723
  type: "object",
3124
3724
  properties: {
3125
- archive: {
3126
- type: "boolean",
3127
- description: "true=up to 3 months; false=last 3 days (default)"
3128
- },
3129
- instType: {
3725
+ productId: {
3130
3726
  type: "string",
3131
- enum: [...FUTURES_INST_TYPES],
3132
- description: "FUTURES (default) or SWAP"
3727
+ description: "Filter by product ID. Omit for all."
3133
3728
  },
3134
- instId: {
3729
+ protocolType: {
3135
3730
  type: "string",
3136
- description: "Instrument ID filter"
3731
+ description: "Filter by protocol type: staking, defi. Omit for all."
3137
3732
  },
3138
- ordId: {
3733
+ ccy: {
3139
3734
  type: "string",
3140
- description: "Order ID filter"
3735
+ description: "Filter by currency, e.g. ETH. Omit for all."
3141
3736
  },
3142
3737
  after: {
3143
3738
  type: "string",
3144
- description: "Pagination: before this bill ID"
3739
+ description: "Pagination: return results before this order ID"
3145
3740
  },
3146
3741
  before: {
3147
3742
  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)"
3743
+ description: "Pagination: return results after this order ID"
3157
3744
  },
3158
3745
  limit: {
3159
- type: "number",
3160
- description: "Max results (default 100 or 20 for archive)"
3746
+ type: "string",
3747
+ description: "Max results to return (default 100, max 100)"
3161
3748
  }
3162
3749
  }
3163
3750
  },
3164
3751
  handler: async (rawArgs, context) => {
3165
3752
  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
3753
  const response = await context.client.privateGet(
3171
- path4,
3754
+ "/api/v5/finance/staking-defi/orders-history",
3172
3755
  compactObject({
3173
- instType,
3174
- instId: readString(args, "instId"),
3175
- ordId: readString(args, "ordId"),
3756
+ productId: readString(args, "productId"),
3757
+ protocolType: readString(args, "protocolType"),
3758
+ ccy: readString(args, "ccy"),
3176
3759
  after: readString(args, "after"),
3177
3760
  before: readString(args, "before"),
3178
- begin: readString(args, "begin"),
3179
- end: readString(args, "end"),
3180
- limit: readNumber(args, "limit") ?? (archive ? 20 : void 0)
3761
+ limit: readString(args, "limit")
3181
3762
  }),
3182
- privateRateLimit("futures_get_fills", 20)
3763
+ privateRateLimit("onchain_earn_get_order_history", 3)
3183
3764
  );
3184
- return normalize5(response);
3765
+ return normalizeResponse(response);
3185
3766
  }
3186
3767
  }
3187
3768
  ];
@@ -5390,6 +5971,8 @@ function allToolSpecs() {
5390
5971
  ...registerAlgoTradeTools(),
5391
5972
  ...registerAccountTools(),
5392
5973
  ...registerBotTools(),
5974
+ ...registerEarnTools(),
5975
+ ...registerOnchainEarnTools(),
5393
5976
  ...registerAuditTools()
5394
5977
  ];
5395
5978
  }
@@ -5427,8 +6010,15 @@ function writeFullConfig(config) {
5427
6010
  writeFileSync(path4, stringify(config), "utf-8");
5428
6011
  }
5429
6012
  var BASE_MODULES = MODULES.filter(
5430
- (m) => !BOT_SUB_MODULE_IDS.includes(m)
6013
+ (m) => !BOT_SUB_MODULE_IDS.includes(m) && !EARN_SUB_MODULE_IDS.includes(m)
5431
6014
  );
6015
+ function expandShorthand(moduleId) {
6016
+ if (moduleId === "all") return [...BASE_MODULES, ...BOT_SUB_MODULE_IDS];
6017
+ if (moduleId === "earn" || moduleId === "earn.all") return [...EARN_SUB_MODULE_IDS];
6018
+ if (moduleId === "bot") return [...BOT_DEFAULT_SUB_MODULES];
6019
+ if (moduleId === "bot.all") return [...BOT_SUB_MODULE_IDS];
6020
+ return null;
6021
+ }
5432
6022
  function parseModuleList(rawModules) {
5433
6023
  if (!rawModules || rawModules.trim().length === 0) {
5434
6024
  return [...DEFAULT_MODULES];
@@ -5437,32 +6027,28 @@ function parseModuleList(rawModules) {
5437
6027
  if (trimmed === "all") {
5438
6028
  return [...BASE_MODULES, ...BOT_SUB_MODULE_IDS];
5439
6029
  }
5440
- const requested = trimmed.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
6030
+ const requested = trimmed.split(",").map((s) => s.trim()).filter(Boolean);
5441
6031
  if (requested.length === 0) {
5442
6032
  return [...DEFAULT_MODULES];
5443
6033
  }
5444
6034
  const deduped = /* @__PURE__ */ new Set();
5445
6035
  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);
6036
+ const expanded = expandShorthand(moduleId);
6037
+ if (expanded) {
6038
+ expanded.forEach((sub) => deduped.add(sub));
5452
6039
  continue;
5453
6040
  }
5454
6041
  if (!MODULES.includes(moduleId)) {
5455
6042
  throw new ConfigError(
5456
6043
  `Unknown module "${moduleId}".`,
5457
- `Use one of: ${MODULES.join(", ")}, "bot", "bot.all", or "all".`
6044
+ `Use one of: ${MODULES.join(", ")}, "earn", "earn.all", "bot", "bot.all", or "all".`
5458
6045
  );
5459
6046
  }
5460
6047
  deduped.add(moduleId);
5461
6048
  }
5462
6049
  return Array.from(deduped);
5463
6050
  }
5464
- function loadConfig(cli) {
5465
- const toml = readTomlProfile(cli.profile);
6051
+ function loadCredentials(toml) {
5466
6052
  const apiKey = process.env.OKX_API_KEY?.trim() ?? toml.api_key;
5467
6053
  const secretKey = process.env.OKX_SECRET_KEY?.trim() ?? toml.secret_key;
5468
6054
  const passphrase = process.env.OKX_PASSPHRASE?.trim() ?? toml.passphrase;
@@ -5474,23 +6060,34 @@ function loadConfig(cli) {
5474
6060
  "Set OKX_API_KEY, OKX_SECRET_KEY and OKX_PASSPHRASE together (env vars or config.toml profile)."
5475
6061
  );
5476
6062
  }
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";
6063
+ return { apiKey, secretKey, passphrase, hasAuth };
6064
+ }
6065
+ function resolveSite(cliSite, tomlSite) {
6066
+ const rawSite = cliSite?.trim() ?? process.env.OKX_SITE?.trim() ?? tomlSite ?? "global";
5479
6067
  if (!SITE_IDS.includes(rawSite)) {
5480
6068
  throw new ConfigError(
5481
6069
  `Unknown site "${rawSite}".`,
5482
6070
  `Use one of: ${SITE_IDS.join(", ")}.`
5483
6071
  );
5484
6072
  }
5485
- const site = rawSite;
5486
- const rawBaseUrl = process.env.OKX_API_BASE_URL?.trim() ?? toml.base_url ?? OKX_SITES[site].apiBaseUrl;
6073
+ return rawSite;
6074
+ }
6075
+ function resolveBaseUrl(site, tomlBaseUrl) {
6076
+ const rawBaseUrl = process.env.OKX_API_BASE_URL?.trim() ?? tomlBaseUrl ?? OKX_SITES[site].apiBaseUrl;
5487
6077
  if (!rawBaseUrl.startsWith("http://") && !rawBaseUrl.startsWith("https://")) {
5488
6078
  throw new ConfigError(
5489
6079
  `Invalid base URL "${rawBaseUrl}".`,
5490
6080
  "OKX_API_BASE_URL must start with http:// or https://"
5491
6081
  );
5492
6082
  }
5493
- const baseUrl = rawBaseUrl.replace(/\/+$/, "");
6083
+ return rawBaseUrl.replace(/\/+$/, "");
6084
+ }
6085
+ function loadConfig(cli) {
6086
+ const toml = readTomlProfile(cli.profile);
6087
+ const creds = loadCredentials(toml);
6088
+ const demo = cli.demo || process.env.OKX_DEMO === "1" || process.env.OKX_DEMO === "true" || (toml.demo ?? false);
6089
+ const site = resolveSite(cli.site, toml.site);
6090
+ const baseUrl = resolveBaseUrl(site, toml.base_url);
5494
6091
  const rawTimeout = process.env.OKX_TIMEOUT_MS ? Number(process.env.OKX_TIMEOUT_MS) : toml.timeout_ms ?? 15e3;
5495
6092
  if (!Number.isFinite(rawTimeout) || rawTimeout <= 0) {
5496
6093
  throw new ConfigError(
@@ -5499,10 +6096,7 @@ function loadConfig(cli) {
5499
6096
  );
5500
6097
  }
5501
6098
  return {
5502
- apiKey,
5503
- secretKey,
5504
- passphrase,
5505
- hasAuth,
6099
+ ...creds,
5506
6100
  baseUrl,
5507
6101
  timeoutMs: Math.floor(rawTimeout),
5508
6102
  modules: parseModuleList(cli.modules),
@@ -5510,7 +6104,8 @@ function loadConfig(cli) {
5510
6104
  demo,
5511
6105
  site,
5512
6106
  userAgent: cli.userAgent,
5513
- sourceTag: cli.sourceTag ?? DEFAULT_SOURCE_TAG
6107
+ sourceTag: cli.sourceTag ?? DEFAULT_SOURCE_TAG,
6108
+ verbose: cli.verbose ?? false
5514
6109
  };
5515
6110
  }
5516
6111
  var CACHE_FILE = join2(homedir2(), ".okx", "update-check.json");
@@ -5726,6 +6321,313 @@ function runSetup(options) {
5726
6321
  }
5727
6322
  }
5728
6323
 
6324
+ // src/commands/diagnose.ts
6325
+ import dns from "dns/promises";
6326
+ import net from "net";
6327
+ import os2 from "os";
6328
+ import tls from "tls";
6329
+ import { createRequire } from "module";
6330
+ var _require = createRequire(import.meta.url);
6331
+ function readCliVersion() {
6332
+ for (const rel of ["../package.json", "../../package.json"]) {
6333
+ try {
6334
+ return _require(rel).version;
6335
+ } catch (_err) {
6336
+ }
6337
+ }
6338
+ return "0.0.0";
6339
+ }
6340
+ var CLI_VERSION = readCliVersion();
6341
+ var GIT_HASH = true ? "7777cc2" : "dev";
6342
+ var Report = class {
6343
+ lines = [];
6344
+ add(key, value) {
6345
+ this.lines.push({ key, value });
6346
+ }
6347
+ print() {
6348
+ const w = process.stdout.write.bind(process.stdout);
6349
+ const sep = "\u2500".repeat(52);
6350
+ w(`
6351
+ \u2500\u2500 Diagnostic Report (copy & share) ${sep.slice(35)}
6352
+ `);
6353
+ for (const { key, value } of this.lines) {
6354
+ w(` ${key.padEnd(14)} ${value}
6355
+ `);
6356
+ }
6357
+ w(` ${sep}
6358
+
6359
+ `);
6360
+ }
6361
+ };
6362
+ function ok(label, detail) {
6363
+ process.stdout.write(` \u2713 ${label.padEnd(14)} ${detail}
6364
+ `);
6365
+ }
6366
+ function fail(label, detail, hints) {
6367
+ process.stdout.write(` \u2717 ${label.padEnd(14)} ${detail}
6368
+ `);
6369
+ for (const hint of hints) {
6370
+ process.stdout.write(` \u2192 ${hint}
6371
+ `);
6372
+ }
6373
+ }
6374
+ function section(title) {
6375
+ process.stdout.write(`
6376
+ ${title}
6377
+ `);
6378
+ }
6379
+ function maskKey2(key) {
6380
+ if (!key) return "(not set)";
6381
+ if (key.length <= 8) return "****";
6382
+ return `${key.slice(0, 2)}****${key.slice(-2)}`;
6383
+ }
6384
+ async function checkDns(hostname) {
6385
+ const t0 = Date.now();
6386
+ try {
6387
+ const addresses = await dns.resolve4(hostname);
6388
+ return { ok: true, ip: addresses[0], ms: Date.now() - t0 };
6389
+ } catch (e) {
6390
+ return { ok: false, ms: Date.now() - t0, error: e instanceof Error ? e.message : String(e) };
6391
+ }
6392
+ }
6393
+ async function checkSocket(createFn, successEvent, timeoutMs) {
6394
+ const t0 = Date.now();
6395
+ return new Promise((resolve) => {
6396
+ const socket = createFn();
6397
+ const cleanup = () => {
6398
+ socket.removeAllListeners();
6399
+ socket.destroy();
6400
+ };
6401
+ socket.once(successEvent, () => {
6402
+ cleanup();
6403
+ resolve({ ok: true, ms: Date.now() - t0 });
6404
+ });
6405
+ socket.once("timeout", () => {
6406
+ cleanup();
6407
+ resolve({ ok: false, ms: Date.now() - t0, error: `timed out after ${timeoutMs}ms` });
6408
+ });
6409
+ socket.once("error", (err) => {
6410
+ cleanup();
6411
+ resolve({ ok: false, ms: Date.now() - t0, error: err.message });
6412
+ });
6413
+ });
6414
+ }
6415
+ async function checkTcp(hostname, port, timeoutMs = 5e3) {
6416
+ return checkSocket(
6417
+ () => net.createConnection({ host: hostname, port, timeout: timeoutMs }),
6418
+ "connect",
6419
+ timeoutMs
6420
+ );
6421
+ }
6422
+ async function checkTls(hostname, port, timeoutMs = 5e3) {
6423
+ return checkSocket(
6424
+ () => tls.connect({ host: hostname, port, timeout: timeoutMs, servername: hostname }),
6425
+ "secureConnect",
6426
+ timeoutMs
6427
+ );
6428
+ }
6429
+ function checkProxyEnv(report) {
6430
+ const httpProxy = process.env.HTTP_PROXY ?? process.env.http_proxy;
6431
+ const httpsProxy = process.env.HTTPS_PROXY ?? process.env.https_proxy;
6432
+ const noProxy = process.env.NO_PROXY ?? process.env.no_proxy;
6433
+ if (httpProxy || httpsProxy) {
6434
+ ok("HTTP_PROXY", httpProxy ?? "(not set)");
6435
+ ok("HTTPS_PROXY", httpsProxy ?? "(not set)");
6436
+ if (noProxy) ok("NO_PROXY", noProxy);
6437
+ report.add("http_proxy", httpProxy ?? "-");
6438
+ report.add("https_proxy", httpsProxy ?? "-");
6439
+ if (noProxy) report.add("no_proxy", noProxy);
6440
+ } else {
6441
+ ok("Proxy", "(none)");
6442
+ report.add("proxy", "none");
6443
+ }
6444
+ }
6445
+ function checkEnvironment(report) {
6446
+ let passed = true;
6447
+ section("Environment");
6448
+ const nodeVersion = process.version;
6449
+ const nodeMajor = parseInt(nodeVersion.slice(1), 10);
6450
+ if (nodeMajor >= 18) {
6451
+ ok("Node.js", `${nodeVersion} (>= 18 required)`);
6452
+ } else {
6453
+ fail("Node.js", `${nodeVersion} (>= 18 required)`, ["Upgrade Node.js to v18 or later"]);
6454
+ passed = false;
6455
+ }
6456
+ ok("CLI", `v${CLI_VERSION} (${GIT_HASH})`);
6457
+ ok("OS", `${process.platform} ${process.arch}`);
6458
+ ok("OS release", os2.release());
6459
+ ok("Shell", process.env.SHELL ?? "(unknown)");
6460
+ ok("Locale", `${process.env.LANG ?? process.env.LC_ALL ?? "(unknown)"}`);
6461
+ ok("Timezone", Intl.DateTimeFormat().resolvedOptions().timeZone);
6462
+ report.add("cli", `${CLI_VERSION} (${GIT_HASH})`);
6463
+ report.add("node", `${nodeVersion} ${process.platform} ${process.arch}`);
6464
+ const machine = typeof os2.machine === "function" ? os2.machine() : process.arch;
6465
+ report.add("os", `${os2.type()} ${os2.release()} ${machine}`);
6466
+ report.add("shell", process.env.SHELL ?? "-");
6467
+ report.add("locale", process.env.LANG ?? process.env.LC_ALL ?? "-");
6468
+ report.add("tz", Intl.DateTimeFormat().resolvedOptions().timeZone);
6469
+ checkProxyEnv(report);
6470
+ return passed;
6471
+ }
6472
+ function checkConfig(config, profile, report) {
6473
+ let passed = true;
6474
+ section(`Config (profile: ${profile})`);
6475
+ if (config.hasAuth) {
6476
+ ok("API key", maskKey2(config.apiKey));
6477
+ ok("Secret", "****");
6478
+ ok("Passphrase", "****");
6479
+ } else {
6480
+ fail("Credentials", "not configured", [
6481
+ "Set OKX_API_KEY, OKX_SECRET_KEY, OKX_PASSPHRASE env vars",
6482
+ "Or run: okx config init"
6483
+ ]);
6484
+ passed = false;
6485
+ }
6486
+ ok("Demo mode", String(config.demo));
6487
+ ok("Site", config.site);
6488
+ ok("Base URL", config.baseUrl);
6489
+ ok("Timeout", `${config.timeoutMs}ms`);
6490
+ report.add("profile", profile);
6491
+ report.add("site", config.site);
6492
+ report.add("base", config.baseUrl);
6493
+ report.add("auth", config.hasAuth ? `true (key=${maskKey2(config.apiKey)})` : "false");
6494
+ report.add("demo", String(config.demo));
6495
+ report.add("timeout", `${config.timeoutMs}ms`);
6496
+ return passed;
6497
+ }
6498
+ async function checkTcpTls(hostname, port, protocol, report) {
6499
+ let passed = true;
6500
+ const tcpResult = await checkTcp(hostname, port);
6501
+ if (tcpResult.ok) {
6502
+ ok("TCP connect", `port ${port} (${tcpResult.ms}ms)`);
6503
+ report.add("tcp", `${port} OK (${tcpResult.ms}ms)`);
6504
+ } else {
6505
+ fail("TCP connect", `port ${port} \u2014 ${tcpResult.error}`, [
6506
+ "Check firewall/proxy/VPN settings",
6507
+ `Try: nc -zv ${hostname} ${port}`
6508
+ ]);
6509
+ report.add("tcp", `FAIL ${port} ${tcpResult.error} (${tcpResult.ms}ms)`);
6510
+ return false;
6511
+ }
6512
+ if (protocol === "https:") {
6513
+ const tlsResult = await checkTls(hostname, port);
6514
+ if (tlsResult.ok) {
6515
+ ok("TLS handshake", `(${tlsResult.ms}ms)`);
6516
+ report.add("tls", `OK (${tlsResult.ms}ms)`);
6517
+ } else {
6518
+ fail("TLS handshake", tlsResult.error ?? "failed", [
6519
+ "Check system certificates or proxy MITM settings"
6520
+ ]);
6521
+ passed = false;
6522
+ report.add("tls", `FAIL ${tlsResult.error} (${tlsResult.ms}ms)`);
6523
+ }
6524
+ }
6525
+ return passed;
6526
+ }
6527
+ async function checkNetwork(config, client, report) {
6528
+ let passed = true;
6529
+ section("Network");
6530
+ const url = new URL(config.baseUrl);
6531
+ const hostname = url.hostname;
6532
+ const defaultPort = url.protocol === "https:" ? 443 : 80;
6533
+ const port = url.port ? parseInt(url.port, 10) : defaultPort;
6534
+ const dnsResult = await checkDns(hostname);
6535
+ if (dnsResult.ok) {
6536
+ ok("DNS resolve", `${hostname} \u2192 ${dnsResult.ip} (${dnsResult.ms}ms)`);
6537
+ report.add("dns", `${hostname} \u2192 ${dnsResult.ip} (${dnsResult.ms}ms)`);
6538
+ } else {
6539
+ fail("DNS resolve", `${hostname} \u2014 ${dnsResult.error}`, [
6540
+ "Check DNS settings or network connection",
6541
+ `Try: nslookup ${hostname}`
6542
+ ]);
6543
+ passed = false;
6544
+ report.add("dns", `FAIL ${hostname} ${dnsResult.error} (${dnsResult.ms}ms)`);
6545
+ }
6546
+ if (dnsResult.ok) {
6547
+ const tcpTlsPassed = await checkTcpTls(hostname, port, url.protocol, report);
6548
+ if (!tcpTlsPassed) passed = false;
6549
+ }
6550
+ const t0 = Date.now();
6551
+ try {
6552
+ await client.publicGet("/api/v5/public/time");
6553
+ const ms = Date.now() - t0;
6554
+ ok("API /public/time", `200 (${ms}ms)`);
6555
+ report.add("api", `/public/time 200 (${ms}ms)`);
6556
+ } catch (e) {
6557
+ const ms = Date.now() - t0;
6558
+ const msg = e instanceof Error ? e.message : String(e);
6559
+ fail("API /public/time", msg, [
6560
+ "OKX API may be down or blocked in your network",
6561
+ `Try: curl ${config.baseUrl}/api/v5/public/time`
6562
+ ]);
6563
+ passed = false;
6564
+ report.add("api", `FAIL /public/time ${msg} (${ms}ms)`);
6565
+ }
6566
+ return passed;
6567
+ }
6568
+ function getAuthHints(msg) {
6569
+ if (msg.includes("50111") || msg.includes("Invalid OK-ACCESS-KEY")) {
6570
+ return ["API key is invalid or expired", "Regenerate at https://www.okx.com/account/my-api"];
6571
+ }
6572
+ if (msg.includes("50112") || msg.includes("Invalid Sign")) {
6573
+ return ["Secret key or passphrase may be wrong", "Regenerate API key at https://www.okx.com/account/my-api"];
6574
+ }
6575
+ if (msg.includes("50113")) {
6576
+ return ["Passphrase is incorrect"];
6577
+ }
6578
+ if (msg.includes("50100")) {
6579
+ return ["API key lacks required permissions", "Update permissions at https://www.okx.com/account/my-api"];
6580
+ }
6581
+ return ["Check API credentials and permissions"];
6582
+ }
6583
+ async function checkAuth(client, config, report) {
6584
+ if (!config.hasAuth) {
6585
+ report.add("auth_api", "skipped (no credentials)");
6586
+ return true;
6587
+ }
6588
+ let passed = true;
6589
+ section("Authentication");
6590
+ const t1 = Date.now();
6591
+ try {
6592
+ await client.privateGet("/api/v5/account/balance");
6593
+ const ms = Date.now() - t1;
6594
+ ok("Account balance", `200 (${ms}ms)`);
6595
+ if (config.demo) {
6596
+ ok("Demo header", "x-simulated-trading: 1");
6597
+ }
6598
+ report.add("auth_api", `/account/balance 200 (${ms}ms)`);
6599
+ } catch (e) {
6600
+ const ms = Date.now() - t1;
6601
+ const msg = e instanceof Error ? e.message : String(e);
6602
+ const hints = getAuthHints(msg);
6603
+ fail("Account balance", msg, hints);
6604
+ passed = false;
6605
+ report.add("auth_api", `FAIL /account/balance ${msg} (${ms}ms)`);
6606
+ }
6607
+ return passed;
6608
+ }
6609
+ async function cmdDiagnose(config, profile) {
6610
+ process.stdout.write("\n OKX Trade CLI Diagnostics\n");
6611
+ 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");
6612
+ const report = new Report();
6613
+ report.add("ts", (/* @__PURE__ */ new Date()).toISOString());
6614
+ const envPassed = checkEnvironment(report);
6615
+ const cfgPassed = checkConfig(config, profile, report);
6616
+ const client = new OkxRestClient(config);
6617
+ const netPassed = await checkNetwork(config, client, report);
6618
+ const authPassed = await checkAuth(client, config, report);
6619
+ const allPassed = envPassed && cfgPassed && netPassed && authPassed;
6620
+ process.stdout.write("\n");
6621
+ if (allPassed) {
6622
+ process.stdout.write(" Result: All checks passed \u2713\n");
6623
+ } else {
6624
+ process.stdout.write(" Result: Some checks failed \u2717\n");
6625
+ process.exitCode = 1;
6626
+ }
6627
+ report.add("result", allPassed ? "PASS" : "FAIL");
6628
+ report.print();
6629
+ }
6630
+
5729
6631
  // src/config/loader.ts
5730
6632
  function loadProfileConfig(opts) {
5731
6633
  return loadConfig({
@@ -5735,7 +6637,8 @@ function loadProfileConfig(opts) {
5735
6637
  demo: opts.demo ?? false,
5736
6638
  site: opts.site,
5737
6639
  userAgent: opts.userAgent,
5738
- sourceTag: opts.sourceTag
6640
+ sourceTag: opts.sourceTag,
6641
+ verbose: opts.verbose
5739
6642
  });
5740
6643
  }
5741
6644
 
@@ -5894,8 +6797,8 @@ var HELP_TREE = {
5894
6797
  description: "Get trade fill history for spot orders"
5895
6798
  },
5896
6799
  place: {
5897
- usage: "okx spot place --instId <id> --side <buy|sell> --ordType <type> --sz <n> [--px <price>] [--tdMode <cash|cross|isolated>]",
5898
- description: "Place a new spot order"
6800
+ 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>]",
6801
+ description: "Place a new spot order (supports attached TP/SL)"
5899
6802
  },
5900
6803
  amend: {
5901
6804
  usage: "okx spot amend --instId <id> --ordId <id> [--newSz <n>] [--newPx <price>]",
@@ -5954,8 +6857,8 @@ var HELP_TREE = {
5954
6857
  description: "Get trade fill history for swap orders"
5955
6858
  },
5956
6859
  place: {
5957
- usage: "okx swap place --instId <id> --side <buy|sell> --ordType <type> --sz <n> [--posSide <side>] [--px <price>] [--tdMode <cross|isolated>]",
5958
- description: "Place a new perpetual swap order"
6860
+ 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>]",
6861
+ description: "Place a new perpetual swap order (supports attached TP/SL)"
5959
6862
  },
5960
6863
  cancel: {
5961
6864
  usage: "okx swap cancel <instId> --ordId <id>",
@@ -6026,8 +6929,8 @@ var HELP_TREE = {
6026
6929
  description: "Get trade fill history for futures orders"
6027
6930
  },
6028
6931
  place: {
6029
- usage: "okx futures place --instId <id> --side <buy|sell> --ordType <type> --sz <n>\n [--tdMode <cross|isolated>] [--posSide <net|long|short>] [--px <price>] [--reduceOnly]",
6030
- description: "Place a new futures order"
6932
+ 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>]",
6933
+ description: "Place a new futures order (supports attached TP/SL)"
6031
6934
  },
6032
6935
  cancel: {
6033
6936
  usage: "okx futures cancel <instId> --ordId <id>",
@@ -6084,6 +6987,73 @@ var HELP_TREE = {
6084
6987
  }
6085
6988
  }
6086
6989
  },
6990
+ earn: {
6991
+ description: "Earn products \u2014 Simple Earn (savings/lending) and On-chain Earn (staking/DeFi)",
6992
+ subgroups: {
6993
+ savings: {
6994
+ description: "Simple Earn \u2014 flexible savings and lending",
6995
+ commands: {
6996
+ balance: {
6997
+ usage: "okx earn savings balance [<ccy>]",
6998
+ description: "Get savings balance (optionally filter by currency)"
6999
+ },
7000
+ purchase: {
7001
+ usage: "okx earn savings purchase --ccy <ccy> --amt <n> [--rate <rate>]",
7002
+ description: "Purchase Simple Earn (flexible savings). Rate defaults to 0.01 (1%)"
7003
+ },
7004
+ redeem: {
7005
+ usage: "okx earn savings redeem --ccy <ccy> --amt <n>",
7006
+ description: "Redeem Simple Earn (flexible savings)"
7007
+ },
7008
+ "set-rate": {
7009
+ usage: "okx earn savings set-rate --ccy <ccy> --rate <rate>",
7010
+ description: "Set lending rate for a currency"
7011
+ },
7012
+ "lending-history": {
7013
+ usage: "okx earn savings lending-history [--ccy <ccy>] [--limit <n>]",
7014
+ description: "Get lending history"
7015
+ },
7016
+ "rate-summary": {
7017
+ usage: "okx earn savings rate-summary [<ccy>]",
7018
+ description: "Get market lending rate summary (public, no auth needed)"
7019
+ },
7020
+ "rate-history": {
7021
+ usage: "okx earn savings rate-history [--ccy <ccy>] [--limit <n>]",
7022
+ description: "Get historical lending rates (public, no auth needed)"
7023
+ }
7024
+ }
7025
+ },
7026
+ onchain: {
7027
+ description: "On-chain Earn \u2014 staking and DeFi products",
7028
+ commands: {
7029
+ offers: {
7030
+ usage: "okx earn onchain offers [--productId <id>] [--protocolType <type>] [--ccy <ccy>]",
7031
+ description: "Browse available on-chain earn products (staking, DeFi)"
7032
+ },
7033
+ purchase: {
7034
+ usage: "okx earn onchain purchase --productId <id> --ccy <ccy> --amt <n> [--term <term>] [--tag <tag>]",
7035
+ description: "Purchase an on-chain earn product (stake/deposit)"
7036
+ },
7037
+ redeem: {
7038
+ usage: "okx earn onchain redeem --ordId <id> --protocolType <type> [--allowEarlyRedeem]",
7039
+ description: "Redeem an on-chain earn position"
7040
+ },
7041
+ cancel: {
7042
+ usage: "okx earn onchain cancel --ordId <id> --protocolType <type>",
7043
+ description: "Cancel a pending on-chain earn order"
7044
+ },
7045
+ orders: {
7046
+ usage: "okx earn onchain orders [--productId <id>] [--protocolType <type>] [--ccy <ccy>] [--state <state>]",
7047
+ description: "List active on-chain earn orders"
7048
+ },
7049
+ history: {
7050
+ usage: "okx earn onchain history [--productId <id>] [--protocolType <type>] [--ccy <ccy>]",
7051
+ description: "Get on-chain earn order history"
7052
+ }
7053
+ }
7054
+ }
7055
+ }
7056
+ },
6087
7057
  bot: {
6088
7058
  description: "Trading bot strategies (grid, dca)",
6089
7059
  subgroups: {
@@ -6163,6 +7133,10 @@ var HELP_TREE = {
6163
7133
  setup: {
6164
7134
  description: "Set up client integrations (Cursor, Windsurf, Claude, etc.)",
6165
7135
  usage: `okx setup --client <${SUPPORTED_CLIENTS.join("|")}> [--profile <name>] [--modules <list>]`
7136
+ },
7137
+ diagnose: {
7138
+ description: "Run network diagnostics (DNS, TCP, TLS, API, auth)",
7139
+ usage: "okx diagnose [--profile <name>] [--demo]"
6166
7140
  }
6167
7141
  };
6168
7142
  function printGlobalHelp() {
@@ -6174,6 +7148,7 @@ function printGlobalHelp() {
6174
7148
  ` --profile <name> Use a named profile from ${configFilePath()}`,
6175
7149
  " --demo Use simulated trading (demo) mode",
6176
7150
  " --json Output raw JSON",
7151
+ " --verbose Show detailed network request/response info (stderr)",
6177
7152
  " --version, -v Show version",
6178
7153
  " --help Show this help",
6179
7154
  "",
@@ -6384,11 +7359,22 @@ var CLI_OPTIONS = {
6384
7359
  // batch
6385
7360
  action: { type: "string" },
6386
7361
  orders: { type: "string" },
7362
+ // earn
7363
+ rate: { type: "string" },
6387
7364
  // audit
6388
7365
  since: { type: "string" },
6389
7366
  tool: { type: "string" },
6390
7367
  // config profile
6391
- force: { type: "boolean", default: false }
7368
+ force: { type: "boolean", default: false },
7369
+ // onchain-earn
7370
+ productId: { type: "string" },
7371
+ protocolType: { type: "string" },
7372
+ term: { type: "string" },
7373
+ tag: { type: "string" },
7374
+ allowEarlyRedeem: { type: "boolean", default: false },
7375
+ state: { type: "string" },
7376
+ // diagnostics
7377
+ verbose: { type: "boolean", default: false }
6392
7378
  };
6393
7379
  function parseCli(argv) {
6394
7380
  const negated = /* @__PURE__ */ new Set();
@@ -6650,7 +7636,7 @@ async function cmdMarketCandles(run, instId, opts) {
6650
7636
  // src/commands/account.ts
6651
7637
  import * as fs4 from "fs";
6652
7638
  import * as path2 from "path";
6653
- import * as os2 from "os";
7639
+ import * as os4 from "os";
6654
7640
  function getData2(result) {
6655
7641
  return result.data;
6656
7642
  }
@@ -6856,7 +7842,7 @@ function readAuditLogs(logDir, days = 7) {
6856
7842
  return entries;
6857
7843
  }
6858
7844
  function cmdAccountAudit(opts) {
6859
- const logDir = path2.join(os2.homedir(), ".okx", "logs");
7845
+ const logDir = path2.join(os4.homedir(), ".okx", "logs");
6860
7846
  const limit = Math.min(Number(opts.limit) || 20, 100);
6861
7847
  let entries = readAuditLogs(logDir);
6862
7848
  if (opts.tool) entries = entries.filter((e) => e.tool === opts.tool);
@@ -6910,7 +7896,11 @@ async function cmdSpotPlace(run, opts) {
6910
7896
  side: opts.side,
6911
7897
  ordType: opts.ordType,
6912
7898
  sz: opts.sz,
6913
- px: opts.px
7899
+ px: opts.px,
7900
+ tpTriggerPx: opts.tpTriggerPx,
7901
+ tpOrdPx: opts.tpOrdPx,
7902
+ slTriggerPx: opts.slTriggerPx,
7903
+ slOrdPx: opts.slOrdPx
6914
7904
  });
6915
7905
  const data = getData3(result);
6916
7906
  if (opts.json) return printJson(data);
@@ -7135,7 +8125,11 @@ async function cmdSwapPlace(run, opts) {
7135
8125
  ordType: opts.ordType,
7136
8126
  sz: opts.sz,
7137
8127
  posSide: opts.posSide,
7138
- px: opts.px
8128
+ px: opts.px,
8129
+ tpTriggerPx: opts.tpTriggerPx,
8130
+ tpOrdPx: opts.tpOrdPx,
8131
+ slTriggerPx: opts.slTriggerPx,
8132
+ slOrdPx: opts.slOrdPx
7139
8133
  });
7140
8134
  const data = getData4(result);
7141
8135
  if (opts.json) return printJson(data);
@@ -7437,7 +8431,11 @@ async function cmdFuturesPlace(run, opts) {
7437
8431
  sz: opts.sz,
7438
8432
  posSide: opts.posSide,
7439
8433
  px: opts.px,
7440
- reduceOnly: opts.reduceOnly
8434
+ reduceOnly: opts.reduceOnly,
8435
+ tpTriggerPx: opts.tpTriggerPx,
8436
+ tpOrdPx: opts.tpOrdPx,
8437
+ slTriggerPx: opts.slTriggerPx,
8438
+ slOrdPx: opts.slOrdPx
7441
8439
  });
7442
8440
  const data = getData5(result);
7443
8441
  if (opts.json) return printJson(data);
@@ -8007,6 +9005,101 @@ function cmdConfigUse(profileName) {
8007
9005
  `);
8008
9006
  }
8009
9007
 
9008
+ // src/commands/earn.ts
9009
+ function extractData(result) {
9010
+ if (result && typeof result === "object") {
9011
+ const data = result["data"];
9012
+ if (Array.isArray(data)) return data;
9013
+ }
9014
+ return [];
9015
+ }
9016
+ function printDataList(data, json, emptyMsg, mapper) {
9017
+ if (json) {
9018
+ printJson(data);
9019
+ return;
9020
+ }
9021
+ if (!data.length) {
9022
+ process.stdout.write(emptyMsg + "\n");
9023
+ return;
9024
+ }
9025
+ printTable(data.map(mapper));
9026
+ }
9027
+ async function cmdEarnSavingsBalance(run, ccy, json) {
9028
+ const data = extractData(await run("earn_get_savings_balance", { ccy }));
9029
+ printDataList(data, json, "No savings balance", (r) => ({
9030
+ ccy: r["ccy"],
9031
+ amt: r["amt"],
9032
+ earnings: r["earnings"],
9033
+ rate: r["rate"],
9034
+ loanAmt: r["loanAmt"],
9035
+ pendingAmt: r["pendingAmt"]
9036
+ }));
9037
+ }
9038
+ async function cmdEarnSavingsPurchase(run, opts) {
9039
+ const data = extractData(await run("earn_savings_purchase", { ccy: opts.ccy, amt: opts.amt, rate: opts.rate }));
9040
+ if (opts.json) {
9041
+ printJson(data);
9042
+ return;
9043
+ }
9044
+ const r = data[0];
9045
+ if (!r) {
9046
+ process.stdout.write("No response data\n");
9047
+ return;
9048
+ }
9049
+ printKv({ ccy: r["ccy"], amt: r["amt"], side: r["side"], rate: r["rate"] });
9050
+ }
9051
+ async function cmdEarnSavingsRedeem(run, opts) {
9052
+ const data = extractData(await run("earn_savings_redeem", { ccy: opts.ccy, amt: opts.amt }));
9053
+ if (opts.json) {
9054
+ printJson(data);
9055
+ return;
9056
+ }
9057
+ const r = data[0];
9058
+ if (!r) {
9059
+ process.stdout.write("No response data\n");
9060
+ return;
9061
+ }
9062
+ printKv({ ccy: r["ccy"], amt: r["amt"], side: r["side"] });
9063
+ }
9064
+ async function cmdEarnSetLendingRate(run, opts) {
9065
+ const data = extractData(await run("earn_set_lending_rate", { ccy: opts.ccy, rate: opts.rate }));
9066
+ if (opts.json) {
9067
+ printJson(data);
9068
+ return;
9069
+ }
9070
+ const r = data[0];
9071
+ process.stdout.write(`Lending rate set: ${r?.["ccy"]} \u2192 ${r?.["rate"]}
9072
+ `);
9073
+ }
9074
+ async function cmdEarnLendingHistory(run, opts) {
9075
+ const data = extractData(await run("earn_get_lending_history", { ccy: opts.ccy, limit: opts.limit }));
9076
+ printDataList(data, opts.json, "No lending history", (r) => ({
9077
+ ccy: r["ccy"],
9078
+ amt: r["amt"],
9079
+ earnings: r["earnings"],
9080
+ rate: r["rate"],
9081
+ ts: new Date(Number(r["ts"])).toLocaleString()
9082
+ }));
9083
+ }
9084
+ async function cmdEarnLendingRateSummary(run, ccy, json) {
9085
+ const data = extractData(await run("earn_get_lending_rate_summary", { ccy }));
9086
+ printDataList(data, json, "No rate summary data", (r) => ({
9087
+ ccy: r["ccy"],
9088
+ avgRate: r["avgRate"],
9089
+ estRate: r["estRate"],
9090
+ avgAmt: r["avgAmt"]
9091
+ }));
9092
+ }
9093
+ async function cmdEarnLendingRateHistory(run, opts) {
9094
+ const data = extractData(await run("earn_get_lending_rate_history", { ccy: opts.ccy, limit: opts.limit }));
9095
+ printDataList(data, opts.json, "No rate history data", (r) => ({
9096
+ ccy: r["ccy"],
9097
+ lendingRate: r["lendingRate"],
9098
+ rate: r["rate"],
9099
+ ts: new Date(Number(r["ts"])).toLocaleString()
9100
+ }));
9101
+ }
9102
+
8010
9103
  // src/commands/bot.ts
8011
9104
  function getData7(result) {
8012
9105
  return result.data;
@@ -8238,10 +9331,56 @@ async function cmdDcaSubOrders(run, opts) {
8238
9331
  );
8239
9332
  }
8240
9333
 
9334
+ // src/commands/onchain-earn.ts
9335
+ function cmdOnchainEarnOffers(run, v) {
9336
+ return run("onchain_earn_get_offers", {
9337
+ productId: v.productId,
9338
+ protocolType: v.protocolType,
9339
+ ccy: v.ccy
9340
+ });
9341
+ }
9342
+ function cmdOnchainEarnPurchase(run, v) {
9343
+ const investData = v.ccy && v.amt ? [{ ccy: v.ccy, amt: v.amt }] : void 0;
9344
+ return run("onchain_earn_purchase", {
9345
+ productId: v.productId,
9346
+ investData,
9347
+ term: v.term,
9348
+ tag: v.tag
9349
+ });
9350
+ }
9351
+ function cmdOnchainEarnRedeem(run, v) {
9352
+ return run("onchain_earn_redeem", {
9353
+ ordId: v.ordId,
9354
+ protocolType: v.protocolType,
9355
+ allowEarlyRedeem: v.allowEarlyRedeem
9356
+ });
9357
+ }
9358
+ function cmdOnchainEarnCancel(run, v) {
9359
+ return run("onchain_earn_cancel", {
9360
+ ordId: v.ordId,
9361
+ protocolType: v.protocolType
9362
+ });
9363
+ }
9364
+ function cmdOnchainEarnActiveOrders(run, v) {
9365
+ return run("onchain_earn_get_active_orders", {
9366
+ productId: v.productId,
9367
+ protocolType: v.protocolType,
9368
+ ccy: v.ccy,
9369
+ state: v.state
9370
+ });
9371
+ }
9372
+ function cmdOnchainEarnOrderHistory(run, v) {
9373
+ return run("onchain_earn_get_order_history", {
9374
+ productId: v.productId,
9375
+ protocolType: v.protocolType,
9376
+ ccy: v.ccy
9377
+ });
9378
+ }
9379
+
8241
9380
  // src/index.ts
8242
- var _require = createRequire(import.meta.url);
8243
- var CLI_VERSION = _require("../package.json").version;
8244
- var GIT_HASH = true ? "c2ddd04" : "dev";
9381
+ var _require2 = createRequire2(import.meta.url);
9382
+ var CLI_VERSION2 = _require2("../package.json").version;
9383
+ var GIT_HASH2 = true ? "7777cc2" : "dev";
8245
9384
  function handleConfigCommand(action, rest, json, lang, force) {
8246
9385
  if (action === "init") return cmdConfigInit(lang === "zh" ? "zh" : "en");
8247
9386
  if (action === "show") return cmdConfigShow(json);
@@ -8413,6 +9552,10 @@ function handleSpotCommand(run, action, rest, v, json) {
8413
9552
  ordType: v.ordType,
8414
9553
  sz: v.sz,
8415
9554
  px: v.px,
9555
+ tpTriggerPx: v.tpTriggerPx,
9556
+ tpOrdPx: v.tpOrdPx,
9557
+ slTriggerPx: v.slTriggerPx,
9558
+ slOrdPx: v.slOrdPx,
8416
9559
  json
8417
9560
  });
8418
9561
  if (action === "cancel")
@@ -8509,6 +9652,10 @@ function handleSwapCommand(run, action, rest, v, json) {
8509
9652
  posSide: v.posSide,
8510
9653
  px: v.px,
8511
9654
  tdMode: v.tdMode ?? "cross",
9655
+ tpTriggerPx: v.tpTriggerPx,
9656
+ tpOrdPx: v.tpOrdPx,
9657
+ slTriggerPx: v.slTriggerPx,
9658
+ slOrdPx: v.slOrdPx,
8512
9659
  json
8513
9660
  });
8514
9661
  if (action === "cancel")
@@ -8603,6 +9750,10 @@ function handleFuturesCommand(run, action, rest, v, json) {
8603
9750
  posSide: v.posSide,
8604
9751
  px: v.px,
8605
9752
  reduceOnly: v.reduceOnly,
9753
+ tpTriggerPx: v.tpTriggerPx,
9754
+ tpOrdPx: v.tpOrdPx,
9755
+ slTriggerPx: v.slTriggerPx,
9756
+ slOrdPx: v.slOrdPx,
8606
9757
  json
8607
9758
  });
8608
9759
  if (action === "cancel")
@@ -8691,23 +9842,75 @@ function handleBotCommand(run, action, rest, v, json) {
8691
9842
  if (action === "grid") return handleBotGridCommand(run, v, rest, json);
8692
9843
  if (action === "dca") return handleBotDcaCommand(run, rest[0], v, json);
8693
9844
  }
9845
+ function handleEarnCommand(run, submodule, rest, v, json) {
9846
+ const action = rest[0];
9847
+ const innerRest = rest.slice(1);
9848
+ if (submodule === "savings") return handleEarnSavingsCommand(run, action, innerRest, v, json);
9849
+ if (submodule === "onchain") return handleEarnOnchainCommand(run, action, v, json);
9850
+ process.stderr.write(`Unknown earn sub-module: ${submodule}
9851
+ Valid: savings, onchain
9852
+ `);
9853
+ process.exitCode = 1;
9854
+ }
9855
+ function handleEarnSavingsCommand(run, action, rest, v, json) {
9856
+ const limit = v.limit !== void 0 ? Number(v.limit) : void 0;
9857
+ if (action === "balance") return cmdEarnSavingsBalance(run, rest[0] ?? v.ccy, json);
9858
+ if (action === "purchase") return cmdEarnSavingsPurchase(run, { ccy: v.ccy, amt: v.amt, rate: v.rate, json });
9859
+ if (action === "redeem") return cmdEarnSavingsRedeem(run, { ccy: v.ccy, amt: v.amt, json });
9860
+ if (action === "set-rate") return cmdEarnSetLendingRate(run, { ccy: v.ccy, rate: v.rate, json });
9861
+ if (action === "lending-history") return cmdEarnLendingHistory(run, { ccy: v.ccy, limit, json });
9862
+ if (action === "rate-summary") return cmdEarnLendingRateSummary(run, rest[0] ?? v.ccy, json);
9863
+ if (action === "rate-history") return cmdEarnLendingRateHistory(run, { ccy: v.ccy, limit, json });
9864
+ process.stderr.write(`Unknown earn savings command: ${action}
9865
+ `);
9866
+ process.exitCode = 1;
9867
+ }
9868
+ function handleEarnOnchainCommand(run, action, v, json) {
9869
+ if (action === "offers") return cmdOnchainEarnOffers(run, v).then((r) => outputResult(r, json));
9870
+ if (action === "purchase") return cmdOnchainEarnPurchase(run, v).then((r) => outputResult(r, json));
9871
+ if (action === "redeem") return cmdOnchainEarnRedeem(run, v).then((r) => outputResult(r, json));
9872
+ if (action === "cancel") return cmdOnchainEarnCancel(run, v).then((r) => outputResult(r, json));
9873
+ if (action === "orders") return cmdOnchainEarnActiveOrders(run, v).then((r) => outputResult(r, json));
9874
+ if (action === "history") return cmdOnchainEarnOrderHistory(run, v).then((r) => outputResult(r, json));
9875
+ process.stderr.write(`Unknown earn onchain command: ${action}
9876
+ `);
9877
+ process.exitCode = 1;
9878
+ }
9879
+ function outputResult(result, json) {
9880
+ if (json) {
9881
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
9882
+ } else {
9883
+ process.stdout.write(JSON.stringify(result.data, null, 2) + "\n");
9884
+ }
9885
+ }
9886
+ function printHelpForLevel(positionals) {
9887
+ const [module, subgroup] = positionals;
9888
+ if (!module) printHelp();
9889
+ else if (!subgroup) printHelp(module);
9890
+ else printHelp(module, subgroup);
9891
+ }
9892
+ function printVerboseConfigSummary(config, profile) {
9893
+ let authLabel = "\u2717";
9894
+ if (config.hasAuth && config.apiKey) {
9895
+ authLabel = `\u2713(${config.apiKey.slice(0, 3)}***${config.apiKey.slice(-3)})`;
9896
+ } else if (config.hasAuth) {
9897
+ authLabel = "\u2713";
9898
+ }
9899
+ process.stderr.write(
9900
+ `[verbose] config: profile=${profile ?? "default"} site=${config.site} base=${config.baseUrl} auth=${authLabel} demo=${config.demo ? "on" : "off"} modules=${config.modules.join(",")}
9901
+ `
9902
+ );
9903
+ }
8694
9904
  async function main() {
8695
- checkForUpdates("@okx_ai/okx-trade-cli", CLI_VERSION);
9905
+ checkForUpdates("@okx_ai/okx-trade-cli", CLI_VERSION2);
8696
9906
  const { values, positionals } = parseCli(process.argv.slice(2));
8697
9907
  if (values.version) {
8698
- process.stdout.write(`${CLI_VERSION} (${GIT_HASH})
9908
+ process.stdout.write(`${CLI_VERSION2} (${GIT_HASH2})
8699
9909
  `);
8700
9910
  return;
8701
9911
  }
8702
9912
  if (values.help || positionals.length === 0) {
8703
- const [module2, subgroup] = positionals;
8704
- if (!module2) {
8705
- printHelp();
8706
- } else if (!subgroup) {
8707
- printHelp(module2);
8708
- } else {
8709
- printHelp(module2, subgroup);
8710
- }
9913
+ printHelpForLevel(positionals);
8711
9914
  return;
8712
9915
  }
8713
9916
  const [module, action, ...rest] = positionals;
@@ -8715,16 +9918,23 @@ async function main() {
8715
9918
  const json = v.json ?? false;
8716
9919
  if (module === "config") return handleConfigCommand(action, rest, json, v.lang, v.force);
8717
9920
  if (module === "setup") return handleSetupCommand(v);
8718
- const config = loadProfileConfig({ profile: v.profile, demo: v.demo, userAgent: `okx-trade-cli/${CLI_VERSION}`, sourceTag: "CLI" });
9921
+ const config = loadProfileConfig({ profile: v.profile, demo: v.demo, verbose: v.verbose, userAgent: `okx-trade-cli/${CLI_VERSION2}`, sourceTag: "CLI" });
9922
+ if (config.verbose) printVerboseConfigSummary(config, v.profile);
9923
+ if (module === "diagnose") return cmdDiagnose(config, v.profile ?? "default");
8719
9924
  const client = new OkxRestClient(config);
8720
9925
  const run = createToolRunner(client, config);
8721
- if (module === "market") return handleMarketCommand(run, action, rest, v, json);
8722
- if (module === "account") return handleAccountCommand(run, action, rest, v, json);
8723
- if (module === "spot") return handleSpotCommand(run, action, rest, v, json);
8724
- if (module === "swap") return handleSwapCommand(run, action, rest, v, json);
8725
- if (module === "futures") return handleFuturesCommand(run, action, rest, v, json);
8726
- if (module === "option") return handleOptionCommand(run, action, rest, v, json);
8727
- if (module === "bot") return handleBotCommand(run, action, rest, v, json);
9926
+ const moduleHandlers = {
9927
+ market: () => handleMarketCommand(run, action, rest, v, json),
9928
+ account: () => handleAccountCommand(run, action, rest, v, json),
9929
+ spot: () => handleSpotCommand(run, action, rest, v, json),
9930
+ swap: () => handleSwapCommand(run, action, rest, v, json),
9931
+ futures: () => handleFuturesCommand(run, action, rest, v, json),
9932
+ option: () => handleOptionCommand(run, action, rest, v, json),
9933
+ bot: () => handleBotCommand(run, action, rest, v, json),
9934
+ earn: () => handleEarnCommand(run, action, rest, v, json)
9935
+ };
9936
+ const handler = moduleHandlers[module];
9937
+ if (handler) return handler();
8728
9938
  process.stderr.write(`Unknown command: ${module} ${action ?? ""}
8729
9939
  `);
8730
9940
  process.exitCode = 1;
@@ -8737,7 +9947,7 @@ main().catch((error) => {
8737
9947
  `);
8738
9948
  if (payload.suggestion) process.stderr.write(`Hint: ${payload.suggestion}
8739
9949
  `);
8740
- process.stderr.write(`Version: @okx_ai/okx-trade-cli@${CLI_VERSION}
9950
+ process.stderr.write(`Version: @okx_ai/okx-trade-cli@${CLI_VERSION2}
8741
9951
  `);
8742
9952
  process.exitCode = 1;
8743
9953
  });
@@ -8747,6 +9957,7 @@ export {
8747
9957
  handleBotDcaCommand,
8748
9958
  handleBotGridCommand,
8749
9959
  handleConfigCommand,
9960
+ handleEarnCommand,
8750
9961
  handleMarketCommand,
8751
9962
  handleMarketDataCommand,
8752
9963
  handleMarketPublicCommand,