You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1098 lines
31 KiB
1098 lines
31 KiB
"use strict"; |
|
var __defProp = Object.defineProperty; |
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor; |
|
var __getOwnPropNames = Object.getOwnPropertyNames; |
|
var __hasOwnProp = Object.prototype.hasOwnProperty; |
|
var __export = (target, all) => { |
|
for (var name in all) |
|
__defProp(target, name, { get: all[name], enumerable: true }); |
|
}; |
|
var __copyProps = (to, from, except, desc) => { |
|
if (from && typeof from === "object" || typeof from === "function") { |
|
for (let key of __getOwnPropNames(from)) |
|
if (!__hasOwnProp.call(to, key) && key !== except) |
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); |
|
} |
|
return to; |
|
}; |
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); |
|
|
|
// src/index.ts |
|
var index_exports = {}; |
|
__export(index_exports, { |
|
AuthenticationError: () => AuthenticationError, |
|
InsufficientFundsError: () => InsufficientFundsError, |
|
InvoicesEndpoint: () => InvoicesEndpoint, |
|
KlingEx: () => KlingEx, |
|
KlingExError: () => KlingExError, |
|
KlingExWebSocket: () => KlingExWebSocket, |
|
MarketsEndpoint: () => MarketsEndpoint, |
|
OrdersEndpoint: () => OrdersEndpoint, |
|
RateLimitError: () => RateLimitError, |
|
ValidationError: () => ValidationError, |
|
WalletEndpoint: () => WalletEndpoint, |
|
default: () => KlingEx |
|
}); |
|
module.exports = __toCommonJS(index_exports); |
|
|
|
// src/types/index.ts |
|
var KlingExError = class extends Error { |
|
constructor(message, code, statusCode, details) { |
|
super(message); |
|
this.code = code; |
|
this.statusCode = statusCode; |
|
this.details = details; |
|
this.name = "KlingExError"; |
|
} |
|
}; |
|
var AuthenticationError = class extends KlingExError { |
|
constructor(message = "Authentication failed") { |
|
super(message, "AUTH_ERROR", 401); |
|
this.name = "AuthenticationError"; |
|
} |
|
}; |
|
var RateLimitError = class extends KlingExError { |
|
constructor(message = "Rate limit exceeded", retryAfter) { |
|
super(message, "RATE_LIMIT", 429); |
|
this.retryAfter = retryAfter; |
|
this.name = "RateLimitError"; |
|
} |
|
}; |
|
var ValidationError = class extends KlingExError { |
|
constructor(message, details) { |
|
super(message, "VALIDATION_ERROR", 400, details); |
|
this.name = "ValidationError"; |
|
} |
|
}; |
|
var InsufficientFundsError = class extends KlingExError { |
|
constructor(message = "Insufficient funds") { |
|
super(message, "INSUFFICIENT_FUNDS", 400); |
|
this.name = "InsufficientFundsError"; |
|
} |
|
}; |
|
|
|
// src/http.ts |
|
var HttpClient = class { |
|
constructor(config) { |
|
this.config = config; |
|
} |
|
/** |
|
* Update authentication credentials |
|
*/ |
|
setAuth(auth) { |
|
if (auth.apiKey) this.config.apiKey = auth.apiKey; |
|
if (auth.jwt) this.config.jwt = auth.jwt; |
|
} |
|
/** |
|
* Make an HTTP request to the API |
|
*/ |
|
async request(endpoint, options = {}) { |
|
const { method = "GET", body, params, headers = {} } = options; |
|
let url = `${this.config.baseUrl}${endpoint}`; |
|
if (params) { |
|
const searchParams = new URLSearchParams(); |
|
for (const [key, value] of Object.entries(params)) { |
|
if (value !== void 0 && value !== null) { |
|
searchParams.append(key, String(value)); |
|
} |
|
} |
|
const queryString = searchParams.toString(); |
|
if (queryString) { |
|
url += `?${queryString}`; |
|
} |
|
} |
|
const requestHeaders = { |
|
"Content-Type": "application/json", |
|
...headers |
|
}; |
|
if (this.config.apiKey) { |
|
requestHeaders["X-API-Key"] = this.config.apiKey; |
|
} else if (this.config.jwt) { |
|
requestHeaders["Authorization"] = `Bearer ${this.config.jwt}`; |
|
} |
|
const controller = new AbortController(); |
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout); |
|
try { |
|
const response = await fetch(url, { |
|
method, |
|
headers: requestHeaders, |
|
body: body ? JSON.stringify(body) : void 0, |
|
signal: controller.signal |
|
}); |
|
clearTimeout(timeoutId); |
|
const contentType = response.headers.get("content-type"); |
|
let data; |
|
if (contentType?.includes("application/json")) { |
|
data = await response.json(); |
|
} else if (contentType?.includes("application/pdf")) { |
|
data = await response.blob(); |
|
} else { |
|
data = await response.text(); |
|
} |
|
if (!response.ok) { |
|
this.handleError(response.status, data); |
|
} |
|
return data; |
|
} catch (error) { |
|
clearTimeout(timeoutId); |
|
if (error instanceof KlingExError) { |
|
throw error; |
|
} |
|
if (error instanceof Error) { |
|
if (error.name === "AbortError") { |
|
throw new KlingExError("Request timeout", "TIMEOUT", 408); |
|
} |
|
throw new KlingExError(error.message, "NETWORK_ERROR"); |
|
} |
|
throw new KlingExError("Unknown error occurred", "UNKNOWN"); |
|
} |
|
} |
|
/** |
|
* Handle HTTP error responses |
|
*/ |
|
handleError(status, data) { |
|
const errorMessage = this.extractErrorMessage(data); |
|
switch (status) { |
|
case 401: |
|
throw new AuthenticationError(errorMessage); |
|
case 429: |
|
const retryAfter = this.extractRetryAfter(data); |
|
throw new RateLimitError(errorMessage, retryAfter); |
|
case 400: |
|
if (errorMessage.toLowerCase().includes("insufficient")) { |
|
throw new InsufficientFundsError(errorMessage); |
|
} |
|
throw new ValidationError(errorMessage, data); |
|
default: |
|
throw new KlingExError(errorMessage, "API_ERROR", status, data); |
|
} |
|
} |
|
/** |
|
* Extract error message from response |
|
*/ |
|
extractErrorMessage(data) { |
|
if (typeof data === "string") return data; |
|
if (typeof data === "object" && data !== null) { |
|
const obj = data; |
|
return String(obj.error || obj.message || obj.detail || "Unknown error"); |
|
} |
|
return "Unknown error"; |
|
} |
|
/** |
|
* Extract retry-after value from rate limit response |
|
*/ |
|
extractRetryAfter(data) { |
|
if (typeof data === "object" && data !== null) { |
|
const obj = data; |
|
if (typeof obj.retry_after === "number") { |
|
return obj.retry_after; |
|
} |
|
} |
|
return void 0; |
|
} |
|
// Convenience methods |
|
async get(endpoint, params) { |
|
return this.request(endpoint, { method: "GET", params }); |
|
} |
|
async post(endpoint, body) { |
|
return this.request(endpoint, { method: "POST", body }); |
|
} |
|
async put(endpoint, body) { |
|
return this.request(endpoint, { method: "PUT", body }); |
|
} |
|
async delete(endpoint, body) { |
|
return this.request(endpoint, { method: "DELETE", body }); |
|
} |
|
}; |
|
|
|
// src/websocket.ts |
|
var KlingExWebSocket = class { |
|
constructor(url, auth, options = {}) { |
|
this.ws = null; |
|
this.subscriptions = /* @__PURE__ */ new Map(); |
|
this.reconnectAttempts = 0; |
|
this.reconnectTimeout = null; |
|
this.pingInterval = null; |
|
this.isConnecting = false; |
|
this.url = url; |
|
this.apiKey = auth.apiKey; |
|
this.jwt = auth.jwt; |
|
this.options = { |
|
reconnect: options.reconnect ?? true, |
|
reconnectInterval: options.reconnectInterval ?? 5e3, |
|
maxReconnectAttempts: options.maxReconnectAttempts ?? 10 |
|
}; |
|
} |
|
/** |
|
* Connect to WebSocket server |
|
*/ |
|
async connect() { |
|
if (this.ws?.readyState === WebSocket.OPEN || this.isConnecting) { |
|
return; |
|
} |
|
this.isConnecting = true; |
|
return new Promise((resolve, reject) => { |
|
try { |
|
const wsUrl = new URL(this.url); |
|
if (this.apiKey) { |
|
wsUrl.searchParams.set("apiKey", this.apiKey); |
|
} else if (this.jwt) { |
|
wsUrl.searchParams.set("token", this.jwt); |
|
} |
|
this.ws = new WebSocket(wsUrl.toString()); |
|
this.ws.onopen = () => { |
|
this.isConnecting = false; |
|
this.reconnectAttempts = 0; |
|
this.startPingInterval(); |
|
this.resubscribeAll(); |
|
resolve(); |
|
}; |
|
this.ws.onclose = (event) => { |
|
this.isConnecting = false; |
|
this.stopPingInterval(); |
|
if (this.options.reconnect && !event.wasClean) { |
|
this.scheduleReconnect(); |
|
} |
|
}; |
|
this.ws.onerror = (_event) => { |
|
this.isConnecting = false; |
|
const err = new Error("WebSocket error"); |
|
this.errorHandler?.(err); |
|
reject(err); |
|
}; |
|
this.ws.onmessage = (event) => { |
|
this.handleMessage(event.data); |
|
}; |
|
} catch (error) { |
|
this.isConnecting = false; |
|
reject(error); |
|
} |
|
}); |
|
} |
|
/** |
|
* Disconnect from WebSocket server |
|
*/ |
|
disconnect() { |
|
this.options.reconnect = false; |
|
this.stopPingInterval(); |
|
if (this.reconnectTimeout) { |
|
clearTimeout(this.reconnectTimeout); |
|
this.reconnectTimeout = null; |
|
} |
|
if (this.ws) { |
|
this.ws.close(1e3, "Client disconnect"); |
|
this.ws = null; |
|
} |
|
this.subscriptions.clear(); |
|
} |
|
/** |
|
* Set error handler |
|
*/ |
|
onError(handler) { |
|
this.errorHandler = handler; |
|
} |
|
/** |
|
* Subscribe to orderbook updates |
|
* @param symbol - Trading pair symbol |
|
* @param handler - Callback for orderbook updates |
|
*/ |
|
orderbook(symbol, handler) { |
|
return this.subscribe("orderbook", symbol, handler); |
|
} |
|
/** |
|
* Subscribe to trade updates |
|
* @param symbol - Trading pair symbol |
|
* @param handler - Callback for trade updates |
|
*/ |
|
trades(symbol, handler) { |
|
return this.subscribe("trades", symbol, handler); |
|
} |
|
/** |
|
* Subscribe to ticker updates |
|
* @param symbol - Trading pair symbol |
|
* @param handler - Callback for ticker updates |
|
*/ |
|
ticker(symbol, handler) { |
|
return this.subscribe("ticker", symbol, handler); |
|
} |
|
/** |
|
* Subscribe to user order updates (requires auth) |
|
* @param handler - Callback for order updates |
|
*/ |
|
userOrders(handler) { |
|
return this.subscribe("user.orders", void 0, handler); |
|
} |
|
/** |
|
* Subscribe to user balance updates (requires auth) |
|
* @param handler - Callback for balance updates |
|
*/ |
|
userBalances(handler) { |
|
return this.subscribe("user.balances", void 0, handler); |
|
} |
|
// ========================================================================= |
|
// Private methods |
|
// ========================================================================= |
|
subscribe(channel, symbol, handler) { |
|
const key = symbol ? `${channel}:${symbol}` : channel; |
|
this.subscriptions.set(key, { |
|
channel, |
|
symbol, |
|
handler |
|
}); |
|
if (this.ws?.readyState === WebSocket.OPEN) { |
|
this.sendSubscribe(channel, symbol); |
|
} |
|
return () => { |
|
this.subscriptions.delete(key); |
|
if (this.ws?.readyState === WebSocket.OPEN) { |
|
this.sendUnsubscribe(channel, symbol); |
|
} |
|
}; |
|
} |
|
sendSubscribe(channel, symbol) { |
|
this.send({ |
|
action: "subscribe", |
|
channel, |
|
symbol |
|
}); |
|
} |
|
sendUnsubscribe(channel, symbol) { |
|
this.send({ |
|
action: "unsubscribe", |
|
channel, |
|
symbol |
|
}); |
|
} |
|
send(data) { |
|
if (this.ws?.readyState === WebSocket.OPEN) { |
|
this.ws.send(JSON.stringify(data)); |
|
} |
|
} |
|
handleMessage(data) { |
|
try { |
|
const message = JSON.parse(data); |
|
if (message.type === "pong") { |
|
return; |
|
} |
|
const key = message.data && typeof message.data === "object" && "symbol" in message.data ? `${message.channel}:${message.data.symbol}` : message.channel; |
|
const subscription = this.subscriptions.get(key) || this.subscriptions.get(message.channel); |
|
if (subscription) { |
|
subscription.handler(message.data); |
|
} |
|
} catch (error) { |
|
this.errorHandler?.(error instanceof Error ? error : new Error("Failed to parse message")); |
|
} |
|
} |
|
resubscribeAll() { |
|
for (const [, subscription] of this.subscriptions) { |
|
this.sendSubscribe(subscription.channel, subscription.symbol); |
|
} |
|
} |
|
scheduleReconnect() { |
|
if (this.reconnectAttempts >= this.options.maxReconnectAttempts) { |
|
this.errorHandler?.(new Error("Max reconnection attempts reached")); |
|
return; |
|
} |
|
this.reconnectAttempts++; |
|
const delay = this.options.reconnectInterval * Math.pow(1.5, this.reconnectAttempts - 1); |
|
this.reconnectTimeout = setTimeout(() => { |
|
this.connect().catch((error) => { |
|
this.errorHandler?.(error instanceof Error ? error : new Error("Reconnection failed")); |
|
}); |
|
}, delay); |
|
} |
|
startPingInterval() { |
|
this.pingInterval = setInterval(() => { |
|
this.send({ type: "ping" }); |
|
}, 3e4); |
|
} |
|
stopPingInterval() { |
|
if (this.pingInterval) { |
|
clearInterval(this.pingInterval); |
|
this.pingInterval = null; |
|
} |
|
} |
|
}; |
|
|
|
// src/endpoints/markets.ts |
|
var MarketsEndpoint = class { |
|
constructor(http) { |
|
this.http = http; |
|
} |
|
/** |
|
* Get all available markets/trading pairs |
|
* @example |
|
* const markets = await client.markets.list(); |
|
* // Returns full market info including: |
|
* // - base_asset_symbol, quote_asset_symbol |
|
* // - maker_fee_rate, taker_fee_rate |
|
* // - last_price, volume_24h, priceChange24h |
|
*/ |
|
async list() { |
|
return this.http.get("/api/markets"); |
|
} |
|
/** |
|
* Get a specific market by ID |
|
* @param marketId - The market/trading pair ID |
|
*/ |
|
async get(marketId) { |
|
const markets = await this.list(); |
|
return markets.find((m) => m.id === marketId); |
|
} |
|
/** |
|
* Find a market by symbol pair |
|
* @param baseSymbol - Base asset symbol (e.g., "BTC") |
|
* @param quoteSymbol - Quote asset symbol (e.g., "USDT") |
|
*/ |
|
async findBySymbols(baseSymbol, quoteSymbol) { |
|
const markets = await this.list(); |
|
return markets.find( |
|
(m) => m.base_asset_symbol.toUpperCase() === baseSymbol.toUpperCase() && m.quote_asset_symbol.toUpperCase() === quoteSymbol.toUpperCase() |
|
); |
|
} |
|
/** |
|
* Get all tickers with 24h price data (CMC format) |
|
* @example |
|
* const tickers = await client.markets.tickers(); |
|
* // Returns ticker_id as "BTC_USDT" format |
|
*/ |
|
async tickers() { |
|
return this.http.get("/api/tickers"); |
|
} |
|
/** |
|
* Get ticker for a specific symbol pair |
|
* @param tickerId - Ticker ID in CMC format (e.g., "BTC_USDT") |
|
*/ |
|
async ticker(tickerId) { |
|
const tickers = await this.tickers(); |
|
return tickers.find((t) => t.ticker_id === tickerId); |
|
} |
|
/** |
|
* Get ticker by base and quote currency |
|
* @param baseCurrency - Base currency symbol (e.g., "BTC") |
|
* @param targetCurrency - Target/quote currency symbol (e.g., "USDT") |
|
*/ |
|
async tickerByPair(baseCurrency, targetCurrency) { |
|
const tickers = await this.tickers(); |
|
return tickers.find( |
|
(t) => t.base_currency.toUpperCase() === baseCurrency.toUpperCase() && t.target_currency.toUpperCase() === targetCurrency.toUpperCase() |
|
); |
|
} |
|
/** |
|
* Get orderbook for a trading pair |
|
* @param marketId - The market/trading pair ID |
|
* @param options - Options for the orderbook request |
|
* @example |
|
* const orderbook = await client.markets.orderbook(1); |
|
* console.log('Best bid:', orderbook.bids[0]); |
|
* console.log('Best ask:', orderbook.asks[0]); |
|
*/ |
|
async orderbook(marketId, options = {}) { |
|
const response = await this.http.get("/api/orderbook", { |
|
marketId, |
|
isCmc: options.isCmc ?? false |
|
}); |
|
return { |
|
trading_pair_id: response.trading_pair_id, |
|
base_symbol: response.base_symbol, |
|
quote_symbol: response.quote_symbol, |
|
bids: (response.bids || []).map(([price, quantity]) => ({ price, quantity })), |
|
asks: (response.asks || []).map(([price, quantity]) => ({ price, quantity })) |
|
}; |
|
} |
|
/** |
|
* Get raw orderbook (original API format with arrays) |
|
* @param marketId - The market/trading pair ID |
|
*/ |
|
async orderbookRaw(marketId, options = {}) { |
|
return this.http.get("/api/orderbook", { |
|
marketId, |
|
isCmc: options.isCmc ?? false |
|
}); |
|
} |
|
/** |
|
* Get OHLCV (candlestick) data |
|
* @param marketId - Trading pair ID |
|
* @param timeframe - Candle timeframe (e.g., "1h", "1d") |
|
* @param options - Additional options |
|
* @example |
|
* const candles = await client.markets.ohlcv(1, '1h'); |
|
*/ |
|
async ohlcv(marketId, timeframe, options = {}) { |
|
return this.http.get("/api/ohlcv", { |
|
marketId, |
|
timeframe, |
|
limit: options.limit, |
|
startDate: options.startDate, |
|
endDate: options.endDate |
|
}); |
|
} |
|
/** |
|
* Get all available assets |
|
* @example |
|
* const assets = await client.markets.assets(); |
|
*/ |
|
async assets() { |
|
const response = await this.http.get("/api/assets"); |
|
return response.assets || []; |
|
} |
|
/** |
|
* Get a specific asset by symbol |
|
* @param symbol - Asset symbol (e.g., "BTC") |
|
*/ |
|
async asset(symbol) { |
|
const assets = await this.assets(); |
|
return assets.find((a) => a.symbol.toUpperCase() === symbol.toUpperCase()); |
|
} |
|
/** |
|
* Get recent trades for a trading pair |
|
* @param marketId - Trading pair ID |
|
* @param limit - Number of trades to return |
|
*/ |
|
async trades(marketId, limit = 50) { |
|
return this.http.get("/api/trades", { marketId, limit }); |
|
} |
|
}; |
|
|
|
// src/endpoints/orders.ts |
|
var OrdersEndpoint = class { |
|
constructor(http, humanReadableDefault = true) { |
|
this.http = http; |
|
this.humanReadableDefault = humanReadableDefault; |
|
} |
|
/** |
|
* Submit a new order |
|
* @param params - Order parameters |
|
* @example |
|
* // Buy 1.5 BTC at $50,000 (human-readable values) |
|
* const order = await client.orders.submit({ |
|
* symbol: 'BTC-USDT', |
|
* tradingPairId: 1, |
|
* side: 'BUY', |
|
* quantity: '1.5', |
|
* price: '50000.00' |
|
* }); |
|
* |
|
* @example |
|
* // Market order (price = 0) |
|
* const order = await client.orders.submit({ |
|
* symbol: 'BTC-USDT', |
|
* tradingPairId: 1, |
|
* side: 'BUY', |
|
* quantity: '1.0', |
|
* price: '0', |
|
* slippage: 0.01 // 1% slippage tolerance |
|
* }); |
|
*/ |
|
async submit(params) { |
|
const rawValues = params.rawValues ?? !this.humanReadableDefault; |
|
return this.http.post("/api/submit-order", { |
|
symbol: params.symbol, |
|
tradingPairId: params.tradingPairId, |
|
side: params.side.toUpperCase(), |
|
quantity: params.quantity, |
|
price: params.price, |
|
rawValues, |
|
slippage: params.slippage |
|
}); |
|
} |
|
/** |
|
* Cancel an existing order |
|
* @param params - Order ID and trading pair ID |
|
* @example |
|
* const result = await client.orders.cancel({ |
|
* orderId: '7c9e6679-7425-40de-944b-e07fc1f90ae7', |
|
* tradingPairId: 1 |
|
* }); |
|
* console.log(`Released balance: ${result.released_balance}`); |
|
*/ |
|
async cancel(params) { |
|
return this.http.post("/api/cancel-order", { |
|
orderId: params.orderId, |
|
tradingPairId: params.tradingPairId |
|
}); |
|
} |
|
/** |
|
* Cancel all open orders for a trading pair |
|
* @param tradingPairId - Trading pair ID |
|
* @example |
|
* const result = await client.orders.cancelAll(1); |
|
* console.log(`Cancelled ${result.cancelledCount} orders`); |
|
*/ |
|
async cancelAll(tradingPairId) { |
|
return this.http.post("/api/cancel-all-orders", { tradingPairId }); |
|
} |
|
/** |
|
* Get your open orders |
|
* @param params - Filter parameters |
|
* @example |
|
* // Get all open orders |
|
* const orders = await client.orders.list(); |
|
* |
|
* // Get orders for specific trading pair |
|
* const orders = await client.orders.list({ tradingPairId: 1 }); |
|
*/ |
|
async list(params = {}) { |
|
const response = await this.http.get("/api/user-orders", { |
|
tradingPairId: params.tradingPairId, |
|
status: params.status, |
|
limit: params.limit || 50 |
|
}); |
|
return response.orders || []; |
|
} |
|
/** |
|
* Get order history (including filled/cancelled orders) |
|
* @param params - Filter and pagination parameters |
|
*/ |
|
async history(params = {}) { |
|
return this.http.get("/api/orders-history", { |
|
tradingPairId: params.tradingPairId, |
|
status: params.status, |
|
limit: params.limit || 50, |
|
offset: params.offset || 0 |
|
}); |
|
} |
|
/** |
|
* Get a specific order by ID |
|
* @param orderId - Order UUID |
|
*/ |
|
async get(orderId) { |
|
const orders = await this.list(); |
|
return orders.find((o) => o.id === orderId); |
|
} |
|
// ========================================================================= |
|
// Convenience methods for common order types |
|
// ========================================================================= |
|
/** |
|
* Place a limit buy order (human-readable values) |
|
* @param symbol - Trading pair symbol (e.g., "BTC-USDT") |
|
* @param tradingPairId - Trading pair ID |
|
* @param quantity - Amount to buy |
|
* @param price - Price per unit |
|
*/ |
|
async limitBuy(symbol, tradingPairId, quantity, price) { |
|
return this.submit({ |
|
symbol, |
|
tradingPairId, |
|
side: "BUY", |
|
quantity, |
|
price |
|
}); |
|
} |
|
/** |
|
* Place a limit sell order (human-readable values) |
|
* @param symbol - Trading pair symbol (e.g., "BTC-USDT") |
|
* @param tradingPairId - Trading pair ID |
|
* @param quantity - Amount to sell |
|
* @param price - Price per unit |
|
*/ |
|
async limitSell(symbol, tradingPairId, quantity, price) { |
|
return this.submit({ |
|
symbol, |
|
tradingPairId, |
|
side: "SELL", |
|
quantity, |
|
price |
|
}); |
|
} |
|
/** |
|
* Place a market buy order |
|
* @param symbol - Trading pair symbol (e.g., "BTC-USDT") |
|
* @param tradingPairId - Trading pair ID |
|
* @param quantity - Amount to buy |
|
* @param slippage - Slippage tolerance (0-1, e.g., 0.01 for 1%) |
|
*/ |
|
async marketBuy(symbol, tradingPairId, quantity, slippage = 0.01) { |
|
return this.submit({ |
|
symbol, |
|
tradingPairId, |
|
side: "BUY", |
|
quantity, |
|
price: "0", |
|
slippage |
|
}); |
|
} |
|
/** |
|
* Place a market sell order |
|
* @param symbol - Trading pair symbol (e.g., "BTC-USDT") |
|
* @param tradingPairId - Trading pair ID |
|
* @param quantity - Amount to sell |
|
* @param slippage - Slippage tolerance (0-1, e.g., 0.01 for 1%) |
|
*/ |
|
async marketSell(symbol, tradingPairId, quantity, slippage = 0.01) { |
|
return this.submit({ |
|
symbol, |
|
tradingPairId, |
|
side: "SELL", |
|
quantity, |
|
price: "0", |
|
slippage |
|
}); |
|
} |
|
}; |
|
|
|
// src/endpoints/wallet.ts |
|
var WalletEndpoint = class { |
|
constructor(http) { |
|
this.http = http; |
|
} |
|
/** |
|
* Get all wallet balances |
|
* Each balance includes: |
|
* - balance: Total balance (raw value) |
|
* - locked_balance: Locked in orders (raw value) |
|
* - wallet_id: UUID of the wallet |
|
* - deposit_address: Deposit address for this asset |
|
* - id, symbol, name, decimals: Asset info |
|
* - min_deposit, min_withdrawal, withdrawal_fee: Limits |
|
* |
|
* @example |
|
* const balances = await client.wallet.balances(); |
|
* const ethBalance = balances.find(b => b.symbol === 'ETH'); |
|
* console.log(`ETH Balance: ${ethBalance?.balance}`); |
|
* console.log(`Deposit Address: ${ethBalance?.deposit_address}`); |
|
*/ |
|
async balances() { |
|
const balances = await this.http.get("/api/user-balances"); |
|
return balances.map((b) => { |
|
const balance = BigInt(b.balance); |
|
const locked = BigInt(b.locked_balance); |
|
const available = balance - locked; |
|
const decimals = b.decimals; |
|
return { |
|
...b, |
|
available_balance: available.toString(), |
|
human_balance: this.formatUnits(b.balance, decimals), |
|
human_locked: this.formatUnits(b.locked_balance, decimals), |
|
human_available: this.formatUnits(available.toString(), decimals) |
|
}; |
|
}); |
|
} |
|
/** |
|
* Get balance for a specific asset |
|
* @param symbol - Asset symbol (e.g., "BTC", "ETH", "USDT") |
|
*/ |
|
async balance(symbol) { |
|
const balances = await this.balances(); |
|
return balances.find((b) => b.symbol.toUpperCase() === symbol.toUpperCase()); |
|
} |
|
/** |
|
* Get deposit address for an asset (from balance data) |
|
* The deposit_address is included in the balance response |
|
* @param symbol - Asset symbol (e.g., "BTC") |
|
* @example |
|
* const address = await client.wallet.depositAddress('ETH'); |
|
* console.log(`Send ETH to: ${address.address}`); |
|
*/ |
|
async depositAddress(symbol) { |
|
const balances = await this.http.get("/api/user-balances"); |
|
const balance = balances.find((b) => b.symbol.toUpperCase() === symbol.toUpperCase()); |
|
if (!balance) { |
|
return void 0; |
|
} |
|
return { |
|
address: balance.deposit_address |
|
}; |
|
} |
|
/** |
|
* Generate a new deposit address (if supported) |
|
* @param assetId - Asset ID |
|
*/ |
|
async generateDepositAddress(assetId) { |
|
const response = await this.http.post("/api/generate-deposit-address", { |
|
assetId |
|
}); |
|
return { |
|
address: response.address, |
|
memo: response.memo |
|
}; |
|
} |
|
/** |
|
* Submit a withdrawal request |
|
* @param params - Withdrawal parameters |
|
* @example |
|
* const result = await client.wallet.withdraw({ |
|
* assetId: 2, |
|
* symbol: 'ETH', |
|
* address: '0x...', |
|
* amount: '0.1' |
|
* }); |
|
* |
|
* if (result.requires_2fa) { |
|
* // Need to complete 2FA verification |
|
* await client.wallet.confirm2FA(result.session_token, '123456'); |
|
* } |
|
*/ |
|
async withdraw(params) { |
|
return this.http.post("/api/submit-withdraw", { |
|
assetId: params.assetId, |
|
symbol: params.symbol, |
|
address: params.address, |
|
amount: params.amount, |
|
destinationTag: params.memo |
|
}); |
|
} |
|
/** |
|
* Complete withdrawal 2FA verification |
|
* @param sessionToken - Session token from withdraw response |
|
* @param code - 2FA code from authenticator app |
|
*/ |
|
async confirm2FA(sessionToken, code) { |
|
return this.http.post("/api/withdrawal/complete-2fa", { |
|
sessionToken, |
|
code |
|
}); |
|
} |
|
/** |
|
* Get deposit history |
|
* @param options - Pagination options |
|
*/ |
|
async deposits(options = {}) { |
|
return this.http.get("/api/deposits", { |
|
limit: options.limit || 50, |
|
offset: options.offset || 0 |
|
}); |
|
} |
|
/** |
|
* Get withdrawal history |
|
* @param options - Pagination options |
|
*/ |
|
async withdrawals(options = {}) { |
|
return this.http.get("/api/withdrawals", { |
|
limit: options.limit || 50, |
|
offset: options.offset || 0 |
|
}); |
|
} |
|
/** |
|
* Get transaction history (deposits + withdrawals + trades) |
|
*/ |
|
async history() { |
|
return this.http.get("/api/history"); |
|
} |
|
/** |
|
* Format raw units to human-readable decimal string |
|
* @param value - Raw value as string |
|
* @param decimals - Number of decimals |
|
*/ |
|
formatUnits(value, decimals) { |
|
if (decimals === 0) return value; |
|
const str = value.padStart(decimals + 1, "0"); |
|
const intPart = str.slice(0, -decimals) || "0"; |
|
const decPart = str.slice(-decimals); |
|
const trimmedDec = decPart.replace(/0+$/, ""); |
|
return trimmedDec ? `${intPart}.${trimmedDec}` : intPart; |
|
} |
|
}; |
|
|
|
// src/endpoints/invoices.ts |
|
var InvoicesEndpoint = class { |
|
constructor(http) { |
|
this.http = http; |
|
} |
|
/** |
|
* Create a new payment invoice |
|
* @param params - Invoice parameters |
|
* @example |
|
* const invoice = await client.invoices.create({ |
|
* amount: '100.00', |
|
* asset: 'USDT', |
|
* description: 'Order #12345', |
|
* external_id: 'order-12345', |
|
* webhook_url: 'https://yoursite.com/webhook' |
|
* }); |
|
* |
|
* console.log(`Payment address: ${invoice.payment_address}`); |
|
* console.log(`Expires: ${invoice.expires_at}`); |
|
*/ |
|
async create(params) { |
|
const response = await this.http.post("/api/invoices", { |
|
amount: params.amount, |
|
asset: params.asset, |
|
description: params.description, |
|
external_id: params.external_id, |
|
webhook_url: params.webhook_url, |
|
redirect_url: params.redirect_url, |
|
expires_in: params.expires_in |
|
}); |
|
if (!response.data) { |
|
throw new Error("Failed to create invoice"); |
|
} |
|
return response.data; |
|
} |
|
/** |
|
* Get all invoices |
|
* @param options - Pagination and filter options |
|
*/ |
|
async list(options = {}) { |
|
return this.http.get("/api/invoices", { |
|
limit: options.limit || 50, |
|
offset: options.offset || 0, |
|
status: options.status |
|
}); |
|
} |
|
/** |
|
* Get a specific invoice by ID |
|
* @param invoiceId - Invoice UUID |
|
*/ |
|
async get(invoiceId) { |
|
const response = await this.http.get(`/api/invoices/${invoiceId}`); |
|
if (!response.data) { |
|
throw new Error("Invoice not found"); |
|
} |
|
return response.data; |
|
} |
|
/** |
|
* Get invoice status (for polling) |
|
* @param invoiceId - Invoice UUID |
|
*/ |
|
async status(invoiceId) { |
|
return this.http.get(`/api/invoices/${invoiceId}/status`); |
|
} |
|
/** |
|
* Cancel a pending invoice |
|
* @param invoiceId - Invoice UUID |
|
*/ |
|
async cancel(invoiceId) { |
|
return this.http.post(`/api/invoices/${invoiceId}/cancel`); |
|
} |
|
/** |
|
* Get invoice PDF |
|
* @param invoiceId - Invoice UUID |
|
* @returns PDF blob |
|
*/ |
|
async pdf(invoiceId) { |
|
return this.http.get(`/api/invoices/${invoiceId}/pdf`); |
|
} |
|
/** |
|
* Get estimated fees for an invoice |
|
* @param asset - Asset symbol |
|
* @param amount - Invoice amount |
|
*/ |
|
async fees(asset, amount) { |
|
const response = await this.http.get("/api/invoices/fees", { |
|
asset, |
|
amount |
|
}); |
|
if (!response.data) { |
|
return { |
|
network_fee: "0", |
|
service_fee: "0", |
|
total_fee: "0" |
|
}; |
|
} |
|
return response.data; |
|
} |
|
/** |
|
* Get public invoice payment page data (no auth required) |
|
* @param invoiceId - Invoice UUID |
|
*/ |
|
async paymentPage(invoiceId) { |
|
return this.http.get(`/api/invoices/${invoiceId}/pay`); |
|
} |
|
}; |
|
|
|
// src/client.ts |
|
var DEFAULT_BASE_URL = "https://api.klingex.io"; |
|
var DEFAULT_WS_URL = "wss://api.klingex.io/ws"; |
|
var DEFAULT_TIMEOUT = 3e4; |
|
var KlingEx = class { |
|
constructor(config = {}) { |
|
this._ws = null; |
|
this.config = { |
|
apiKey: config.apiKey, |
|
jwt: config.jwt, |
|
baseUrl: config.baseUrl || DEFAULT_BASE_URL, |
|
wsUrl: config.wsUrl || DEFAULT_WS_URL, |
|
timeout: config.timeout || DEFAULT_TIMEOUT, |
|
humanReadable: config.humanReadable ?? true |
|
}; |
|
this.http = new HttpClient({ |
|
baseUrl: this.config.baseUrl, |
|
apiKey: this.config.apiKey, |
|
jwt: this.config.jwt, |
|
timeout: this.config.timeout |
|
}); |
|
this.markets = new MarketsEndpoint(this.http); |
|
this.orders = new OrdersEndpoint(this.http, this.config.humanReadable); |
|
this.wallet = new WalletEndpoint(this.http); |
|
this.invoices = new InvoicesEndpoint(this.http); |
|
} |
|
/** |
|
* WebSocket client for real-time data |
|
* Note: Call ws.connect() before subscribing to channels |
|
*/ |
|
get ws() { |
|
if (!this._ws) { |
|
this._ws = new KlingExWebSocket( |
|
this.config.wsUrl, |
|
{ apiKey: this.config.apiKey, jwt: this.config.jwt } |
|
); |
|
} |
|
return this._ws; |
|
} |
|
/** |
|
* Create WebSocket connection with custom options |
|
*/ |
|
createWebSocket(options) { |
|
return new KlingExWebSocket( |
|
this.config.wsUrl, |
|
{ apiKey: this.config.apiKey, jwt: this.config.jwt }, |
|
options |
|
); |
|
} |
|
/** |
|
* Update authentication credentials |
|
*/ |
|
setAuth(auth) { |
|
if (auth.apiKey) this.config.apiKey = auth.apiKey; |
|
if (auth.jwt) this.config.jwt = auth.jwt; |
|
this.http.setAuth(auth); |
|
} |
|
/** |
|
* Check if client has authentication configured |
|
*/ |
|
get isAuthenticated() { |
|
return !!(this.config.apiKey || this.config.jwt); |
|
} |
|
/** |
|
* Get the configured base URL |
|
*/ |
|
get baseUrl() { |
|
return this.config.baseUrl; |
|
} |
|
// ========================================================================= |
|
// Account endpoints (directly on client for convenience) |
|
// ========================================================================= |
|
/** |
|
* Get current user profile |
|
*/ |
|
async getProfile() { |
|
return this.http.get("/api/profile"); |
|
} |
|
/** |
|
* Get current user info |
|
*/ |
|
async getUser() { |
|
return this.http.get("/api/user"); |
|
} |
|
/** |
|
* Get API key statistics |
|
*/ |
|
async getApiKeyStats() { |
|
return this.http.get("/api/api-keys/stats"); |
|
} |
|
}; |
|
// Annotate the CommonJS export names for ESM import in node: |
|
0 && (module.exports = { |
|
AuthenticationError, |
|
InsufficientFundsError, |
|
InvoicesEndpoint, |
|
KlingEx, |
|
KlingExError, |
|
KlingExWebSocket, |
|
MarketsEndpoint, |
|
OrdersEndpoint, |
|
RateLimitError, |
|
ValidationError, |
|
WalletEndpoint |
|
});
|
|
|