@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 +1406 -195
- package/dist/index.js.map +1 -1
- package/package.json +14 -27
- package/LICENSE +0 -21
- package/scripts/postinstall.js +0 -14
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
|
-
|
|
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
|
|
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
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
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.
|
|
1119
|
-
|
|
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
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
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
|
|
1137
|
-
const
|
|
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: `${
|
|
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 ${
|
|
1156
|
-
`${
|
|
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: `${
|
|
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
|
-
|
|
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: `${
|
|
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: "
|
|
3081
|
-
module: "
|
|
3082
|
-
description: "Get
|
|
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
|
-
|
|
3681
|
+
productId: {
|
|
3088
3682
|
type: "string",
|
|
3089
|
-
|
|
3090
|
-
description: "FUTURES (default) or SWAP"
|
|
3683
|
+
description: "Filter by product ID. Omit for all."
|
|
3091
3684
|
},
|
|
3092
|
-
|
|
3685
|
+
protocolType: {
|
|
3093
3686
|
type: "string",
|
|
3094
|
-
description: "
|
|
3687
|
+
description: "Filter by protocol type: staking, defi. Omit for all."
|
|
3095
3688
|
},
|
|
3096
|
-
|
|
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/
|
|
3702
|
+
"/api/v5/finance/staking-defi/orders-active",
|
|
3107
3703
|
compactObject({
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3704
|
+
productId: readString(args, "productId"),
|
|
3705
|
+
protocolType: readString(args, "protocolType"),
|
|
3706
|
+
ccy: readString(args, "ccy"),
|
|
3707
|
+
state: readString(args, "state")
|
|
3111
3708
|
}),
|
|
3112
|
-
privateRateLimit("
|
|
3709
|
+
privateRateLimit("onchain_earn_get_active_orders", 3)
|
|
3113
3710
|
);
|
|
3114
|
-
return
|
|
3711
|
+
return normalizeResponse(response);
|
|
3115
3712
|
}
|
|
3116
3713
|
},
|
|
3714
|
+
// -------------------------------------------------------------------------
|
|
3715
|
+
// Get Order History
|
|
3716
|
+
// -------------------------------------------------------------------------
|
|
3117
3717
|
{
|
|
3118
|
-
name: "
|
|
3119
|
-
module: "
|
|
3120
|
-
description: "Get
|
|
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
|
-
|
|
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
|
-
|
|
3132
|
-
description: "FUTURES (default) or SWAP"
|
|
3727
|
+
description: "Filter by product ID. Omit for all."
|
|
3133
3728
|
},
|
|
3134
|
-
|
|
3729
|
+
protocolType: {
|
|
3135
3730
|
type: "string",
|
|
3136
|
-
description: "
|
|
3731
|
+
description: "Filter by protocol type: staking, defi. Omit for all."
|
|
3137
3732
|
},
|
|
3138
|
-
|
|
3733
|
+
ccy: {
|
|
3139
3734
|
type: "string",
|
|
3140
|
-
description: "
|
|
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
|
|
3739
|
+
description: "Pagination: return results before this order ID"
|
|
3145
3740
|
},
|
|
3146
3741
|
before: {
|
|
3147
3742
|
type: "string",
|
|
3148
|
-
description: "Pagination: after this
|
|
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: "
|
|
3160
|
-
description: "Max results (default 100
|
|
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
|
-
|
|
3754
|
+
"/api/v5/finance/staking-defi/orders-history",
|
|
3172
3755
|
compactObject({
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
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
|
-
|
|
3179
|
-
end: readString(args, "end"),
|
|
3180
|
-
limit: readNumber(args, "limit") ?? (archive ? 20 : void 0)
|
|
3761
|
+
limit: readString(args, "limit")
|
|
3181
3762
|
}),
|
|
3182
|
-
privateRateLimit("
|
|
3763
|
+
privateRateLimit("onchain_earn_get_order_history", 3)
|
|
3183
3764
|
);
|
|
3184
|
-
return
|
|
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((
|
|
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
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
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
|
|
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
|
-
|
|
5478
|
-
|
|
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
|
-
|
|
5486
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
8243
|
-
var
|
|
8244
|
-
var
|
|
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",
|
|
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(`${
|
|
9908
|
+
process.stdout.write(`${CLI_VERSION2} (${GIT_HASH2})
|
|
8699
9909
|
`);
|
|
8700
9910
|
return;
|
|
8701
9911
|
}
|
|
8702
9912
|
if (values.help || positionals.length === 0) {
|
|
8703
|
-
|
|
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/${
|
|
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
|
-
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
8726
|
-
|
|
8727
|
-
|
|
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@${
|
|
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,
|