@zebpay_rajesh/zebpay-mcp-server 0.1.0
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.
Potentially problematic release.
This version of @zebpay_rajesh/zebpay-mcp-server might be problematic. Click here for more details.
- package/.env.example +14 -0
- package/README.md +223 -0
- package/dist/__tests__/errors.test.d.ts +5 -0
- package/dist/__tests__/errors.test.js +147 -0
- package/dist/__tests__/errors.test.js.map +1 -0
- package/dist/__tests__/prompts.test.d.ts +1 -0
- package/dist/__tests__/prompts.test.js +73 -0
- package/dist/__tests__/prompts.test.js.map +1 -0
- package/dist/__tests__/resources.test.d.ts +1 -0
- package/dist/__tests__/resources.test.js +79 -0
- package/dist/__tests__/resources.test.js.map +1 -0
- package/dist/__tests__/validation.test.d.ts +15 -0
- package/dist/__tests__/validation.test.js +64 -0
- package/dist/__tests__/validation.test.js.map +1 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.js +81 -0
- package/dist/config.js.map +1 -0
- package/dist/http/httpClient.d.ts +40 -0
- package/dist/http/httpClient.js +341 -0
- package/dist/http/httpClient.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/errors.d.ts +21 -0
- package/dist/mcp/errors.js +214 -0
- package/dist/mcp/errors.js.map +1 -0
- package/dist/mcp/logging.d.ts +21 -0
- package/dist/mcp/logging.js +241 -0
- package/dist/mcp/logging.js.map +1 -0
- package/dist/mcp/prompts.d.ts +9 -0
- package/dist/mcp/prompts.js +165 -0
- package/dist/mcp/prompts.js.map +1 -0
- package/dist/mcp/resources.d.ts +9 -0
- package/dist/mcp/resources.js +125 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/tools_futures.d.ts +5 -0
- package/dist/mcp/tools_futures.js +694 -0
- package/dist/mcp/tools_futures.js.map +1 -0
- package/dist/mcp/tools_spot.d.ts +11 -0
- package/dist/mcp/tools_spot.js +2225 -0
- package/dist/mcp/tools_spot.js.map +1 -0
- package/dist/private/FuturesClient.d.ts +57 -0
- package/dist/private/FuturesClient.js +181 -0
- package/dist/private/FuturesClient.js.map +1 -0
- package/dist/private/SpotClient.d.ts +44 -0
- package/dist/private/SpotClient.js +201 -0
- package/dist/private/SpotClient.js.map +1 -0
- package/dist/private/ZebpayAPI.d.ts +19 -0
- package/dist/private/ZebpayAPI.js +172 -0
- package/dist/private/ZebpayAPI.js.map +1 -0
- package/dist/public/PublicClient.d.ts +79 -0
- package/dist/public/PublicClient.js +283 -0
- package/dist/public/PublicClient.js.map +1 -0
- package/dist/public/PublicFuturesClient.d.ts +27 -0
- package/dist/public/PublicFuturesClient.js +187 -0
- package/dist/public/PublicFuturesClient.js.map +1 -0
- package/dist/security/credentials.d.ts +42 -0
- package/dist/security/credentials.js +80 -0
- package/dist/security/credentials.js.map +1 -0
- package/dist/security/signing.d.ts +33 -0
- package/dist/security/signing.js +56 -0
- package/dist/security/signing.js.map +1 -0
- package/dist/types/responses.d.ts +130 -0
- package/dist/types/responses.js +6 -0
- package/dist/types/responses.js.map +1 -0
- package/dist/utils/cache.d.ts +29 -0
- package/dist/utils/cache.js +72 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/fileLogger.d.ts +10 -0
- package/dist/utils/fileLogger.js +81 -0
- package/dist/utils/fileLogger.js.map +1 -0
- package/dist/utils/metrics.d.ts +35 -0
- package/dist/utils/metrics.js +94 -0
- package/dist/utils/metrics.js.map +1 -0
- package/dist/utils/responseFormatter.d.ts +93 -0
- package/dist/utils/responseFormatter.js +268 -0
- package/dist/utils/responseFormatter.js.map +1 -0
- package/dist/validation/schemas.d.ts +70 -0
- package/dist/validation/schemas.js +48 -0
- package/dist/validation/schemas.js.map +1 -0
- package/dist/validation/validators.d.ts +28 -0
- package/dist/validation/validators.js +129 -0
- package/dist/validation/validators.js.map +1 -0
- package/docs/LOGGING.md +371 -0
- package/docs/zebpay-ai-trading-beginner.png +0 -0
- package/mcp-config.json.example +20 -0
- package/package.json +54 -0
- package/scripts/README.md +103 -0
- package/scripts/clear-logs.js +52 -0
- package/scripts/log-stats.js +264 -0
- package/scripts/log-viewer.js +288 -0
- package/server.json +31 -0
- package/src/__tests__/errors.test.ts +180 -0
- package/src/__tests__/prompts.test.ts +89 -0
- package/src/__tests__/resources.test.ts +95 -0
- package/src/__tests__/validation.test.ts +88 -0
- package/src/config.ts +108 -0
- package/src/http/httpClient.ts +398 -0
- package/src/index.ts +71 -0
- package/src/mcp/errors.ts +262 -0
- package/src/mcp/logging.ts +284 -0
- package/src/mcp/prompts.ts +206 -0
- package/src/mcp/resources.ts +163 -0
- package/src/mcp/tools_futures.ts +874 -0
- package/src/mcp/tools_spot.ts +2702 -0
- package/src/private/FuturesClient.ts +189 -0
- package/src/private/SpotClient.ts +250 -0
- package/src/private/ZebpayAPI.ts +205 -0
- package/src/public/PublicClient.ts +381 -0
- package/src/public/PublicFuturesClient.ts +228 -0
- package/src/security/credentials.ts +114 -0
- package/src/security/signing.ts +98 -0
- package/src/types/responses.ts +146 -0
- package/src/utils/cache.ts +90 -0
- package/src/utils/fileLogger.ts +88 -0
- package/src/utils/metrics.ts +135 -0
- package/src/utils/responseFormatter.ts +361 -0
- package/src/validation/schemas.ts +66 -0
- package/src/validation/validators.ts +189 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Futures client for basic trading and positions.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ZebpayAPI } from "./ZebpayAPI.js";
|
|
6
|
+
|
|
7
|
+
export class FuturesClient {
|
|
8
|
+
constructor(private readonly api: ZebpayAPI) {}
|
|
9
|
+
|
|
10
|
+
// GET /api/v1/wallet/balance
|
|
11
|
+
async getWalletBalance(): Promise<unknown> {
|
|
12
|
+
return this.api.request({
|
|
13
|
+
method: "GET",
|
|
14
|
+
path: "/wallet/balance",
|
|
15
|
+
useFutures: true,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// POST /api/v1/trade/order
|
|
20
|
+
async placeOrder(params: {
|
|
21
|
+
symbol: string;
|
|
22
|
+
amount: number | string;
|
|
23
|
+
side: "BUY" | "SELL";
|
|
24
|
+
type: "MARKET" | "LIMIT";
|
|
25
|
+
marginAsset: string;
|
|
26
|
+
price?: number | string; // for LIMIT orders
|
|
27
|
+
clientOrderId?: string;
|
|
28
|
+
}): Promise<unknown> {
|
|
29
|
+
const body: Record<string, unknown> = {
|
|
30
|
+
symbol: params.symbol,
|
|
31
|
+
amount: typeof params.amount === "string" ? params.amount : String(params.amount),
|
|
32
|
+
side: params.side,
|
|
33
|
+
type: params.type,
|
|
34
|
+
marginAsset: params.marginAsset,
|
|
35
|
+
};
|
|
36
|
+
if (params.price !== undefined) body.price = typeof params.price === "string" ? params.price : String(params.price);
|
|
37
|
+
if (params.clientOrderId) body.clientOrderId = params.clientOrderId;
|
|
38
|
+
return this.api.request({
|
|
39
|
+
method: "POST",
|
|
40
|
+
path: "/trade/order",
|
|
41
|
+
body,
|
|
42
|
+
useFutures: true,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// POST /api/v1/trade/addMargin
|
|
47
|
+
async addMargin(params: { symbol: string; amount: number | string; marginAsset: string }): Promise<unknown> {
|
|
48
|
+
const body = {
|
|
49
|
+
symbol: params.symbol,
|
|
50
|
+
amount: typeof params.amount === "string" ? params.amount : String(params.amount),
|
|
51
|
+
marginAsset: params.marginAsset,
|
|
52
|
+
};
|
|
53
|
+
return this.api.request({
|
|
54
|
+
method: "POST",
|
|
55
|
+
path: "/trade/addMargin",
|
|
56
|
+
body,
|
|
57
|
+
useFutures: true,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// POST /api/v1/trade/reduceMargin
|
|
62
|
+
async reduceMargin(params: { symbol: string; amount: number | string; marginAsset: string }): Promise<unknown> {
|
|
63
|
+
const body = {
|
|
64
|
+
symbol: params.symbol,
|
|
65
|
+
amount: typeof params.amount === "string" ? params.amount : String(params.amount),
|
|
66
|
+
marginAsset: params.marginAsset,
|
|
67
|
+
};
|
|
68
|
+
return this.api.request({
|
|
69
|
+
method: "POST",
|
|
70
|
+
path: "/trade/reduceMargin",
|
|
71
|
+
body,
|
|
72
|
+
useFutures: true,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// GET /api/v1/trade/order/open-orders
|
|
77
|
+
async getOpenOrders(params: { symbol?: string; limit?: number; since?: string | number }): Promise<unknown> {
|
|
78
|
+
const query: Record<string, string> = {};
|
|
79
|
+
if (params.symbol) query.symbol = params.symbol;
|
|
80
|
+
if (params.limit !== undefined) query.limit = String(params.limit);
|
|
81
|
+
if (params.since !== undefined) query.since = String(params.since);
|
|
82
|
+
return this.api.request({
|
|
83
|
+
method: "GET",
|
|
84
|
+
path: "/trade/order/open-orders",
|
|
85
|
+
queryParams: query,
|
|
86
|
+
useFutures: true,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// GET /api/v1/trade/order/history
|
|
91
|
+
async getOrderHistory(params: { page?: number; pageSize?: number }): Promise<unknown> {
|
|
92
|
+
const query: Record<string, string> = {};
|
|
93
|
+
if (params.page !== undefined) query.page = String(params.page);
|
|
94
|
+
if (params.pageSize !== undefined) query.pageSize = String(params.pageSize);
|
|
95
|
+
return this.api.request({
|
|
96
|
+
method: "GET",
|
|
97
|
+
path: "/trade/order/history",
|
|
98
|
+
queryParams: query,
|
|
99
|
+
useFutures: true,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// GET /api/v1/trade/linkedOrders/{clientOrderId}
|
|
104
|
+
async getLinkedOrders(clientOrderId: string): Promise<unknown> {
|
|
105
|
+
const path = `/trade/linkedOrders/${encodeURIComponent(clientOrderId)}`;
|
|
106
|
+
return this.api.request({
|
|
107
|
+
method: "GET",
|
|
108
|
+
path,
|
|
109
|
+
useFutures: true,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// GET /api/v1/trade/positions
|
|
114
|
+
async getPositions(params: { status?: string } = {}): Promise<unknown> {
|
|
115
|
+
const query: Record<string, string> = {};
|
|
116
|
+
if (params.status) query.status = params.status;
|
|
117
|
+
return this.api.request({
|
|
118
|
+
method: "GET",
|
|
119
|
+
path: "/trade/positions",
|
|
120
|
+
queryParams: query,
|
|
121
|
+
useFutures: true,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// GET /api/v1/trade/history
|
|
126
|
+
async getTradeHistory(params: { page?: number; pageSize?: number }): Promise<unknown> {
|
|
127
|
+
const query: Record<string, string> = {};
|
|
128
|
+
if (params.page !== undefined) query.page = String(params.page);
|
|
129
|
+
if (params.pageSize !== undefined) query.pageSize = String(params.pageSize);
|
|
130
|
+
return this.api.request({
|
|
131
|
+
method: "GET",
|
|
132
|
+
path: "/trade/history",
|
|
133
|
+
queryParams: query,
|
|
134
|
+
useFutures: true,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// GET /api/v1/trade/transaction/history
|
|
139
|
+
async getTransactionHistory(params: { page?: number; pageSize?: number }): Promise<unknown> {
|
|
140
|
+
const query: Record<string, string> = {};
|
|
141
|
+
if (params.page !== undefined) query.page = String(params.page);
|
|
142
|
+
if (params.pageSize !== undefined) query.pageSize = String(params.pageSize);
|
|
143
|
+
return this.api.request({
|
|
144
|
+
method: "GET",
|
|
145
|
+
path: "/trade/transaction/history",
|
|
146
|
+
queryParams: query,
|
|
147
|
+
useFutures: true,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// DELETE /api/v1/trade/order
|
|
152
|
+
async deleteOrder(params: { orderId?: string | number; clientOrderId?: string }): Promise<unknown> {
|
|
153
|
+
const query: Record<string, string> = {};
|
|
154
|
+
if (params.orderId !== undefined) query.orderId = String(params.orderId);
|
|
155
|
+
if (params.clientOrderId !== undefined) query.clientOrderId = params.clientOrderId;
|
|
156
|
+
return this.api.request({
|
|
157
|
+
method: "DELETE",
|
|
158
|
+
path: "/trade/order",
|
|
159
|
+
queryParams: query,
|
|
160
|
+
useFutures: true,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// GET /api/v1/trade/userLeverage
|
|
165
|
+
async getUserLeverage(params: { symbol: string }): Promise<unknown> {
|
|
166
|
+
const query: Record<string, string> = { symbol: params.symbol };
|
|
167
|
+
return this.api.request({
|
|
168
|
+
method: "GET",
|
|
169
|
+
path: "/trade/userLeverage",
|
|
170
|
+
queryParams: query,
|
|
171
|
+
useFutures: true,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// POST /api/v1/trade/update/userLeverage
|
|
176
|
+
async updateUserLeverage(params: { symbol: string; leverage: number | string }): Promise<unknown> {
|
|
177
|
+
const body = {
|
|
178
|
+
symbol: params.symbol,
|
|
179
|
+
leverage: typeof params.leverage === "string" ? params.leverage : String(params.leverage),
|
|
180
|
+
};
|
|
181
|
+
return this.api.request({
|
|
182
|
+
method: "POST",
|
|
183
|
+
path: "/trade/update/userLeverage",
|
|
184
|
+
body,
|
|
185
|
+
useFutures: true,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Spot client for market orders.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ZebpayAPI } from "./ZebpayAPI.js";
|
|
6
|
+
|
|
7
|
+
export interface SpotMarketOrderParams {
|
|
8
|
+
symbol: string; // e.g., BTC-INR
|
|
9
|
+
side: "BUY" | "SELL";
|
|
10
|
+
quoteOrderAmount?: string; // Quote currency value (e.g., INR) - will be mapped to body based on side
|
|
11
|
+
amount?: string; // Base currency amount (e.g., BTC) - will be mapped to body based on side
|
|
12
|
+
clientOrderId?: string; // Must contain only letters, numbers, dots, colons, slashes, underscores, and hyphens, 1-36 characters
|
|
13
|
+
platform?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SpotLimitOrderParams {
|
|
17
|
+
symbol: string;
|
|
18
|
+
side: "BUY" | "SELL";
|
|
19
|
+
quoteOrderAmount?: string; // Quote currency value (e.g., INR) - will be mapped to body based on side
|
|
20
|
+
amount?: string; // Base currency amount (e.g., BTC) - will be mapped to body based on side
|
|
21
|
+
price: string; // Limit price as string to preserve precision
|
|
22
|
+
clientOrderId?: string; // Must contain only letters, numbers, dots, colons, slashes, underscores, and hyphens, 1-36 characters
|
|
23
|
+
platform?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface GetOrdersParams {
|
|
27
|
+
symbol: string; // Trading pair symbol (required)
|
|
28
|
+
status?: "ACTIVE" | "FILLED" | "CANCELLED"; // Order status (default: ACTIVE)
|
|
29
|
+
currentPage?: number; // Current page number (default: 1)
|
|
30
|
+
pageSize?: number; // Number of records per page (default: 20)
|
|
31
|
+
startTime?: number; // Start time in milliseconds
|
|
32
|
+
endTime?: number; // End time in milliseconds
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface GetExchangeFeeParams {
|
|
36
|
+
symbol: string; // Trading pair symbol (required)
|
|
37
|
+
side: "BUY" | "SELL"; // Order side (required)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class SpotClient {
|
|
41
|
+
constructor(private readonly api: ZebpayAPI) {}
|
|
42
|
+
|
|
43
|
+
async placeMarketOrder(params: SpotMarketOrderParams): Promise<unknown> {
|
|
44
|
+
const path = "/ex/orders";
|
|
45
|
+
|
|
46
|
+
// Build body based on order side:
|
|
47
|
+
// BUY orders: use quoteOrderAmount (value in quote currency, e.g., INR)
|
|
48
|
+
// SELL orders: use amount (quantity in base currency, e.g., BTC)
|
|
49
|
+
const body: Record<string, unknown> = {
|
|
50
|
+
symbol: params.symbol,
|
|
51
|
+
side: params.side,
|
|
52
|
+
type: "MARKET",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Map to the appropriate field based on side
|
|
56
|
+
if (params.side === "BUY") {
|
|
57
|
+
// For BUY orders, use quoteOrderAmount in body
|
|
58
|
+
if (params.quoteOrderAmount) {
|
|
59
|
+
body.quoteOrderAmount = params.quoteOrderAmount;
|
|
60
|
+
} else if (params.amount) {
|
|
61
|
+
// If amount is provided for BUY, it should already be converted to quoteOrderAmount by the tool
|
|
62
|
+
body.quoteOrderAmount = params.amount;
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
// For SELL orders, use amount in body
|
|
66
|
+
if (params.amount) {
|
|
67
|
+
body.amount = params.amount;
|
|
68
|
+
} else if (params.quoteOrderAmount) {
|
|
69
|
+
// If quoteOrderAmount is provided for SELL, it should already be converted to amount by the tool
|
|
70
|
+
body.amount = params.quoteOrderAmount;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (params.clientOrderId) {
|
|
75
|
+
body.clientOrderId = params.clientOrderId;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (params.platform) {
|
|
79
|
+
body.platform = params.platform;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return this.api.request({
|
|
83
|
+
method: "POST",
|
|
84
|
+
path,
|
|
85
|
+
body,
|
|
86
|
+
useFutures: false,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async placeLimitOrder(params: SpotLimitOrderParams): Promise<unknown> {
|
|
91
|
+
const path = "/ex/orders";
|
|
92
|
+
|
|
93
|
+
// Build body based on order side:
|
|
94
|
+
// BUY orders: use quoteOrderAmount (value in quote currency, e.g., INR)
|
|
95
|
+
// SELL orders: use amount (quantity in base currency, e.g., BTC)
|
|
96
|
+
const body: Record<string, unknown> = {
|
|
97
|
+
symbol: params.symbol,
|
|
98
|
+
side: params.side,
|
|
99
|
+
type: "LIMIT",
|
|
100
|
+
price: params.price,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Map to the appropriate field based on side
|
|
104
|
+
if (params.side === "BUY") {
|
|
105
|
+
// For BUY orders, use quoteOrderAmount in body
|
|
106
|
+
if (params.quoteOrderAmount) {
|
|
107
|
+
body.quoteOrderAmount = params.quoteOrderAmount;
|
|
108
|
+
} else if (params.amount) {
|
|
109
|
+
// If amount is provided for BUY, it should already be converted to quoteOrderAmount by the tool
|
|
110
|
+
body.quoteOrderAmount = params.amount;
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
// For SELL orders, use amount in body
|
|
114
|
+
if (params.amount) {
|
|
115
|
+
body.amount = params.amount;
|
|
116
|
+
} else if (params.quoteOrderAmount) {
|
|
117
|
+
// If quoteOrderAmount is provided for SELL, it should already be converted to amount by the tool
|
|
118
|
+
body.amount = params.quoteOrderAmount;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (params.clientOrderId) {
|
|
123
|
+
body.clientOrderId = params.clientOrderId;
|
|
124
|
+
}
|
|
125
|
+
if (params.platform) {
|
|
126
|
+
body.platform = params.platform;
|
|
127
|
+
}
|
|
128
|
+
return this.api.request({
|
|
129
|
+
method: "POST",
|
|
130
|
+
path,
|
|
131
|
+
body,
|
|
132
|
+
useFutures: false,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async cancelOrdersBySymbol(symbol: string): Promise<unknown> {
|
|
137
|
+
const path = "/ex/orders";
|
|
138
|
+
return this.api.request({
|
|
139
|
+
method: "DELETE",
|
|
140
|
+
path,
|
|
141
|
+
useFutures: false,
|
|
142
|
+
queryParams: {
|
|
143
|
+
symbol: symbol.toUpperCase(),
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async cancelAllOrders(): Promise<unknown> {
|
|
149
|
+
const path = "/ex/orders/cancelAll";
|
|
150
|
+
return this.api.request({
|
|
151
|
+
method: "DELETE",
|
|
152
|
+
path,
|
|
153
|
+
useFutures: false,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async getOrderFills(orderId: string): Promise<unknown> {
|
|
158
|
+
const path = "/ex/order/fills/";
|
|
159
|
+
return this.api.request({
|
|
160
|
+
method: "GET",
|
|
161
|
+
path,
|
|
162
|
+
useFutures: false,
|
|
163
|
+
queryParams: {
|
|
164
|
+
orderId,
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async getOrderById(orderId: string): Promise<unknown> {
|
|
170
|
+
const path = "/ex/order";
|
|
171
|
+
return this.api.request({
|
|
172
|
+
method: "GET",
|
|
173
|
+
path,
|
|
174
|
+
useFutures: false,
|
|
175
|
+
queryParams: {
|
|
176
|
+
orderId,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async cancelOrderById(orderId: string): Promise<unknown> {
|
|
182
|
+
const path = "/ex/order";
|
|
183
|
+
return this.api.request({
|
|
184
|
+
method: "DELETE",
|
|
185
|
+
path,
|
|
186
|
+
useFutures: false,
|
|
187
|
+
queryParams: {
|
|
188
|
+
orderId,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async getBalance(currencies?: string): Promise<unknown> {
|
|
194
|
+
const path = "/account/balance";
|
|
195
|
+
const queryParams: Record<string, string> | undefined = currencies
|
|
196
|
+
? { currencies }
|
|
197
|
+
: undefined;
|
|
198
|
+
return this.api.request({
|
|
199
|
+
method: "GET",
|
|
200
|
+
path,
|
|
201
|
+
useFutures: false,
|
|
202
|
+
queryParams,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async getOrders(params: GetOrdersParams): Promise<unknown> {
|
|
207
|
+
const path = "/ex/orders";
|
|
208
|
+
const queryParams: Record<string, string> = {
|
|
209
|
+
symbol: params.symbol.toUpperCase(),
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
if (params.status) {
|
|
213
|
+
queryParams.status = params.status;
|
|
214
|
+
}
|
|
215
|
+
if (params.currentPage !== undefined) {
|
|
216
|
+
queryParams.currentPage = params.currentPage.toString();
|
|
217
|
+
}
|
|
218
|
+
if (params.pageSize !== undefined) {
|
|
219
|
+
queryParams.pageSize = params.pageSize.toString();
|
|
220
|
+
}
|
|
221
|
+
if (params.startTime !== undefined) {
|
|
222
|
+
queryParams.startTime = params.startTime.toString();
|
|
223
|
+
}
|
|
224
|
+
if (params.endTime !== undefined) {
|
|
225
|
+
queryParams.endTime = params.endTime.toString();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return this.api.request({
|
|
229
|
+
method: "GET",
|
|
230
|
+
path,
|
|
231
|
+
useFutures: false,
|
|
232
|
+
queryParams,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async getExchangeFee(params: GetExchangeFeeParams): Promise<unknown> {
|
|
237
|
+
const path = `/ex/tradefee`;
|
|
238
|
+
|
|
239
|
+
return this.api.request({
|
|
240
|
+
method: "GET",
|
|
241
|
+
path,
|
|
242
|
+
useFutures: false,
|
|
243
|
+
queryParams: {
|
|
244
|
+
symbol: params.symbol.toUpperCase(),
|
|
245
|
+
side: params.side,
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Core ZebpayAPI encapsulates signing, sending, and error normalization.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { AppConfig } from "../config.js";
|
|
6
|
+
import { CredentialsProvider } from "../security/credentials.js";
|
|
7
|
+
import { buildAuthHeaders, buildQueryStringAuthHeaders, signQueryString, signPayloadString, signRequest } from "../security/signing.js";
|
|
8
|
+
import { HttpClient, HttpError } from "../http/httpClient.js";
|
|
9
|
+
import { convertHttpErrorToMcpError, createInternalError } from "../mcp/errors.js";
|
|
10
|
+
import { McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
11
|
+
|
|
12
|
+
export interface RequestOptions {
|
|
13
|
+
method: string;
|
|
14
|
+
path: string; // path starting with '/'
|
|
15
|
+
body?: unknown;
|
|
16
|
+
useFutures?: boolean; // choose base URL
|
|
17
|
+
queryParams?: Record<string, string>; // optional query parameters
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class ZebpayAPI {
|
|
21
|
+
constructor(
|
|
22
|
+
private readonly config: AppConfig,
|
|
23
|
+
private readonly creds: CredentialsProvider,
|
|
24
|
+
private readonly http: HttpClient
|
|
25
|
+
) {}
|
|
26
|
+
|
|
27
|
+
private baseUrl(useFutures: boolean): string {
|
|
28
|
+
return useFutures ? this.config.futuresBaseUrl : this.config.spotBaseUrl;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private buildUrl(useFutures: boolean, path: string, queryParams?: Record<string, string>): string {
|
|
32
|
+
const trimmed = path.startsWith("/") ? path : `/${path}`;
|
|
33
|
+
let url = `${this.baseUrl(useFutures)}${trimmed}`;
|
|
34
|
+
|
|
35
|
+
if (queryParams && Object.keys(queryParams).length > 0) {
|
|
36
|
+
const queryString = Object.entries(queryParams)
|
|
37
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
|
38
|
+
.join("&");
|
|
39
|
+
url += `?${queryString}`;
|
|
40
|
+
|
|
41
|
+
// Debug logging
|
|
42
|
+
if (this.config.logLevel === "debug") {
|
|
43
|
+
console.error(JSON.stringify({
|
|
44
|
+
level: "debug",
|
|
45
|
+
msg: "buildUrl details",
|
|
46
|
+
queryParams,
|
|
47
|
+
queryString,
|
|
48
|
+
finalUrl: url
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return url;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async request<T = unknown>({ method, path, body, useFutures = false, queryParams: providedQueryParams }: RequestOptions): Promise<T> {
|
|
57
|
+
const apiKey = this.creds.getApiKey();
|
|
58
|
+
const secret = this.creds.getSecretKey();
|
|
59
|
+
|
|
60
|
+
const methodUpper = method.toUpperCase();
|
|
61
|
+
const isGetOrDelete = methodUpper === "GET" || methodUpper === "DELETE";
|
|
62
|
+
const isPost = methodUpper === "POST";
|
|
63
|
+
const includeBody = isPost && body !== undefined && body !== null;
|
|
64
|
+
|
|
65
|
+
let url: string;
|
|
66
|
+
let authHeaders: Record<string, string>;
|
|
67
|
+
let finalBody: unknown = body;
|
|
68
|
+
|
|
69
|
+
if (isGetOrDelete) {
|
|
70
|
+
// For GET and DELETE requests, use query-string based signing (Zebpay API format)
|
|
71
|
+
const timestamp = Date.now().toString();
|
|
72
|
+
const queryParams: Record<string, string> = {
|
|
73
|
+
timestamp,
|
|
74
|
+
...providedQueryParams, // Merge provided query params with timestamp
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Build query string for signing (sorted alphabetically for consistent signing)
|
|
78
|
+
// This same sorted order must be used in the URL to match the signature
|
|
79
|
+
const sortedEntries = Object.entries(queryParams)
|
|
80
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
81
|
+
const queryString = sortedEntries
|
|
82
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
|
83
|
+
.join("&");
|
|
84
|
+
|
|
85
|
+
// Sign the query string
|
|
86
|
+
const signature = signQueryString(queryString, secret);
|
|
87
|
+
|
|
88
|
+
// Build auth headers (only API key and signature, no timestamp header)
|
|
89
|
+
authHeaders = buildQueryStringAuthHeaders(apiKey, signature, this.config.signingHeaders);
|
|
90
|
+
|
|
91
|
+
// Build URL with the same sorted query string to match the signature
|
|
92
|
+
const trimmed = path.startsWith("/") ? path : `/${path}`;
|
|
93
|
+
url = `${this.baseUrl(useFutures)}${trimmed}?${queryString}`;
|
|
94
|
+
|
|
95
|
+
// Debug logging
|
|
96
|
+
if (this.config.logLevel === "debug") {
|
|
97
|
+
console.error(JSON.stringify({
|
|
98
|
+
level: "debug",
|
|
99
|
+
msg: `${methodUpper} request details`,
|
|
100
|
+
path,
|
|
101
|
+
queryString,
|
|
102
|
+
timestamp,
|
|
103
|
+
url,
|
|
104
|
+
hasQueryParams: url.includes("?")
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
} else if (isPost) {
|
|
108
|
+
// For POST requests, sign the JSON body directly (Zebpay API format)
|
|
109
|
+
const timestamp = Date.now();
|
|
110
|
+
|
|
111
|
+
// Prepare payload with timestamp added
|
|
112
|
+
const payload = body ? { ...(body as Record<string, unknown>), timestamp } : { timestamp };
|
|
113
|
+
|
|
114
|
+
// Stringify the payload (deterministic JSON stringify)
|
|
115
|
+
const payloadString = JSON.stringify(payload);
|
|
116
|
+
|
|
117
|
+
// Sign the payload string directly
|
|
118
|
+
const signature = signPayloadString(payloadString, secret);
|
|
119
|
+
|
|
120
|
+
// Build auth headers (only API key and signature, no timestamp header)
|
|
121
|
+
authHeaders = buildQueryStringAuthHeaders(apiKey, signature, this.config.signingHeaders);
|
|
122
|
+
|
|
123
|
+
// Set the final body with timestamp included
|
|
124
|
+
finalBody = payload;
|
|
125
|
+
url = this.buildUrl(useFutures, path);
|
|
126
|
+
|
|
127
|
+
// Debug logging
|
|
128
|
+
if (this.config.logLevel === "debug") {
|
|
129
|
+
console.error(JSON.stringify({
|
|
130
|
+
level: "debug",
|
|
131
|
+
msg: "POST request details",
|
|
132
|
+
path,
|
|
133
|
+
payloadString,
|
|
134
|
+
timestamp,
|
|
135
|
+
signature
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
// For other methods (PUT, PATCH, etc.), fall back to original signing method
|
|
140
|
+
const signature = signRequest(method, path, includeBody ? body : undefined, secret, {
|
|
141
|
+
headers: this.config.signingHeaders,
|
|
142
|
+
includeBody,
|
|
143
|
+
});
|
|
144
|
+
authHeaders = buildAuthHeaders(apiKey, signature, this.config.signingHeaders);
|
|
145
|
+
url = this.buildUrl(useFutures, path);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const res = await this.http.request<T>({
|
|
150
|
+
method,
|
|
151
|
+
url,
|
|
152
|
+
headers: authHeaders,
|
|
153
|
+
body: isPost ? finalBody : (isGetOrDelete ? undefined : body),
|
|
154
|
+
timeoutMs: this.config.timeoutMs,
|
|
155
|
+
retryCount: this.config.retryCount,
|
|
156
|
+
});
|
|
157
|
+
return res.data as T;
|
|
158
|
+
} catch (err) {
|
|
159
|
+
if (err instanceof HttpError) {
|
|
160
|
+
// Extract error message from various possible response formats
|
|
161
|
+
let errorMessage = err.message || "Unknown API error";
|
|
162
|
+
|
|
163
|
+
// Extract error message and preserve full details for better error handling
|
|
164
|
+
let errorDetails = err.details;
|
|
165
|
+
|
|
166
|
+
if (err.details) {
|
|
167
|
+
if (typeof err.details === "string") {
|
|
168
|
+
errorMessage = err.details;
|
|
169
|
+
} else if (typeof err.details === "object") {
|
|
170
|
+
// Check common error response fields (in order of preference)
|
|
171
|
+
const details = err.details as Record<string, unknown>;
|
|
172
|
+
|
|
173
|
+
// Extract statusDescription first (most specific)
|
|
174
|
+
if (typeof details.statusDescription === "string" && details.statusDescription) {
|
|
175
|
+
errorMessage = details.statusDescription;
|
|
176
|
+
} else if (typeof details.error === "string" && details.error) {
|
|
177
|
+
errorMessage = details.error;
|
|
178
|
+
} else if (typeof details.message === "string" && details.message) {
|
|
179
|
+
errorMessage = details.message;
|
|
180
|
+
} else if (typeof details.msg === "string" && details.msg) {
|
|
181
|
+
errorMessage = details.msg;
|
|
182
|
+
} else if (typeof details.errorMessage === "string" && details.errorMessage) {
|
|
183
|
+
errorMessage = details.errorMessage;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Preserve the full details object for error conversion
|
|
187
|
+
errorDetails = details;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
throw convertHttpErrorToMcpError(err.status, errorMessage, errorDetails, err.isHtmlResponse);
|
|
192
|
+
}
|
|
193
|
+
// Re-throw MCP errors as-is
|
|
194
|
+
if (err instanceof McpError) {
|
|
195
|
+
throw err;
|
|
196
|
+
}
|
|
197
|
+
// Wrap other errors as internal errors
|
|
198
|
+
throw createInternalError(
|
|
199
|
+
err instanceof Error ? err.message : String(err),
|
|
200
|
+
{ originalError: String(err) }
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|