@okx_ai/okx-trade-mcp 1.2.2 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +721 -109
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { parseArgs } from "util";
|
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
|
|
7
7
|
// ../core/dist/index.js
|
|
8
|
+
import { ProxyAgent } from "undici";
|
|
8
9
|
import { createHmac } from "crypto";
|
|
9
10
|
import fs from "fs";
|
|
10
11
|
import path from "path";
|
|
@@ -796,8 +797,10 @@ function sleep(ms) {
|
|
|
796
797
|
var RateLimiter = class {
|
|
797
798
|
buckets = /* @__PURE__ */ new Map();
|
|
798
799
|
maxWaitMs;
|
|
799
|
-
|
|
800
|
+
verbose;
|
|
801
|
+
constructor(maxWaitMs = 3e4, verbose = false) {
|
|
800
802
|
this.maxWaitMs = maxWaitMs;
|
|
803
|
+
this.verbose = verbose;
|
|
801
804
|
}
|
|
802
805
|
async consume(config, amount = 1) {
|
|
803
806
|
const bucket = this.getBucket(config);
|
|
@@ -815,6 +818,10 @@ var RateLimiter = class {
|
|
|
815
818
|
"Reduce tool call frequency or retry later."
|
|
816
819
|
);
|
|
817
820
|
}
|
|
821
|
+
if (this.verbose) {
|
|
822
|
+
process.stderr.write(`[verbose] \u23F3 rate-limit: waiting ${waitMs}ms for "${config.key}"
|
|
823
|
+
`);
|
|
824
|
+
}
|
|
818
825
|
await sleep(waitMs);
|
|
819
826
|
this.refill(bucket);
|
|
820
827
|
if (bucket.tokens < amount) {
|
|
@@ -909,11 +916,38 @@ function buildQueryString(query) {
|
|
|
909
916
|
}
|
|
910
917
|
return params.toString();
|
|
911
918
|
}
|
|
919
|
+
function maskKey(key) {
|
|
920
|
+
if (key.length <= 8) return "***";
|
|
921
|
+
return `${key.slice(0, 3)}***${key.slice(-3)}`;
|
|
922
|
+
}
|
|
923
|
+
function vlog(message) {
|
|
924
|
+
process.stderr.write(`[verbose] ${message}
|
|
925
|
+
`);
|
|
926
|
+
}
|
|
912
927
|
var OkxRestClient = class {
|
|
913
928
|
config;
|
|
914
|
-
rateLimiter
|
|
929
|
+
rateLimiter;
|
|
930
|
+
dispatcher;
|
|
915
931
|
constructor(config) {
|
|
916
932
|
this.config = config;
|
|
933
|
+
this.rateLimiter = new RateLimiter(3e4, config.verbose);
|
|
934
|
+
if (config.proxyUrl) {
|
|
935
|
+
this.dispatcher = new ProxyAgent(config.proxyUrl);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
logRequest(method, url, auth) {
|
|
939
|
+
if (!this.config.verbose) return;
|
|
940
|
+
vlog(`\u2192 ${method} ${url}`);
|
|
941
|
+
const authInfo = auth === "private" && this.config.apiKey ? `auth=\u2713(${maskKey(this.config.apiKey)})` : `auth=${auth}`;
|
|
942
|
+
vlog(` ${authInfo} demo=${this.config.demo} timeout=${this.config.timeoutMs}ms`);
|
|
943
|
+
}
|
|
944
|
+
logResponse(status, rawLen, elapsed, traceId, code, msg) {
|
|
945
|
+
if (!this.config.verbose) return;
|
|
946
|
+
if (code && code !== "0" && code !== "1") {
|
|
947
|
+
vlog(`\u2717 ${status} | code=${code} | msg=${msg ?? "-"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
|
|
948
|
+
} else {
|
|
949
|
+
vlog(`\u2190 ${status} | code=${code ?? "0"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
|
|
950
|
+
}
|
|
917
951
|
}
|
|
918
952
|
async publicGet(path4, query, rateLimit) {
|
|
919
953
|
return this.request({
|
|
@@ -942,126 +976,150 @@ var OkxRestClient = class {
|
|
|
942
976
|
rateLimit
|
|
943
977
|
});
|
|
944
978
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
if (config.rateLimit) {
|
|
952
|
-
await this.rateLimiter.consume(config.rateLimit);
|
|
953
|
-
}
|
|
954
|
-
const headers = new Headers({
|
|
955
|
-
"Content-Type": "application/json",
|
|
956
|
-
Accept: "application/json"
|
|
957
|
-
});
|
|
958
|
-
if (this.config.userAgent) {
|
|
959
|
-
headers.set("User-Agent", this.config.userAgent);
|
|
960
|
-
}
|
|
961
|
-
if (config.auth === "private") {
|
|
962
|
-
if (!this.config.hasAuth) {
|
|
963
|
-
throw new ConfigError(
|
|
964
|
-
"Private endpoint requires API credentials.",
|
|
965
|
-
"Configure OKX_API_KEY, OKX_SECRET_KEY and OKX_PASSPHRASE."
|
|
966
|
-
);
|
|
967
|
-
}
|
|
968
|
-
if (!this.config.apiKey || !this.config.secretKey || !this.config.passphrase) {
|
|
969
|
-
throw new ConfigError(
|
|
970
|
-
"Invalid private API credentials state.",
|
|
971
|
-
"Ensure all OKX credentials are set."
|
|
972
|
-
);
|
|
973
|
-
}
|
|
974
|
-
const payload = `${timestamp}${config.method.toUpperCase()}${requestPath}${bodyJson}`;
|
|
975
|
-
const signature = signOkxPayload(payload, this.config.secretKey);
|
|
976
|
-
headers.set("OK-ACCESS-KEY", this.config.apiKey);
|
|
977
|
-
headers.set("OK-ACCESS-SIGN", signature);
|
|
978
|
-
headers.set("OK-ACCESS-PASSPHRASE", this.config.passphrase);
|
|
979
|
-
headers.set("OK-ACCESS-TIMESTAMP", timestamp);
|
|
979
|
+
setAuthHeaders(headers, method, requestPath, bodyJson, timestamp) {
|
|
980
|
+
if (!this.config.hasAuth) {
|
|
981
|
+
throw new ConfigError(
|
|
982
|
+
"Private endpoint requires API credentials.",
|
|
983
|
+
"Configure OKX_API_KEY, OKX_SECRET_KEY and OKX_PASSPHRASE."
|
|
984
|
+
);
|
|
980
985
|
}
|
|
981
|
-
if (this.config.
|
|
982
|
-
|
|
986
|
+
if (!this.config.apiKey || !this.config.secretKey || !this.config.passphrase) {
|
|
987
|
+
throw new ConfigError(
|
|
988
|
+
"Invalid private API credentials state.",
|
|
989
|
+
"Ensure all OKX credentials are set."
|
|
990
|
+
);
|
|
983
991
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
992
|
+
const payload = `${timestamp}${method.toUpperCase()}${requestPath}${bodyJson}`;
|
|
993
|
+
const signature = signOkxPayload(payload, this.config.secretKey);
|
|
994
|
+
headers.set("OK-ACCESS-KEY", this.config.apiKey);
|
|
995
|
+
headers.set("OK-ACCESS-SIGN", signature);
|
|
996
|
+
headers.set("OK-ACCESS-PASSPHRASE", this.config.passphrase);
|
|
997
|
+
headers.set("OK-ACCESS-TIMESTAMP", timestamp);
|
|
998
|
+
}
|
|
999
|
+
throwOkxError(code, msg, reqConfig, traceId) {
|
|
1000
|
+
const message = msg || "OKX API request failed.";
|
|
1001
|
+
const endpoint = `${reqConfig.method} ${reqConfig.path}`;
|
|
1002
|
+
if (code === "50111" || code === "50112" || code === "50113") {
|
|
1003
|
+
throw new AuthenticationError(
|
|
1004
|
+
message,
|
|
1005
|
+
"Check API key, secret, passphrase and permissions.",
|
|
1006
|
+
endpoint,
|
|
1007
|
+
traceId
|
|
997
1008
|
);
|
|
998
1009
|
}
|
|
999
|
-
const
|
|
1000
|
-
const
|
|
1010
|
+
const behavior = OKX_CODE_BEHAVIORS[code];
|
|
1011
|
+
const suggestion = behavior?.suggestion?.replace("{site}", this.config.site);
|
|
1012
|
+
if (code === "50011" || code === "50061") {
|
|
1013
|
+
throw new RateLimitError(message, suggestion, endpoint, traceId);
|
|
1014
|
+
}
|
|
1015
|
+
throw new OkxApiError(message, {
|
|
1016
|
+
code,
|
|
1017
|
+
endpoint,
|
|
1018
|
+
suggestion,
|
|
1019
|
+
traceId
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
processResponse(rawText, response, elapsed, traceId, reqConfig, requestPath) {
|
|
1001
1023
|
let parsed;
|
|
1002
1024
|
try {
|
|
1003
1025
|
parsed = rawText ? JSON.parse(rawText) : {};
|
|
1004
1026
|
} catch (error) {
|
|
1027
|
+
this.logResponse(response.status, rawText.length, elapsed, traceId, "non-JSON");
|
|
1005
1028
|
if (!response.ok) {
|
|
1006
1029
|
const messagePreview = rawText.slice(0, 160).replace(/\s+/g, " ").trim();
|
|
1007
1030
|
throw new OkxApiError(
|
|
1008
1031
|
`HTTP ${response.status} from OKX: ${messagePreview || "Non-JSON response body"}`,
|
|
1009
1032
|
{
|
|
1010
1033
|
code: String(response.status),
|
|
1011
|
-
endpoint: `${
|
|
1034
|
+
endpoint: `${reqConfig.method} ${reqConfig.path}`,
|
|
1012
1035
|
suggestion: "Verify endpoint path and request parameters.",
|
|
1013
1036
|
traceId
|
|
1014
1037
|
}
|
|
1015
1038
|
);
|
|
1016
1039
|
}
|
|
1017
1040
|
throw new NetworkError(
|
|
1018
|
-
`OKX returned non-JSON response for ${
|
|
1019
|
-
`${
|
|
1041
|
+
`OKX returned non-JSON response for ${reqConfig.method} ${requestPath}.`,
|
|
1042
|
+
`${reqConfig.method} ${requestPath}`,
|
|
1020
1043
|
error
|
|
1021
1044
|
);
|
|
1022
1045
|
}
|
|
1023
1046
|
if (!response.ok) {
|
|
1047
|
+
this.logResponse(response.status, rawText.length, elapsed, traceId, parsed.code ?? "-", parsed.msg);
|
|
1024
1048
|
throw new OkxApiError(
|
|
1025
1049
|
`HTTP ${response.status} from OKX: ${parsed.msg ?? "Unknown error"}`,
|
|
1026
1050
|
{
|
|
1027
1051
|
code: String(response.status),
|
|
1028
|
-
endpoint: `${
|
|
1052
|
+
endpoint: `${reqConfig.method} ${reqConfig.path}`,
|
|
1029
1053
|
suggestion: "Retry later or verify endpoint parameters.",
|
|
1030
1054
|
traceId
|
|
1031
1055
|
}
|
|
1032
1056
|
);
|
|
1033
1057
|
}
|
|
1034
1058
|
const responseCode = parsed.code;
|
|
1059
|
+
this.logResponse(response.status, rawText.length, elapsed, traceId, responseCode, parsed.msg);
|
|
1035
1060
|
if (responseCode && responseCode !== "0" && responseCode !== "1") {
|
|
1036
|
-
|
|
1037
|
-
const endpoint = `${config.method} ${config.path}`;
|
|
1038
|
-
if (responseCode === "50111" || responseCode === "50112" || responseCode === "50113") {
|
|
1039
|
-
throw new AuthenticationError(
|
|
1040
|
-
message,
|
|
1041
|
-
"Check API key, secret, passphrase and permissions.",
|
|
1042
|
-
endpoint,
|
|
1043
|
-
traceId
|
|
1044
|
-
);
|
|
1045
|
-
}
|
|
1046
|
-
const behavior = OKX_CODE_BEHAVIORS[responseCode];
|
|
1047
|
-
const suggestion = behavior?.suggestion?.replace("{site}", this.config.site);
|
|
1048
|
-
if (responseCode === "50011" || responseCode === "50061") {
|
|
1049
|
-
throw new RateLimitError(message, suggestion, endpoint, traceId);
|
|
1050
|
-
}
|
|
1051
|
-
throw new OkxApiError(message, {
|
|
1052
|
-
code: responseCode,
|
|
1053
|
-
endpoint,
|
|
1054
|
-
suggestion,
|
|
1055
|
-
traceId
|
|
1056
|
-
});
|
|
1061
|
+
this.throwOkxError(responseCode, parsed.msg, reqConfig, traceId);
|
|
1057
1062
|
}
|
|
1058
1063
|
return {
|
|
1059
|
-
endpoint: `${
|
|
1064
|
+
endpoint: `${reqConfig.method} ${reqConfig.path}`,
|
|
1060
1065
|
requestTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1061
1066
|
data: parsed.data ?? null,
|
|
1062
1067
|
raw: parsed
|
|
1063
1068
|
};
|
|
1064
1069
|
}
|
|
1070
|
+
async request(reqConfig) {
|
|
1071
|
+
const queryString = buildQueryString(reqConfig.query);
|
|
1072
|
+
const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
|
|
1073
|
+
const url = `${this.config.baseUrl}${requestPath}`;
|
|
1074
|
+
const bodyJson = reqConfig.body ? JSON.stringify(reqConfig.body) : "";
|
|
1075
|
+
const timestamp = getNow();
|
|
1076
|
+
this.logRequest(reqConfig.method, url, reqConfig.auth);
|
|
1077
|
+
if (reqConfig.rateLimit) {
|
|
1078
|
+
await this.rateLimiter.consume(reqConfig.rateLimit);
|
|
1079
|
+
}
|
|
1080
|
+
const headers = new Headers({
|
|
1081
|
+
"Content-Type": "application/json",
|
|
1082
|
+
Accept: "application/json"
|
|
1083
|
+
});
|
|
1084
|
+
if (this.config.userAgent) {
|
|
1085
|
+
headers.set("User-Agent", this.config.userAgent);
|
|
1086
|
+
}
|
|
1087
|
+
if (reqConfig.auth === "private") {
|
|
1088
|
+
this.setAuthHeaders(headers, reqConfig.method, requestPath, bodyJson, timestamp);
|
|
1089
|
+
}
|
|
1090
|
+
if (this.config.demo) {
|
|
1091
|
+
headers.set("x-simulated-trading", "1");
|
|
1092
|
+
}
|
|
1093
|
+
const t0 = Date.now();
|
|
1094
|
+
let response;
|
|
1095
|
+
try {
|
|
1096
|
+
const fetchOptions = {
|
|
1097
|
+
method: reqConfig.method,
|
|
1098
|
+
headers,
|
|
1099
|
+
body: reqConfig.method === "POST" ? bodyJson : void 0,
|
|
1100
|
+
signal: AbortSignal.timeout(this.config.timeoutMs)
|
|
1101
|
+
};
|
|
1102
|
+
if (this.dispatcher) {
|
|
1103
|
+
fetchOptions.dispatcher = this.dispatcher;
|
|
1104
|
+
}
|
|
1105
|
+
response = await fetch(url, fetchOptions);
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
if (this.config.verbose) {
|
|
1108
|
+
const elapsed2 = Date.now() - t0;
|
|
1109
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
1110
|
+
vlog(`\u2717 NetworkError after ${elapsed2}ms: ${cause}`);
|
|
1111
|
+
}
|
|
1112
|
+
throw new NetworkError(
|
|
1113
|
+
`Failed to call OKX endpoint ${reqConfig.method} ${requestPath}.`,
|
|
1114
|
+
`${reqConfig.method} ${requestPath}`,
|
|
1115
|
+
error
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
const rawText = await response.text();
|
|
1119
|
+
const elapsed = Date.now() - t0;
|
|
1120
|
+
const traceId = extractTraceId(response.headers);
|
|
1121
|
+
return this.processResponse(rawText, response, elapsed, traceId, reqConfig, requestPath);
|
|
1122
|
+
}
|
|
1065
1123
|
};
|
|
1066
1124
|
var DEFAULT_SOURCE_TAG = "MCP";
|
|
1067
1125
|
var OKX_SITES = {
|
|
@@ -1087,6 +1145,10 @@ var BOT_SUB_MODULE_IDS = [
|
|
|
1087
1145
|
"bot.dca"
|
|
1088
1146
|
];
|
|
1089
1147
|
var BOT_DEFAULT_SUB_MODULES = ["bot.grid"];
|
|
1148
|
+
var EARN_SUB_MODULE_IDS = [
|
|
1149
|
+
"earn.savings",
|
|
1150
|
+
"earn.onchain"
|
|
1151
|
+
];
|
|
1090
1152
|
var MODULES = [
|
|
1091
1153
|
"market",
|
|
1092
1154
|
"spot",
|
|
@@ -1094,6 +1156,7 @@ var MODULES = [
|
|
|
1094
1156
|
"futures",
|
|
1095
1157
|
"option",
|
|
1096
1158
|
"account",
|
|
1159
|
+
...EARN_SUB_MODULE_IDS,
|
|
1097
1160
|
...BOT_SUB_MODULE_IDS
|
|
1098
1161
|
];
|
|
1099
1162
|
var DEFAULT_MODULES = ["spot", "swap", "option", "account", ...BOT_DEFAULT_SUB_MODULES];
|
|
@@ -1159,6 +1222,13 @@ function compactObject(object) {
|
|
|
1159
1222
|
}
|
|
1160
1223
|
return next;
|
|
1161
1224
|
}
|
|
1225
|
+
function normalizeResponse(response) {
|
|
1226
|
+
return {
|
|
1227
|
+
endpoint: response.endpoint,
|
|
1228
|
+
requestTime: response.requestTime,
|
|
1229
|
+
data: response.data
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1162
1232
|
var OKX_CANDLE_BARS = [
|
|
1163
1233
|
"1m",
|
|
1164
1234
|
"3m",
|
|
@@ -1198,6 +1268,14 @@ function privateRateLimit(key, rps = 10) {
|
|
|
1198
1268
|
refillPerSecond: rps
|
|
1199
1269
|
};
|
|
1200
1270
|
}
|
|
1271
|
+
function assertNotDemo(config, endpoint) {
|
|
1272
|
+
if (config.demo) {
|
|
1273
|
+
throw new ConfigError(
|
|
1274
|
+
`"${endpoint}" is not supported in simulated trading mode.`,
|
|
1275
|
+
"Disable demo mode (remove OKX_DEMO=1 or --demo flag) to use this endpoint."
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1201
1279
|
function normalize(response) {
|
|
1202
1280
|
return {
|
|
1203
1281
|
endpoint: response.endpoint,
|
|
@@ -1776,7 +1854,7 @@ function registerAlgoTradeTools() {
|
|
|
1776
1854
|
},
|
|
1777
1855
|
sz: {
|
|
1778
1856
|
type: "string",
|
|
1779
|
-
description: "
|
|
1857
|
+
description: "Number of contracts to close (NOT USDT amount). Use market_get_instruments to get ctVal for conversion."
|
|
1780
1858
|
},
|
|
1781
1859
|
tpTriggerPx: {
|
|
1782
1860
|
type: "string",
|
|
@@ -1871,7 +1949,7 @@ function registerAlgoTradeTools() {
|
|
|
1871
1949
|
},
|
|
1872
1950
|
sz: {
|
|
1873
1951
|
type: "string",
|
|
1874
|
-
description: "
|
|
1952
|
+
description: "Number of contracts (NOT USDT amount). Use market_get_instruments to get ctVal for conversion."
|
|
1875
1953
|
},
|
|
1876
1954
|
callbackRatio: {
|
|
1877
1955
|
type: "string",
|
|
@@ -2684,6 +2762,246 @@ function registerBotTools() {
|
|
|
2684
2762
|
...registerDcaTools()
|
|
2685
2763
|
];
|
|
2686
2764
|
}
|
|
2765
|
+
function registerEarnTools() {
|
|
2766
|
+
return [
|
|
2767
|
+
{
|
|
2768
|
+
name: "earn_get_savings_balance",
|
|
2769
|
+
module: "earn.savings",
|
|
2770
|
+
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.",
|
|
2771
|
+
isWrite: false,
|
|
2772
|
+
inputSchema: {
|
|
2773
|
+
type: "object",
|
|
2774
|
+
properties: {
|
|
2775
|
+
ccy: {
|
|
2776
|
+
type: "string",
|
|
2777
|
+
description: "e.g. USDT or BTC. Omit for all."
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
},
|
|
2781
|
+
handler: async (rawArgs, context) => {
|
|
2782
|
+
const args = asRecord(rawArgs);
|
|
2783
|
+
const response = await context.client.privateGet(
|
|
2784
|
+
"/api/v5/finance/savings/balance",
|
|
2785
|
+
compactObject({ ccy: readString(args, "ccy") }),
|
|
2786
|
+
privateRateLimit("earn_get_savings_balance", 6)
|
|
2787
|
+
);
|
|
2788
|
+
return normalizeResponse(response);
|
|
2789
|
+
}
|
|
2790
|
+
},
|
|
2791
|
+
{
|
|
2792
|
+
name: "earn_savings_purchase",
|
|
2793
|
+
module: "earn.savings",
|
|
2794
|
+
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.",
|
|
2795
|
+
isWrite: true,
|
|
2796
|
+
inputSchema: {
|
|
2797
|
+
type: "object",
|
|
2798
|
+
properties: {
|
|
2799
|
+
ccy: {
|
|
2800
|
+
type: "string",
|
|
2801
|
+
description: "Currency to purchase, e.g. USDT"
|
|
2802
|
+
},
|
|
2803
|
+
amt: {
|
|
2804
|
+
type: "string",
|
|
2805
|
+
description: "Purchase amount"
|
|
2806
|
+
},
|
|
2807
|
+
rate: {
|
|
2808
|
+
type: "string",
|
|
2809
|
+
description: "Lending rate. Annual rate in decimal, e.g. 0.01 = 1%. Defaults to 0.01 (1%, minimum rate, easiest to match)."
|
|
2810
|
+
}
|
|
2811
|
+
},
|
|
2812
|
+
required: ["ccy", "amt"]
|
|
2813
|
+
},
|
|
2814
|
+
handler: async (rawArgs, context) => {
|
|
2815
|
+
assertNotDemo(context.config, "earn_savings_purchase");
|
|
2816
|
+
const args = asRecord(rawArgs);
|
|
2817
|
+
const response = await context.client.privatePost(
|
|
2818
|
+
"/api/v5/finance/savings/purchase-redempt",
|
|
2819
|
+
compactObject({
|
|
2820
|
+
ccy: requireString(args, "ccy"),
|
|
2821
|
+
amt: requireString(args, "amt"),
|
|
2822
|
+
side: "purchase",
|
|
2823
|
+
rate: readString(args, "rate") ?? "0.01"
|
|
2824
|
+
}),
|
|
2825
|
+
privateRateLimit("earn_savings_purchase", 6)
|
|
2826
|
+
);
|
|
2827
|
+
return normalizeResponse(response);
|
|
2828
|
+
}
|
|
2829
|
+
},
|
|
2830
|
+
{
|
|
2831
|
+
name: "earn_savings_redeem",
|
|
2832
|
+
module: "earn.savings",
|
|
2833
|
+
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.",
|
|
2834
|
+
isWrite: true,
|
|
2835
|
+
inputSchema: {
|
|
2836
|
+
type: "object",
|
|
2837
|
+
properties: {
|
|
2838
|
+
ccy: {
|
|
2839
|
+
type: "string",
|
|
2840
|
+
description: "Currency to redeem, e.g. USDT"
|
|
2841
|
+
},
|
|
2842
|
+
amt: {
|
|
2843
|
+
type: "string",
|
|
2844
|
+
description: "Redemption amount"
|
|
2845
|
+
}
|
|
2846
|
+
},
|
|
2847
|
+
required: ["ccy", "amt"]
|
|
2848
|
+
},
|
|
2849
|
+
handler: async (rawArgs, context) => {
|
|
2850
|
+
assertNotDemo(context.config, "earn_savings_redeem");
|
|
2851
|
+
const args = asRecord(rawArgs);
|
|
2852
|
+
const response = await context.client.privatePost(
|
|
2853
|
+
"/api/v5/finance/savings/purchase-redempt",
|
|
2854
|
+
compactObject({
|
|
2855
|
+
ccy: requireString(args, "ccy"),
|
|
2856
|
+
amt: requireString(args, "amt"),
|
|
2857
|
+
side: "redempt"
|
|
2858
|
+
}),
|
|
2859
|
+
privateRateLimit("earn_savings_redeem", 6)
|
|
2860
|
+
);
|
|
2861
|
+
return normalizeResponse(response);
|
|
2862
|
+
}
|
|
2863
|
+
},
|
|
2864
|
+
{
|
|
2865
|
+
name: "earn_set_lending_rate",
|
|
2866
|
+
module: "earn.savings",
|
|
2867
|
+
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.",
|
|
2868
|
+
isWrite: true,
|
|
2869
|
+
inputSchema: {
|
|
2870
|
+
type: "object",
|
|
2871
|
+
properties: {
|
|
2872
|
+
ccy: {
|
|
2873
|
+
type: "string",
|
|
2874
|
+
description: "Currency, e.g. USDT"
|
|
2875
|
+
},
|
|
2876
|
+
rate: {
|
|
2877
|
+
type: "string",
|
|
2878
|
+
description: "Lending rate. Annual rate in decimal, e.g. 0.01 = 1%"
|
|
2879
|
+
}
|
|
2880
|
+
},
|
|
2881
|
+
required: ["ccy", "rate"]
|
|
2882
|
+
},
|
|
2883
|
+
handler: async (rawArgs, context) => {
|
|
2884
|
+
assertNotDemo(context.config, "earn_set_lending_rate");
|
|
2885
|
+
const args = asRecord(rawArgs);
|
|
2886
|
+
const response = await context.client.privatePost(
|
|
2887
|
+
"/api/v5/finance/savings/set-lending-rate",
|
|
2888
|
+
{
|
|
2889
|
+
ccy: requireString(args, "ccy"),
|
|
2890
|
+
rate: requireString(args, "rate")
|
|
2891
|
+
},
|
|
2892
|
+
privateRateLimit("earn_set_lending_rate", 6)
|
|
2893
|
+
);
|
|
2894
|
+
return normalizeResponse(response);
|
|
2895
|
+
}
|
|
2896
|
+
},
|
|
2897
|
+
{
|
|
2898
|
+
name: "earn_get_lending_history",
|
|
2899
|
+
module: "earn.savings",
|
|
2900
|
+
description: "Get lending history for Simple Earn. Returns lending records with details like amount, rate, and earnings. Private endpoint. Rate limit: 6 req/s.",
|
|
2901
|
+
isWrite: false,
|
|
2902
|
+
inputSchema: {
|
|
2903
|
+
type: "object",
|
|
2904
|
+
properties: {
|
|
2905
|
+
ccy: {
|
|
2906
|
+
type: "string",
|
|
2907
|
+
description: "e.g. USDT. Omit for all."
|
|
2908
|
+
},
|
|
2909
|
+
after: {
|
|
2910
|
+
type: "string",
|
|
2911
|
+
description: "Pagination: before this record ID"
|
|
2912
|
+
},
|
|
2913
|
+
before: {
|
|
2914
|
+
type: "string",
|
|
2915
|
+
description: "Pagination: after this record ID"
|
|
2916
|
+
},
|
|
2917
|
+
limit: {
|
|
2918
|
+
type: "number",
|
|
2919
|
+
description: "Max results (default 100)"
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
},
|
|
2923
|
+
handler: async (rawArgs, context) => {
|
|
2924
|
+
const args = asRecord(rawArgs);
|
|
2925
|
+
const response = await context.client.privateGet(
|
|
2926
|
+
"/api/v5/finance/savings/lending-history",
|
|
2927
|
+
compactObject({
|
|
2928
|
+
ccy: readString(args, "ccy"),
|
|
2929
|
+
after: readString(args, "after"),
|
|
2930
|
+
before: readString(args, "before"),
|
|
2931
|
+
limit: readNumber(args, "limit")
|
|
2932
|
+
}),
|
|
2933
|
+
privateRateLimit("earn_get_lending_history", 6)
|
|
2934
|
+
);
|
|
2935
|
+
return normalizeResponse(response);
|
|
2936
|
+
}
|
|
2937
|
+
},
|
|
2938
|
+
{
|
|
2939
|
+
name: "earn_get_lending_rate_summary",
|
|
2940
|
+
module: "earn.savings",
|
|
2941
|
+
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.",
|
|
2942
|
+
isWrite: false,
|
|
2943
|
+
inputSchema: {
|
|
2944
|
+
type: "object",
|
|
2945
|
+
properties: {
|
|
2946
|
+
ccy: {
|
|
2947
|
+
type: "string",
|
|
2948
|
+
description: "e.g. USDT. Omit for all."
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
},
|
|
2952
|
+
handler: async (rawArgs, context) => {
|
|
2953
|
+
const args = asRecord(rawArgs);
|
|
2954
|
+
const response = await context.client.publicGet(
|
|
2955
|
+
"/api/v5/finance/savings/lending-rate-summary",
|
|
2956
|
+
compactObject({ ccy: readString(args, "ccy") }),
|
|
2957
|
+
publicRateLimit("earn_get_lending_rate_summary", 6)
|
|
2958
|
+
);
|
|
2959
|
+
return normalizeResponse(response);
|
|
2960
|
+
}
|
|
2961
|
+
},
|
|
2962
|
+
{
|
|
2963
|
+
name: "earn_get_lending_rate_history",
|
|
2964
|
+
module: "earn.savings",
|
|
2965
|
+
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.",
|
|
2966
|
+
isWrite: false,
|
|
2967
|
+
inputSchema: {
|
|
2968
|
+
type: "object",
|
|
2969
|
+
properties: {
|
|
2970
|
+
ccy: {
|
|
2971
|
+
type: "string",
|
|
2972
|
+
description: "e.g. USDT. Omit for all."
|
|
2973
|
+
},
|
|
2974
|
+
after: {
|
|
2975
|
+
type: "string",
|
|
2976
|
+
description: "Pagination: before this timestamp (ms)"
|
|
2977
|
+
},
|
|
2978
|
+
before: {
|
|
2979
|
+
type: "string",
|
|
2980
|
+
description: "Pagination: after this timestamp (ms)"
|
|
2981
|
+
},
|
|
2982
|
+
limit: {
|
|
2983
|
+
type: "number",
|
|
2984
|
+
description: "Max results (default 100)"
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
},
|
|
2988
|
+
handler: async (rawArgs, context) => {
|
|
2989
|
+
const args = asRecord(rawArgs);
|
|
2990
|
+
const response = await context.client.publicGet(
|
|
2991
|
+
"/api/v5/finance/savings/lending-rate-history",
|
|
2992
|
+
compactObject({
|
|
2993
|
+
ccy: readString(args, "ccy"),
|
|
2994
|
+
after: readString(args, "after"),
|
|
2995
|
+
before: readString(args, "before"),
|
|
2996
|
+
limit: readNumber(args, "limit")
|
|
2997
|
+
}),
|
|
2998
|
+
publicRateLimit("earn_get_lending_rate_history", 6)
|
|
2999
|
+
);
|
|
3000
|
+
return normalizeResponse(response);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
];
|
|
3004
|
+
}
|
|
2687
3005
|
var FUTURES_INST_TYPES = ["FUTURES", "SWAP"];
|
|
2688
3006
|
function normalize5(response) {
|
|
2689
3007
|
return {
|
|
@@ -2728,7 +3046,7 @@ function registerFuturesTools() {
|
|
|
2728
3046
|
},
|
|
2729
3047
|
sz: {
|
|
2730
3048
|
type: "string",
|
|
2731
|
-
description: "
|
|
3049
|
+
description: "Number of contracts (NOT USDT amount). Use market_get_instruments to get ctVal for conversion."
|
|
2732
3050
|
},
|
|
2733
3051
|
px: {
|
|
2734
3052
|
type: "string",
|
|
@@ -3049,6 +3367,278 @@ function registerFuturesTools() {
|
|
|
3049
3367
|
}
|
|
3050
3368
|
];
|
|
3051
3369
|
}
|
|
3370
|
+
function registerOnchainEarnTools() {
|
|
3371
|
+
return [
|
|
3372
|
+
// -------------------------------------------------------------------------
|
|
3373
|
+
// Get Offers
|
|
3374
|
+
// -------------------------------------------------------------------------
|
|
3375
|
+
{
|
|
3376
|
+
name: "onchain_earn_get_offers",
|
|
3377
|
+
module: "earn.onchain",
|
|
3378
|
+
description: "Get available on-chain earn (staking/DeFi) offers. Returns investment products with APY, terms, and limits. Private endpoint. Rate limit: 3 req/s.",
|
|
3379
|
+
isWrite: false,
|
|
3380
|
+
inputSchema: {
|
|
3381
|
+
type: "object",
|
|
3382
|
+
properties: {
|
|
3383
|
+
productId: {
|
|
3384
|
+
type: "string",
|
|
3385
|
+
description: "Specific product ID to query. Omit for all offers."
|
|
3386
|
+
},
|
|
3387
|
+
protocolType: {
|
|
3388
|
+
type: "string",
|
|
3389
|
+
description: "Protocol type filter: staking, defi. Omit for all types."
|
|
3390
|
+
},
|
|
3391
|
+
ccy: {
|
|
3392
|
+
type: "string",
|
|
3393
|
+
description: "Currency filter, e.g. ETH. Omit for all currencies."
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
},
|
|
3397
|
+
handler: async (rawArgs, context) => {
|
|
3398
|
+
const args = asRecord(rawArgs);
|
|
3399
|
+
const response = await context.client.privateGet(
|
|
3400
|
+
"/api/v5/finance/staking-defi/offers",
|
|
3401
|
+
compactObject({
|
|
3402
|
+
productId: readString(args, "productId"),
|
|
3403
|
+
protocolType: readString(args, "protocolType"),
|
|
3404
|
+
ccy: readString(args, "ccy")
|
|
3405
|
+
}),
|
|
3406
|
+
privateRateLimit("onchain_earn_get_offers", 3)
|
|
3407
|
+
);
|
|
3408
|
+
return normalizeResponse(response);
|
|
3409
|
+
}
|
|
3410
|
+
},
|
|
3411
|
+
// -------------------------------------------------------------------------
|
|
3412
|
+
// Purchase
|
|
3413
|
+
// -------------------------------------------------------------------------
|
|
3414
|
+
{
|
|
3415
|
+
name: "onchain_earn_purchase",
|
|
3416
|
+
module: "earn.onchain",
|
|
3417
|
+
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.",
|
|
3418
|
+
isWrite: true,
|
|
3419
|
+
inputSchema: {
|
|
3420
|
+
type: "object",
|
|
3421
|
+
properties: {
|
|
3422
|
+
productId: {
|
|
3423
|
+
type: "string",
|
|
3424
|
+
description: "Product ID to purchase"
|
|
3425
|
+
},
|
|
3426
|
+
investData: {
|
|
3427
|
+
type: "array",
|
|
3428
|
+
description: "Investment data array: [{ccy, amt}]. Each item specifies currency and amount.",
|
|
3429
|
+
items: {
|
|
3430
|
+
type: "object",
|
|
3431
|
+
properties: {
|
|
3432
|
+
ccy: { type: "string", description: "Currency, e.g. ETH" },
|
|
3433
|
+
amt: { type: "string", description: "Amount to invest" }
|
|
3434
|
+
},
|
|
3435
|
+
required: ["ccy", "amt"]
|
|
3436
|
+
}
|
|
3437
|
+
},
|
|
3438
|
+
term: {
|
|
3439
|
+
type: "string",
|
|
3440
|
+
description: "Investment term in days. Required for fixed-term products."
|
|
3441
|
+
},
|
|
3442
|
+
tag: {
|
|
3443
|
+
type: "string",
|
|
3444
|
+
description: "Order tag for tracking (optional)."
|
|
3445
|
+
}
|
|
3446
|
+
},
|
|
3447
|
+
required: ["productId", "investData"]
|
|
3448
|
+
},
|
|
3449
|
+
handler: async (rawArgs, context) => {
|
|
3450
|
+
assertNotDemo(context.config, "onchain_earn_purchase");
|
|
3451
|
+
const args = asRecord(rawArgs);
|
|
3452
|
+
const response = await context.client.privatePost(
|
|
3453
|
+
"/api/v5/finance/staking-defi/purchase",
|
|
3454
|
+
compactObject({
|
|
3455
|
+
productId: requireString(args, "productId"),
|
|
3456
|
+
investData: args.investData,
|
|
3457
|
+
term: readString(args, "term"),
|
|
3458
|
+
tag: readString(args, "tag")
|
|
3459
|
+
}),
|
|
3460
|
+
privateRateLimit("onchain_earn_purchase", 2)
|
|
3461
|
+
);
|
|
3462
|
+
return normalizeResponse(response);
|
|
3463
|
+
}
|
|
3464
|
+
},
|
|
3465
|
+
// -------------------------------------------------------------------------
|
|
3466
|
+
// Redeem
|
|
3467
|
+
// -------------------------------------------------------------------------
|
|
3468
|
+
{
|
|
3469
|
+
name: "onchain_earn_redeem",
|
|
3470
|
+
module: "earn.onchain",
|
|
3471
|
+
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.",
|
|
3472
|
+
isWrite: true,
|
|
3473
|
+
inputSchema: {
|
|
3474
|
+
type: "object",
|
|
3475
|
+
properties: {
|
|
3476
|
+
ordId: {
|
|
3477
|
+
type: "string",
|
|
3478
|
+
description: "Order ID to redeem"
|
|
3479
|
+
},
|
|
3480
|
+
protocolType: {
|
|
3481
|
+
type: "string",
|
|
3482
|
+
description: "Protocol type: staking, defi"
|
|
3483
|
+
},
|
|
3484
|
+
allowEarlyRedeem: {
|
|
3485
|
+
type: "boolean",
|
|
3486
|
+
description: "Allow early redemption for fixed-term products (may incur penalties). Default false."
|
|
3487
|
+
}
|
|
3488
|
+
},
|
|
3489
|
+
required: ["ordId", "protocolType"]
|
|
3490
|
+
},
|
|
3491
|
+
handler: async (rawArgs, context) => {
|
|
3492
|
+
assertNotDemo(context.config, "onchain_earn_redeem");
|
|
3493
|
+
const args = asRecord(rawArgs);
|
|
3494
|
+
const response = await context.client.privatePost(
|
|
3495
|
+
"/api/v5/finance/staking-defi/redeem",
|
|
3496
|
+
compactObject({
|
|
3497
|
+
ordId: requireString(args, "ordId"),
|
|
3498
|
+
protocolType: requireString(args, "protocolType"),
|
|
3499
|
+
allowEarlyRedeem: readBoolean(args, "allowEarlyRedeem")
|
|
3500
|
+
}),
|
|
3501
|
+
privateRateLimit("onchain_earn_redeem", 2)
|
|
3502
|
+
);
|
|
3503
|
+
return normalizeResponse(response);
|
|
3504
|
+
}
|
|
3505
|
+
},
|
|
3506
|
+
// -------------------------------------------------------------------------
|
|
3507
|
+
// Cancel
|
|
3508
|
+
// -------------------------------------------------------------------------
|
|
3509
|
+
{
|
|
3510
|
+
name: "onchain_earn_cancel",
|
|
3511
|
+
module: "earn.onchain",
|
|
3512
|
+
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.",
|
|
3513
|
+
isWrite: true,
|
|
3514
|
+
inputSchema: {
|
|
3515
|
+
type: "object",
|
|
3516
|
+
properties: {
|
|
3517
|
+
ordId: {
|
|
3518
|
+
type: "string",
|
|
3519
|
+
description: "Order ID to cancel"
|
|
3520
|
+
},
|
|
3521
|
+
protocolType: {
|
|
3522
|
+
type: "string",
|
|
3523
|
+
description: "Protocol type: staking, defi"
|
|
3524
|
+
}
|
|
3525
|
+
},
|
|
3526
|
+
required: ["ordId", "protocolType"]
|
|
3527
|
+
},
|
|
3528
|
+
handler: async (rawArgs, context) => {
|
|
3529
|
+
assertNotDemo(context.config, "onchain_earn_cancel");
|
|
3530
|
+
const args = asRecord(rawArgs);
|
|
3531
|
+
const response = await context.client.privatePost(
|
|
3532
|
+
"/api/v5/finance/staking-defi/cancel",
|
|
3533
|
+
{
|
|
3534
|
+
ordId: requireString(args, "ordId"),
|
|
3535
|
+
protocolType: requireString(args, "protocolType")
|
|
3536
|
+
},
|
|
3537
|
+
privateRateLimit("onchain_earn_cancel", 2)
|
|
3538
|
+
);
|
|
3539
|
+
return normalizeResponse(response);
|
|
3540
|
+
}
|
|
3541
|
+
},
|
|
3542
|
+
// -------------------------------------------------------------------------
|
|
3543
|
+
// Get Active Orders
|
|
3544
|
+
// -------------------------------------------------------------------------
|
|
3545
|
+
{
|
|
3546
|
+
name: "onchain_earn_get_active_orders",
|
|
3547
|
+
module: "earn.onchain",
|
|
3548
|
+
description: "Get active on-chain earn orders. Returns current staking/DeFi investments. Private endpoint. Rate limit: 3 req/s.",
|
|
3549
|
+
isWrite: false,
|
|
3550
|
+
inputSchema: {
|
|
3551
|
+
type: "object",
|
|
3552
|
+
properties: {
|
|
3553
|
+
productId: {
|
|
3554
|
+
type: "string",
|
|
3555
|
+
description: "Filter by product ID. Omit for all."
|
|
3556
|
+
},
|
|
3557
|
+
protocolType: {
|
|
3558
|
+
type: "string",
|
|
3559
|
+
description: "Filter by protocol type: staking, defi. Omit for all."
|
|
3560
|
+
},
|
|
3561
|
+
ccy: {
|
|
3562
|
+
type: "string",
|
|
3563
|
+
description: "Filter by currency, e.g. ETH. Omit for all."
|
|
3564
|
+
},
|
|
3565
|
+
state: {
|
|
3566
|
+
type: "string",
|
|
3567
|
+
description: "Filter by state: 8 (pending), 13 (cancelling), 9 (onchain), 1 (earning), 2 (redeeming). Omit for all."
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
},
|
|
3571
|
+
handler: async (rawArgs, context) => {
|
|
3572
|
+
const args = asRecord(rawArgs);
|
|
3573
|
+
const response = await context.client.privateGet(
|
|
3574
|
+
"/api/v5/finance/staking-defi/orders-active",
|
|
3575
|
+
compactObject({
|
|
3576
|
+
productId: readString(args, "productId"),
|
|
3577
|
+
protocolType: readString(args, "protocolType"),
|
|
3578
|
+
ccy: readString(args, "ccy"),
|
|
3579
|
+
state: readString(args, "state")
|
|
3580
|
+
}),
|
|
3581
|
+
privateRateLimit("onchain_earn_get_active_orders", 3)
|
|
3582
|
+
);
|
|
3583
|
+
return normalizeResponse(response);
|
|
3584
|
+
}
|
|
3585
|
+
},
|
|
3586
|
+
// -------------------------------------------------------------------------
|
|
3587
|
+
// Get Order History
|
|
3588
|
+
// -------------------------------------------------------------------------
|
|
3589
|
+
{
|
|
3590
|
+
name: "onchain_earn_get_order_history",
|
|
3591
|
+
module: "earn.onchain",
|
|
3592
|
+
description: "Get on-chain earn order history. Returns past staking/DeFi investments including redeemed orders. Private endpoint. Rate limit: 3 req/s.",
|
|
3593
|
+
isWrite: false,
|
|
3594
|
+
inputSchema: {
|
|
3595
|
+
type: "object",
|
|
3596
|
+
properties: {
|
|
3597
|
+
productId: {
|
|
3598
|
+
type: "string",
|
|
3599
|
+
description: "Filter by product ID. Omit for all."
|
|
3600
|
+
},
|
|
3601
|
+
protocolType: {
|
|
3602
|
+
type: "string",
|
|
3603
|
+
description: "Filter by protocol type: staking, defi. Omit for all."
|
|
3604
|
+
},
|
|
3605
|
+
ccy: {
|
|
3606
|
+
type: "string",
|
|
3607
|
+
description: "Filter by currency, e.g. ETH. Omit for all."
|
|
3608
|
+
},
|
|
3609
|
+
after: {
|
|
3610
|
+
type: "string",
|
|
3611
|
+
description: "Pagination: return results before this order ID"
|
|
3612
|
+
},
|
|
3613
|
+
before: {
|
|
3614
|
+
type: "string",
|
|
3615
|
+
description: "Pagination: return results after this order ID"
|
|
3616
|
+
},
|
|
3617
|
+
limit: {
|
|
3618
|
+
type: "string",
|
|
3619
|
+
description: "Max results to return (default 100, max 100)"
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
},
|
|
3623
|
+
handler: async (rawArgs, context) => {
|
|
3624
|
+
const args = asRecord(rawArgs);
|
|
3625
|
+
const response = await context.client.privateGet(
|
|
3626
|
+
"/api/v5/finance/staking-defi/orders-history",
|
|
3627
|
+
compactObject({
|
|
3628
|
+
productId: readString(args, "productId"),
|
|
3629
|
+
protocolType: readString(args, "protocolType"),
|
|
3630
|
+
ccy: readString(args, "ccy"),
|
|
3631
|
+
after: readString(args, "after"),
|
|
3632
|
+
before: readString(args, "before"),
|
|
3633
|
+
limit: readString(args, "limit")
|
|
3634
|
+
}),
|
|
3635
|
+
privateRateLimit("onchain_earn_get_order_history", 3)
|
|
3636
|
+
);
|
|
3637
|
+
return normalizeResponse(response);
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
];
|
|
3641
|
+
}
|
|
3052
3642
|
function normalize6(response) {
|
|
3053
3643
|
return {
|
|
3054
3644
|
endpoint: response.endpoint,
|
|
@@ -3566,7 +4156,7 @@ function registerOptionTools() {
|
|
|
3566
4156
|
},
|
|
3567
4157
|
sz: {
|
|
3568
4158
|
type: "string",
|
|
3569
|
-
description: "Number of contracts"
|
|
4159
|
+
description: "Number of contracts (NOT USDT amount). Use market_get_instruments to get ctVal for conversion."
|
|
3570
4160
|
},
|
|
3571
4161
|
px: {
|
|
3572
4162
|
type: "string",
|
|
@@ -3673,7 +4263,7 @@ function registerOptionTools() {
|
|
|
3673
4263
|
instId: { type: "string", description: "e.g. BTC-USD-241227-50000-C" },
|
|
3674
4264
|
ordId: { type: "string" },
|
|
3675
4265
|
clOrdId: { type: "string" },
|
|
3676
|
-
newSz: { type: "string", description: "New
|
|
4266
|
+
newSz: { type: "string", description: "New number of contracts (NOT USDT amount)" },
|
|
3677
4267
|
newPx: { type: "string", description: "New price" }
|
|
3678
4268
|
},
|
|
3679
4269
|
required: ["instId"]
|
|
@@ -4635,7 +5225,7 @@ function registerSwapTradeTools() {
|
|
|
4635
5225
|
},
|
|
4636
5226
|
sz: {
|
|
4637
5227
|
type: "string",
|
|
4638
|
-
description: "
|
|
5228
|
+
description: "Number of contracts (NOT USDT amount). Use market_get_instruments to get ctVal for conversion."
|
|
4639
5229
|
},
|
|
4640
5230
|
px: {
|
|
4641
5231
|
type: "string",
|
|
@@ -4900,7 +5490,7 @@ function registerSwapTradeTools() {
|
|
|
4900
5490
|
properties: {
|
|
4901
5491
|
instId: { type: "string", description: "e.g. BTC-USDT-SWAP" },
|
|
4902
5492
|
algoId: { type: "string", description: "Algo order ID" },
|
|
4903
|
-
newSz: { type: "string", description: "New
|
|
5493
|
+
newSz: { type: "string", description: "New number of contracts (NOT USDT amount)" },
|
|
4904
5494
|
newTpTriggerPx: { type: "string", description: "New TP trigger price" },
|
|
4905
5495
|
newTpOrdPx: { type: "string", description: "New TP order price; -1=market" },
|
|
4906
5496
|
newSlTriggerPx: { type: "string", description: "New SL trigger price" },
|
|
@@ -5253,6 +5843,8 @@ function allToolSpecs() {
|
|
|
5253
5843
|
...registerAlgoTradeTools(),
|
|
5254
5844
|
...registerAccountTools(),
|
|
5255
5845
|
...registerBotTools(),
|
|
5846
|
+
...registerEarnTools(),
|
|
5847
|
+
...registerOnchainEarnTools(),
|
|
5256
5848
|
...registerAuditTools()
|
|
5257
5849
|
];
|
|
5258
5850
|
}
|
|
@@ -5292,8 +5884,15 @@ function readTomlProfile(profileName) {
|
|
|
5292
5884
|
return config.profiles?.[name] ?? {};
|
|
5293
5885
|
}
|
|
5294
5886
|
var BASE_MODULES = MODULES.filter(
|
|
5295
|
-
(m) => !BOT_SUB_MODULE_IDS.includes(m)
|
|
5887
|
+
(m) => !BOT_SUB_MODULE_IDS.includes(m) && !EARN_SUB_MODULE_IDS.includes(m)
|
|
5296
5888
|
);
|
|
5889
|
+
function expandShorthand(moduleId) {
|
|
5890
|
+
if (moduleId === "all") return [...BASE_MODULES, ...BOT_SUB_MODULE_IDS];
|
|
5891
|
+
if (moduleId === "earn" || moduleId === "earn.all") return [...EARN_SUB_MODULE_IDS];
|
|
5892
|
+
if (moduleId === "bot") return [...BOT_DEFAULT_SUB_MODULES];
|
|
5893
|
+
if (moduleId === "bot.all") return [...BOT_SUB_MODULE_IDS];
|
|
5894
|
+
return null;
|
|
5895
|
+
}
|
|
5297
5896
|
function parseModuleList(rawModules) {
|
|
5298
5897
|
if (!rawModules || rawModules.trim().length === 0) {
|
|
5299
5898
|
return [...DEFAULT_MODULES];
|
|
@@ -5302,32 +5901,28 @@ function parseModuleList(rawModules) {
|
|
|
5302
5901
|
if (trimmed === "all") {
|
|
5303
5902
|
return [...BASE_MODULES, ...BOT_SUB_MODULE_IDS];
|
|
5304
5903
|
}
|
|
5305
|
-
const requested = trimmed.split(",").map((
|
|
5904
|
+
const requested = trimmed.split(",").map((s) => s.trim()).filter(Boolean);
|
|
5306
5905
|
if (requested.length === 0) {
|
|
5307
5906
|
return [...DEFAULT_MODULES];
|
|
5308
5907
|
}
|
|
5309
5908
|
const deduped = /* @__PURE__ */ new Set();
|
|
5310
5909
|
for (const moduleId of requested) {
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
}
|
|
5315
|
-
if (moduleId === "bot.all") {
|
|
5316
|
-
for (const sub of BOT_SUB_MODULE_IDS) deduped.add(sub);
|
|
5910
|
+
const expanded = expandShorthand(moduleId);
|
|
5911
|
+
if (expanded) {
|
|
5912
|
+
expanded.forEach((sub) => deduped.add(sub));
|
|
5317
5913
|
continue;
|
|
5318
5914
|
}
|
|
5319
5915
|
if (!MODULES.includes(moduleId)) {
|
|
5320
5916
|
throw new ConfigError(
|
|
5321
5917
|
`Unknown module "${moduleId}".`,
|
|
5322
|
-
`Use one of: ${MODULES.join(", ")}, "bot", "bot.all", or "all".`
|
|
5918
|
+
`Use one of: ${MODULES.join(", ")}, "earn", "earn.all", "bot", "bot.all", or "all".`
|
|
5323
5919
|
);
|
|
5324
5920
|
}
|
|
5325
5921
|
deduped.add(moduleId);
|
|
5326
5922
|
}
|
|
5327
5923
|
return Array.from(deduped);
|
|
5328
5924
|
}
|
|
5329
|
-
function
|
|
5330
|
-
const toml = readTomlProfile(cli.profile);
|
|
5925
|
+
function loadCredentials(toml) {
|
|
5331
5926
|
const apiKey = process.env.OKX_API_KEY?.trim() ?? toml.api_key;
|
|
5332
5927
|
const secretKey = process.env.OKX_SECRET_KEY?.trim() ?? toml.secret_key;
|
|
5333
5928
|
const passphrase = process.env.OKX_PASSPHRASE?.trim() ?? toml.passphrase;
|
|
@@ -5339,23 +5934,34 @@ function loadConfig(cli) {
|
|
|
5339
5934
|
"Set OKX_API_KEY, OKX_SECRET_KEY and OKX_PASSPHRASE together (env vars or config.toml profile)."
|
|
5340
5935
|
);
|
|
5341
5936
|
}
|
|
5342
|
-
|
|
5343
|
-
|
|
5937
|
+
return { apiKey, secretKey, passphrase, hasAuth };
|
|
5938
|
+
}
|
|
5939
|
+
function resolveSite(cliSite, tomlSite) {
|
|
5940
|
+
const rawSite = cliSite?.trim() ?? process.env.OKX_SITE?.trim() ?? tomlSite ?? "global";
|
|
5344
5941
|
if (!SITE_IDS.includes(rawSite)) {
|
|
5345
5942
|
throw new ConfigError(
|
|
5346
5943
|
`Unknown site "${rawSite}".`,
|
|
5347
5944
|
`Use one of: ${SITE_IDS.join(", ")}.`
|
|
5348
5945
|
);
|
|
5349
5946
|
}
|
|
5350
|
-
|
|
5351
|
-
|
|
5947
|
+
return rawSite;
|
|
5948
|
+
}
|
|
5949
|
+
function resolveBaseUrl(site, tomlBaseUrl) {
|
|
5950
|
+
const rawBaseUrl = process.env.OKX_API_BASE_URL?.trim() ?? tomlBaseUrl ?? OKX_SITES[site].apiBaseUrl;
|
|
5352
5951
|
if (!rawBaseUrl.startsWith("http://") && !rawBaseUrl.startsWith("https://")) {
|
|
5353
5952
|
throw new ConfigError(
|
|
5354
5953
|
`Invalid base URL "${rawBaseUrl}".`,
|
|
5355
5954
|
"OKX_API_BASE_URL must start with http:// or https://"
|
|
5356
5955
|
);
|
|
5357
5956
|
}
|
|
5358
|
-
|
|
5957
|
+
return rawBaseUrl.replace(/\/+$/, "");
|
|
5958
|
+
}
|
|
5959
|
+
function loadConfig(cli) {
|
|
5960
|
+
const toml = readTomlProfile(cli.profile);
|
|
5961
|
+
const creds = loadCredentials(toml);
|
|
5962
|
+
const demo = cli.demo || process.env.OKX_DEMO === "1" || process.env.OKX_DEMO === "true" || (toml.demo ?? false);
|
|
5963
|
+
const site = resolveSite(cli.site, toml.site);
|
|
5964
|
+
const baseUrl = resolveBaseUrl(site, toml.base_url);
|
|
5359
5965
|
const rawTimeout = process.env.OKX_TIMEOUT_MS ? Number(process.env.OKX_TIMEOUT_MS) : toml.timeout_ms ?? 15e3;
|
|
5360
5966
|
if (!Number.isFinite(rawTimeout) || rawTimeout <= 0) {
|
|
5361
5967
|
throw new ConfigError(
|
|
@@ -5363,11 +5969,15 @@ function loadConfig(cli) {
|
|
|
5363
5969
|
"Set OKX_TIMEOUT_MS as a positive integer in milliseconds."
|
|
5364
5970
|
);
|
|
5365
5971
|
}
|
|
5972
|
+
const rawProxyUrl = toml.proxy_url?.trim();
|
|
5973
|
+
if (rawProxyUrl && !rawProxyUrl.startsWith("http://") && !rawProxyUrl.startsWith("https://")) {
|
|
5974
|
+
throw new ConfigError(
|
|
5975
|
+
`Invalid proxy URL "${rawProxyUrl}".`,
|
|
5976
|
+
"proxy_url must start with http:// or https://. SOCKS proxies are not supported."
|
|
5977
|
+
);
|
|
5978
|
+
}
|
|
5366
5979
|
return {
|
|
5367
|
-
|
|
5368
|
-
secretKey,
|
|
5369
|
-
passphrase,
|
|
5370
|
-
hasAuth,
|
|
5980
|
+
...creds,
|
|
5371
5981
|
baseUrl,
|
|
5372
5982
|
timeoutMs: Math.floor(rawTimeout),
|
|
5373
5983
|
modules: parseModuleList(cli.modules),
|
|
@@ -5375,7 +5985,9 @@ function loadConfig(cli) {
|
|
|
5375
5985
|
demo,
|
|
5376
5986
|
site,
|
|
5377
5987
|
userAgent: cli.userAgent,
|
|
5378
|
-
sourceTag: cli.sourceTag ?? DEFAULT_SOURCE_TAG
|
|
5988
|
+
sourceTag: cli.sourceTag ?? DEFAULT_SOURCE_TAG,
|
|
5989
|
+
proxyUrl: rawProxyUrl || void 0,
|
|
5990
|
+
verbose: cli.verbose ?? false
|
|
5379
5991
|
};
|
|
5380
5992
|
}
|
|
5381
5993
|
var CACHE_FILE = join2(homedir2(), ".okx", "update-check.json");
|
|
@@ -5660,7 +6272,7 @@ var _require = createRequire(import.meta.url);
|
|
|
5660
6272
|
var pkg = _require("../package.json");
|
|
5661
6273
|
var SERVER_NAME = "okx-trade-mcp";
|
|
5662
6274
|
var SERVER_VERSION = pkg.version;
|
|
5663
|
-
var GIT_HASH = true ? "
|
|
6275
|
+
var GIT_HASH = true ? "4427916" : "dev";
|
|
5664
6276
|
|
|
5665
6277
|
// src/server.ts
|
|
5666
6278
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|