ccxt
Version:
1,166 lines (1,164 loc) • 94.1 kB
JavaScript
'use strict';
var Precise = require('./base/Precise.js');
var paradex$1 = require('./abstract/paradex.js');
var errors = require('./base/errors.js');
var number = require('./base/functions/number.js');
var crypto = require('./base/functions/crypto.js');
var sha3 = require('./static_dependencies/noble-hashes/sha3.js');
var secp256k1 = require('./static_dependencies/noble-curves/secp256k1.js');
// ----------------------------------------------------------------------------
// ---------------------------------------------------------------------------
/**
* @class paradex
* @description Paradex is a decentralized exchange built on the StarkWare layer 2 scaling solution. To access private methods you can either use the ETH public key and private key by setting (exchange.privateKey and exchange.walletAddress)
* or alternatively you can provide the startknet private key and public key by setting exchange.options['paradexAccount'] with add {"privateKey": A, "publicKey": B, "address": C}
* @augments Exchange
*/
class paradex extends paradex$1 {
describe() {
return this.deepExtend(super.describe(), {
'id': 'paradex',
'name': 'Paradex',
'countries': [],
'version': 'v1',
'rateLimit': 50,
'certified': false,
'pro': true,
'dex': true,
'has': {
'CORS': undefined,
'spot': false,
'margin': false,
'swap': true,
'future': false,
'option': false,
'addMargin': false,
'borrowCrossMargin': false,
'borrowIsolatedMargin': false,
'cancelAllOrders': true,
'cancelAllOrdersAfter': false,
'cancelOrder': false,
'cancelOrders': false,
'cancelOrdersForSymbols': false,
'closeAllPositions': false,
'closePosition': false,
'createMarketBuyOrderWithCost': false,
'createMarketOrderWithCost': false,
'createMarketSellOrderWithCost': false,
'createOrder': true,
'createOrders': false,
'createReduceOnlyOrder': false,
'createStopOrder': true,
'createTriggerOrder': true,
'editOrder': false,
'fetchAccounts': false,
'fetchBalance': true,
'fetchBorrowInterest': false,
'fetchBorrowRateHistories': false,
'fetchBorrowRateHistory': false,
'fetchCanceledOrders': false,
'fetchClosedOrders': false,
'fetchCrossBorrowRate': false,
'fetchCrossBorrowRates': false,
'fetchCurrencies': false,
'fetchDepositAddress': false,
'fetchDepositAddresses': false,
'fetchDeposits': true,
'fetchDepositWithdrawFee': false,
'fetchDepositWithdrawFees': false,
'fetchFundingHistory': false,
'fetchFundingRate': false,
'fetchFundingRateHistory': false,
'fetchFundingRates': false,
'fetchIndexOHLCV': false,
'fetchIsolatedBorrowRate': false,
'fetchIsolatedBorrowRates': false,
'fetchLedger': false,
'fetchLeverage': false,
'fetchLeverageTiers': false,
'fetchLiquidations': true,
'fetchMarginMode': undefined,
'fetchMarketLeverageTiers': false,
'fetchMarkets': true,
'fetchMarkOHLCV': false,
'fetchMyLiquidations': false,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenInterest': true,
'fetchOpenInterestHistory': false,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchOrderTrades': false,
'fetchPosition': true,
'fetchPositionMode': false,
'fetchPositions': true,
'fetchPositionsRisk': false,
'fetchPremiumIndexOHLCV': false,
'fetchStatus': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTime': true,
'fetchTrades': true,
'fetchTradingFee': false,
'fetchTradingFees': false,
'fetchTransfer': false,
'fetchTransfers': false,
'fetchWithdrawal': false,
'fetchWithdrawals': true,
'reduceMargin': false,
'repayCrossMargin': false,
'repayIsolatedMargin': false,
'sandbox': true,
'setLeverage': false,
'setMarginMode': false,
'setPositionMode': false,
'transfer': false,
'withdraw': false,
},
'timeframes': {
'1m': 1,
'3m': 3,
'5m': 5,
'15m': 15,
'30m': 30,
'1h': 60,
},
'hostname': 'paradex.trade',
'urls': {
'logo': 'https://github.com/user-attachments/assets/84628770-784e-4ec4-a759-ec2fbb2244ea',
'api': {
'v1': 'https://api.prod.{hostname}/v1',
},
'test': {
'v1': 'https://api.testnet.{hostname}/v1',
},
'www': 'https://www.paradex.trade/',
'doc': 'https://docs.api.testnet.paradex.trade/',
'fees': 'https://docs.paradex.trade/getting-started/trading-fees',
'referral': 'https://app.paradex.trade/r/ccxt24',
},
'api': {
'public': {
'get': {
'bbo/{market}': 1,
'funding/data': 1,
'markets': 1,
'markets/klines': 1,
'markets/summary': 1,
'orderbook/{market}': 1,
'insurance': 1,
'referrals/config': 1,
'system/config': 1,
'system/state': 1,
'system/time': 1,
'trades': 1,
},
},
'private': {
'get': {
'account': 1,
'account/profile': 1,
'balance': 1,
'fills': 1,
'funding/payments': 1,
'positions': 1,
'tradebusts': 1,
'transactions': 1,
'liquidations': 1,
'orders': 1,
'orders-history': 1,
'orders/by_client_id/{client_id}': 1,
'orders/{order_id}': 1,
'points_data/{market}/{program}': 1,
'referrals/summary': 1,
'transfers': 1,
},
'post': {
'account/profile/referral_code': 1,
'account/profile/username': 1,
'auth': 1,
'onboarding': 1,
'orders': 1,
},
'delete': {
'orders': 1,
'orders/by_client_id/{client_id}': 1,
'orders/{order_id}': 1,
},
},
},
'fees': {
'swap': {
'taker': this.parseNumber('0.0002'),
'maker': this.parseNumber('0.0002'),
},
'spot': {
'taker': this.parseNumber('0.0002'),
'maker': this.parseNumber('0.0002'),
},
},
'requiredCredentials': {
'apiKey': false,
'secret': false,
'walletAddress': true,
'privateKey': true,
},
'exceptions': {
'exact': {
'VALIDATION_ERROR': errors.AuthenticationError,
'BINDING_ERROR': errors.OperationRejected,
'INTERNAL_ERROR': errors.ExchangeError,
'NOT_FOUND': errors.BadRequest,
'SERVICE_UNAVAILABLE': errors.ExchangeError,
'INVALID_REQUEST_PARAMETER': errors.BadRequest,
'ORDER_ID_NOT_FOUND': errors.InvalidOrder,
'ORDER_IS_CLOSED': errors.InvalidOrder,
'ORDER_IS_NOT_OPEN_YET': errors.InvalidOrder,
'CLIENT_ORDER_ID_NOT_FOUND': errors.InvalidOrder,
'DUPLICATED_CLIENT_ID': errors.InvalidOrder,
'INVALID_PRICE_PRECISION': errors.OperationRejected,
'INVALID_SYMBOL': errors.OperationRejected,
'INVALID_TOKEN': errors.OperationRejected,
'INVALID_ETHEREUM_ADDRESS': errors.OperationRejected,
'INVALID_ETHEREUM_SIGNATURE': errors.OperationRejected,
'INVALID_STARKNET_ADDRESS': errors.OperationRejected,
'INVALID_STARKNET_SIGNATURE': errors.OperationRejected,
'STARKNET_SIGNATURE_VERIFICATION_FAILED': errors.AuthenticationError,
'BAD_STARKNET_REQUEST': errors.BadRequest,
'ETHEREUM_SIGNER_MISMATCH': errors.BadRequest,
'ETHEREUM_HASH_MISMATCH': errors.BadRequest,
'NOT_ONBOARDED': errors.BadRequest,
'INVALID_TIMESTAMP': errors.BadRequest,
'INVALID_SIGNATURE_EXPIRATION': errors.AuthenticationError,
'ACCOUNT_NOT_FOUND': errors.AuthenticationError,
'INVALID_ORDER_SIGNATURE': errors.AuthenticationError,
'PUBLIC_KEY_INVALID': errors.BadRequest,
'UNAUTHORIZED_ETHEREUM_ADDRESS': errors.BadRequest,
'ETHEREUM_ADDRESS_ALREADY_ONBOARDED': errors.BadRequest,
'MARKET_NOT_FOUND': errors.BadRequest,
'ALLOWLIST_ENTRY_NOT_FOUND': errors.BadRequest,
'USERNAME_IN_USE': errors.AuthenticationError,
'GEO_IP_BLOCK': errors.PermissionDenied,
'ETHEREUM_ADDRESS_BLOCKED': errors.PermissionDenied,
'PROGRAM_NOT_FOUND': errors.BadRequest,
'INVALID_DASHBOARD': errors.OperationRejected,
'MARKET_NOT_OPEN': errors.BadRequest,
'INVALID_REFERRAL_CODE': errors.OperationRejected,
'PARENT_ADDRESS_ALREADY_ONBOARDED': errors.BadRequest,
'INVALID_PARENT_ACCOUNT': errors.OperationRejected,
'INVALID_VAULT_OPERATOR_CHAIN': errors.OperationRejected,
'VAULT_OPERATOR_ALREADY_ONBOARDED': errors.OperationRejected,
'VAULT_NAME_IN_USE': errors.OperationRejected,
'BATCH_SIZE_OUT_OF_RANGE': errors.OperationRejected,
'ISOLATED_MARKET_ACCOUNT_MISMATCH': errors.OperationRejected,
'POINTS_SUMMARY_NOT_FOUND': errors.OperationRejected,
'-32700': errors.BadRequest,
'-32600': errors.BadRequest,
'-32601': errors.BadRequest,
'-32602': errors.BadRequest,
'-32603': errors.ExchangeError,
'100': errors.BadRequest,
'40110': errors.AuthenticationError,
'40111': errors.AuthenticationError,
'40112': errors.PermissionDenied, // Geo IP blocked
},
'broad': {
'missing or malformed jwt': errors.AuthenticationError,
},
},
'precisionMode': number.TICK_SIZE,
'commonCurrencies': {},
'options': {
'paradexAccount': undefined,
'broker': 'CCXT',
},
'features': {
'spot': undefined,
'forSwap': {
'sandbox': true,
'createOrder': {
'marginMode': false,
'triggerPrice': true,
'triggerDirection': true,
'triggerPriceType': undefined,
'stopLossPrice': false,
'takeProfitPrice': false,
'attachedStopLossTakeProfit': undefined,
'timeInForce': {
'IOC': true,
'FOK': false,
'PO': true,
'GTD': false,
},
'hedged': false,
'trailing': false,
'leverage': false,
'marketBuyByCost': false,
'marketBuyRequiresPrice': false,
'selfTradePrevention': true,
'iceberg': false,
},
'createOrders': undefined,
'fetchMyTrades': {
'marginMode': false,
'limit': 100,
'daysBack': 100000,
'untilDays': 100000,
'symbolRequired': false,
},
'fetchOrder': {
'marginMode': false,
'trigger': false,
'trailing': false,
'symbolRequired': false,
},
'fetchOpenOrders': {
'marginMode': false,
'limit': 100,
'trigger': false,
'trailing': false,
'symbolRequired': false,
},
'fetchOrders': {
'marginMode': false,
'limit': 100,
'daysBack': 100000,
'untilDays': 100000,
'trigger': false,
'trailing': false,
'symbolRequired': false,
},
'fetchClosedOrders': undefined,
'fetchOHLCV': {
'limit': undefined, // todo by from/to
},
},
'swap': {
'linear': {
'extends': 'forSwap',
},
'inverse': undefined,
},
'future': {
'linear': undefined,
'inverse': undefined,
},
},
});
}
/**
* @method
* @name paradex#fetchTime
* @description fetches the current integer timestamp in milliseconds from the exchange server
* @see https://docs.api.testnet.paradex.trade/#get-system-time-unix-milliseconds
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {int} the current integer timestamp in milliseconds from the exchange server
*/
async fetchTime(params = {}) {
const response = await this.publicGetSystemTime(params);
//
// {
// "server_time": "1681493415023"
// }
//
return this.safeInteger(response, 'server_time');
}
/**
* @method
* @name paradex#fetchStatus
* @description the latest known information on the availability of the exchange API
* @see https://docs.api.testnet.paradex.trade/#get-system-state
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a [status structure]{@link https://docs.ccxt.com/#/?id=exchange-status-structure}
*/
async fetchStatus(params = {}) {
const response = await this.publicGetSystemState(params);
//
// {
// "status": "ok"
// }
//
const status = this.safeString(response, 'status');
return {
'status': (status === 'ok') ? 'ok' : 'maintenance',
'updated': undefined,
'eta': undefined,
'url': undefined,
'info': response,
};
}
/**
* @method
* @name paradex#fetchMarkets
* @description retrieves data on all markets for bitget
* @see https://docs.api.testnet.paradex.trade/#list-available-markets
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object[]} an array of objects representing market data
*/
async fetchMarkets(params = {}) {
const response = await this.publicGetMarkets(params);
//
// {
// "results": [
// {
// "symbol": "BODEN-USD-PERP",
// "base_currency": "BODEN",
// "quote_currency": "USD",
// "settlement_currency": "USDC",
// "order_size_increment": "1",
// "price_tick_size": "0.00001",
// "min_notional": "200",
// "open_at": 1717065600000,
// "expiry_at": 0,
// "asset_kind": "PERP",
// "position_limit": "2000000",
// "price_bands_width": "0.2",
// "max_open_orders": 50,
// "max_funding_rate": "0.05",
// "delta1_cross_margin_params": {
// "imf_base": "0.2",
// "imf_shift": "180000",
// "imf_factor": "0.00071",
// "mmf_factor": "0.5"
// },
// "price_feed_id": "9LScEHse1ioZt2rUuhwiN6bmYnqpMqvZkQJDNUpxVHN5",
// "oracle_ewma_factor": "0.14999987905913592",
// "max_order_size": "520000",
// "max_funding_rate_change": "0.0005",
// "max_tob_spread": "0.2"
// }
// ]
// }
//
const data = this.safeList(response, 'results');
return this.parseMarkets(data);
}
parseMarket(market) {
//
// {
// "symbol": "BODEN-USD-PERP",
// "base_currency": "BODEN",
// "quote_currency": "USD",
// "settlement_currency": "USDC",
// "order_size_increment": "1",
// "price_tick_size": "0.00001",
// "min_notional": "200",
// "open_at": 1717065600000,
// "expiry_at": 0,
// "asset_kind": "PERP",
// "position_limit": "2000000",
// "price_bands_width": "0.2",
// "max_open_orders": 50,
// "max_funding_rate": "0.05",
// "delta1_cross_margin_params": {
// "imf_base": "0.2",
// "imf_shift": "180000",
// "imf_factor": "0.00071",
// "mmf_factor": "0.5"
// },
// "price_feed_id": "9LScEHse1ioZt2rUuhwiN6bmYnqpMqvZkQJDNUpxVHN5",
// "oracle_ewma_factor": "0.14999987905913592",
// "max_order_size": "520000",
// "max_funding_rate_change": "0.0005",
// "max_tob_spread": "0.2"
// }
//
const marketId = this.safeString(market, 'symbol');
const quoteId = this.safeString(market, 'quote_currency');
const baseId = this.safeString(market, 'base_currency');
const quote = this.safeCurrencyCode(quoteId);
const base = this.safeCurrencyCode(baseId);
const settleId = this.safeString(market, 'settlement_currency');
const settle = this.safeCurrencyCode(settleId);
const symbol = base + '/' + quote + ':' + settle;
const expiry = this.safeInteger(market, 'expiry_at');
const takerFee = this.parseNumber('0.0003');
const makerFee = this.parseNumber('-0.00005');
return this.safeMarketStructure({
'id': marketId,
'symbol': symbol,
'base': base,
'quote': quote,
'settle': settle,
'baseId': baseId,
'quoteId': quoteId,
'settleId': settleId,
'type': 'swap',
'spot': false,
'margin': undefined,
'swap': true,
'future': false,
'option': false,
'active': this.safeBool(market, 'enableTrading'),
'contract': true,
'linear': true,
'inverse': undefined,
'taker': takerFee,
'maker': makerFee,
'contractSize': this.parseNumber('1'),
'expiry': (expiry === 0) ? undefined : expiry,
'expiryDatetime': (expiry === 0) ? undefined : this.iso8601(expiry),
'strike': undefined,
'optionType': undefined,
'precision': {
'amount': this.safeNumber(market, 'order_size_increment'),
'price': this.safeNumber(market, 'price_tick_size'),
},
'limits': {
'leverage': {
'min': undefined,
'max': undefined,
},
'amount': {
'min': undefined,
'max': this.safeNumber(market, 'max_order_size'),
},
'price': {
'min': undefined,
'max': undefined,
},
'cost': {
'min': this.safeNumber(market, 'min_notional'),
'max': undefined,
},
},
'created': undefined,
'info': market,
});
}
/**
* @method
* @name paradex#fetchOHLCV
* @description fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
* @see https://docs.api.testnet.paradex.trade/#ohlcv-for-a-symbol
* @param {string} symbol unified symbol of the market to fetch OHLCV data for
* @param {string} timeframe the length of time each candle represents
* @param {int} [since] timestamp in ms of the earliest candle to fetch
* @param {int} [limit] the maximum amount of candles to fetch
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {int} [params.until] timestamp in ms of the latest candle to fetch
* @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume
*/
async fetchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const request = {
'resolution': this.safeString(this.timeframes, timeframe, timeframe),
'symbol': market['id'],
};
const now = this.milliseconds();
const duration = this.parseTimeframe(timeframe);
const until = this.safeInteger2(params, 'until', 'till', now);
params = this.omit(params, ['until', 'till']);
if (since !== undefined) {
request['start_at'] = since;
if (limit !== undefined) {
request['end_at'] = this.sum(since, duration * (limit + 1) * 1000) - 1;
}
else {
request['end_at'] = until;
}
}
else {
request['end_at'] = until;
if (limit !== undefined) {
request['start_at'] = until - duration * (limit + 1) * 1000 + 1;
}
else {
request['start_at'] = until - duration * 101 * 1000 + 1;
}
}
const response = await this.publicGetMarketsKlines(this.extend(request, params));
//
// {
// "results": [
// [
// 1720071900000,
// 58961.3,
// 58961.3,
// 58961.3,
// 58961.3,
// 1591
// ]
// ]
// }
//
const data = this.safeList(response, 'results', []);
return this.parseOHLCVs(data, market, timeframe, since, limit);
}
parseOHLCV(ohlcv, market = undefined) {
//
// [
// 1720071900000,
// 58961.3,
// 58961.3,
// 58961.3,
// 58961.3,
// 1591
// ]
//
return [
this.safeInteger(ohlcv, 0),
this.safeNumber(ohlcv, 1),
this.safeNumber(ohlcv, 2),
this.safeNumber(ohlcv, 3),
this.safeNumber(ohlcv, 4),
this.safeNumber(ohlcv, 5),
];
}
/**
* @method
* @name paradex#fetchTickers
* @description fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
* @see https://docs.api.testnet.paradex.trade/#list-available-markets-summary
* @param {string[]|undefined} symbols unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure}
*/
async fetchTickers(symbols = undefined, params = {}) {
await this.loadMarkets();
symbols = this.marketSymbols(symbols);
const request = {};
if (symbols !== undefined) {
if (Array.isArray(symbols)) {
request['market'] = this.marketId(symbols[0]);
}
else {
request['market'] = this.marketId(symbols);
}
}
else {
request['market'] = 'ALL';
}
const response = await this.publicGetMarketsSummary(this.extend(request, params));
//
// {
// "results": [
// {
// "symbol": "BTC-USD-PERP",
// "oracle_price": "68465.17449906",
// "mark_price": "68465.17449906",
// "last_traded_price": "68495.1",
// "bid": "68477.6",
// "ask": "69578.2",
// "volume_24h": "5815541.397939004",
// "total_volume": "584031465.525259686",
// "created_at": 1718170156580,
// "underlying_price": "67367.37268422",
// "open_interest": "162.272",
// "funding_rate": "0.01629574927887",
// "price_change_rate_24h": "0.009032"
// }
// ]
// }
//
const data = this.safeList(response, 'results', []);
return this.parseTickers(data, symbols);
}
/**
* @method
* @name paradex#fetchTicker
* @description fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
* @see https://docs.api.testnet.paradex.trade/#list-available-markets-summary
* @param {string} symbol unified symbol of the market to fetch the ticker for
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}
*/
async fetchTicker(symbol, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const request = {
'market': market['id'],
};
const response = await this.publicGetMarketsSummary(this.extend(request, params));
//
// {
// "results": [
// {
// "symbol": "BTC-USD-PERP",
// "oracle_price": "68465.17449906",
// "mark_price": "68465.17449906",
// "last_traded_price": "68495.1",
// "bid": "68477.6",
// "ask": "69578.2",
// "volume_24h": "5815541.397939004",
// "total_volume": "584031465.525259686",
// "created_at": 1718170156580,
// "underlying_price": "67367.37268422",
// "open_interest": "162.272",
// "funding_rate": "0.01629574927887",
// "price_change_rate_24h": "0.009032"
// }
// ]
// }
//
const data = this.safeList(response, 'results', []);
const ticker = this.safeDict(data, 0, {});
return this.parseTicker(ticker, market);
}
parseTicker(ticker, market = undefined) {
//
// {
// "symbol": "BTC-USD-PERP",
// "oracle_price": "68465.17449906",
// "mark_price": "68465.17449906",
// "last_traded_price": "68495.1",
// "bid": "68477.6",
// "ask": "69578.2",
// "volume_24h": "5815541.397939004",
// "total_volume": "584031465.525259686",
// "created_at": 1718170156580,
// "underlying_price": "67367.37268422",
// "open_interest": "162.272",
// "funding_rate": "0.01629574927887",
// "price_change_rate_24h": "0.009032"
// }
//
let percentage = this.safeString(ticker, 'price_change_rate_24h');
if (percentage !== undefined) {
percentage = Precise["default"].stringMul(percentage, '100');
}
const last = this.safeString(ticker, 'last_traded_price');
const marketId = this.safeString(ticker, 'symbol');
market = this.safeMarket(marketId, market);
const symbol = market['symbol'];
const timestamp = this.safeInteger(ticker, 'created_at');
return this.safeTicker({
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'high': undefined,
'low': undefined,
'bid': this.safeString(ticker, 'bid'),
'bidVolume': undefined,
'ask': this.safeString(ticker, 'ask'),
'askVolume': undefined,
'vwap': undefined,
'open': undefined,
'close': last,
'last': last,
'previousClose': undefined,
'change': undefined,
'percentage': percentage,
'average': undefined,
'baseVolume': undefined,
'quoteVolume': this.safeString(ticker, 'volume_24h'),
'markPrice': this.safeString(ticker, 'mark_price'),
'info': ticker,
}, market);
}
/**
* @method
* @name paradex#fetchOrderBook
* @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
* @see https://docs.api.testnet.paradex.trade/#get-market-orderbook
* @param {string} symbol unified symbol of the market to fetch the order book for
* @param {int} [limit] the maximum amount of order book entries to return
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
*/
async fetchOrderBook(symbol, limit = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const request = { 'market': market['id'] };
const response = await this.publicGetOrderbookMarket(this.extend(request, params));
//
// {
// "market": "BTC-USD-PERP",
// "seq_no": 14115975,
// "last_updated_at": 1718172538340,
// "asks": [
// [
// "69578.2",
// "3.019"
// ]
// ],
// "bids": [
// [
// "68477.6",
// "0.1"
// ]
// ]
// }
//
if (limit !== undefined) {
request['depth'] = limit;
}
const timestamp = this.safeInteger(response, 'last_updated_at');
const orderbook = this.parseOrderBook(response, market['symbol'], timestamp);
orderbook['nonce'] = this.safeInteger(response, 'seq_no');
return orderbook;
}
/**
* @method
* @name paradex#fetchTrades
* @description get the list of most recent trades for a particular symbol
* @see https://docs.api.testnet.paradex.trade/#trade-tape
* @param {string} symbol unified symbol of the market to fetch trades for
* @param {int} [since] timestamp in ms of the earliest trade to fetch
* @param {int} [limit] the maximum amount of trades to fetch
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {int} [params.until] the latest time in ms to fetch trades for
* @param {boolean} [params.paginate] default false, when true will automatically paginate by calling this endpoint multiple times
* @returns {Trade[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades}
*/
async fetchTrades(symbol, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
let paginate = false;
[paginate, params] = this.handleOptionAndParams(params, 'fetchTrades', 'paginate');
if (paginate) {
return await this.fetchPaginatedCallCursor('fetchTrades', symbol, since, limit, params, 'next', 'cursor', undefined, 100);
}
const market = this.market(symbol);
let request = {
'market': market['id'],
};
if (limit !== undefined) {
request['page_size'] = limit;
}
if (since !== undefined) {
request['start_at'] = since;
}
[request, params] = this.handleUntilOption('end_at', request, params);
const response = await this.publicGetTrades(this.extend(request, params));
//
// {
// "next": "...",
// "prev": "...",
// "results": [
// {
// "id": "1718154353750201703989430001",
// "market": "BTC-USD-PERP",
// "side": "BUY",
// "size": "0.026",
// "price": "69578.2",
// "created_at": 1718154353750,
// "trade_type": "FILL"
// }
// ]
// }
//
const trades = this.safeList(response, 'results', []);
for (let i = 0; i < trades.length; i++) {
trades[i]['next'] = this.safeString(response, 'next');
}
return this.parseTrades(trades, market, since, limit);
}
parseTrade(trade, market = undefined) {
//
// fetchTrades (public)
//
// {
// "id": "1718154353750201703989430001",
// "market": "BTC-USD-PERP",
// "side": "BUY",
// "size": "0.026",
// "price": "69578.2",
// "created_at": 1718154353750,
// "trade_type": "FILL"
// }
//
// fetchMyTrades (private)
//
// {
// "id": "1718947571560201703986670001",
// "side": "BUY",
// "liquidity": "TAKER",
// "market": "BTC-USD-PERP",
// "order_id": "1718947571540201703992340000",
// "price": "64852.9",
// "size": "0.01",
// "fee": "0.1945587",
// "fee_currency": "USDC",
// "created_at": 1718947571569,
// "remaining_size": "0",
// "client_id": "",
// "fill_type": "FILL"
// }
//
const marketId = this.safeString(trade, 'market');
market = this.safeMarket(marketId, market);
const id = this.safeString(trade, 'id');
const timestamp = this.safeInteger(trade, 'created_at');
const priceString = this.safeString(trade, 'price');
const amountString = this.safeString(trade, 'size');
const side = this.safeStringLower(trade, 'side');
const liability = this.safeStringLower(trade, 'liquidity', 'taker');
const isTaker = liability === 'taker';
const takerOrMaker = (isTaker) ? 'taker' : 'maker';
const currencyId = this.safeString(trade, 'fee_currency');
const code = this.safeCurrencyCode(currencyId);
return this.safeTrade({
'info': trade,
'id': id,
'order': this.safeString(trade, 'order_id'),
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'symbol': market['symbol'],
'type': undefined,
'takerOrMaker': takerOrMaker,
'side': side,
'price': priceString,
'amount': amountString,
'cost': undefined,
'fee': {
'cost': this.safeString(trade, 'fee'),
'currency': code,
'rate': undefined,
},
}, market);
}
/**
* @method
* @name paradex#fetchOpenInterest
* @description retrieves the open interest of a contract trading pair
* @see https://docs.api.testnet.paradex.trade/#list-available-markets-summary
* @param {string} symbol unified CCXT market symbol
* @param {object} [params] exchange specific parameters
* @returns {object} an open interest structure{@link https://docs.ccxt.com/#/?id=open-interest-structure}
*/
async fetchOpenInterest(symbol, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
if (!market['contract']) {
throw new errors.BadRequest(this.id + ' fetchOpenInterest() supports contract markets only');
}
const request = {
'market': market['id'],
};
const response = await this.publicGetMarketsSummary(this.extend(request, params));
//
// {
// "results": [
// {
// "symbol": "BTC-USD-PERP",
// "oracle_price": "68465.17449906",
// "mark_price": "68465.17449906",
// "last_traded_price": "68495.1",
// "bid": "68477.6",
// "ask": "69578.2",
// "volume_24h": "5815541.397939004",
// "total_volume": "584031465.525259686",
// "created_at": 1718170156580,
// "underlying_price": "67367.37268422",
// "open_interest": "162.272",
// "funding_rate": "0.01629574927887",
// "price_change_rate_24h": "0.009032"
// }
// ]
// }
//
const data = this.safeList(response, 'results', []);
const interest = this.safeDict(data, 0, {});
return this.parseOpenInterest(interest, market);
}
parseOpenInterest(interest, market = undefined) {
//
// {
// "symbol": "BTC-USD-PERP",
// "oracle_price": "68465.17449904",
// "mark_price": "68465.17449906",
// "last_traded_price": "68495.1",
// "bid": "68477.6",
// "ask": "69578.2",
// "volume_24h": "5815541.397939004",
// "total_volume": "584031465.525259686",
// "created_at": 1718170156580,
// "underlying_price": "67367.37268422",
// "open_interest": "162.272",
// "funding_rate": "0.01629574927887",
// "price_change_rate_24h": "0.009032"
// }
//
const timestamp = this.safeInteger(interest, 'created_at');
const marketId = this.safeString(interest, 'symbol');
market = this.safeMarket(marketId, market);
const symbol = market['symbol'];
return this.safeOpenInterest({
'symbol': symbol,
'openInterestAmount': this.safeString(interest, 'open_interest'),
'openInterestValue': undefined,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'info': interest,
}, market);
}
hashMessage(message) {
return '0x' + this.hash(message, sha3.keccak_256, 'hex');
}
signHash(hash, privateKey) {
const signature = crypto.ecdsa(hash.slice(-64), privateKey.slice(-64), secp256k1.secp256k1, undefined);
const r = signature['r'];
const s = signature['s'];
const v = this.intToBase16(this.sum(27, signature['v']));
return '0x' + r.padStart(64, '0') + s.padStart(64, '0') + v;
}
signMessage(message, privateKey) {
return this.signHash(this.hashMessage(message), privateKey.slice(-64));
}
async getSystemConfig() {
const cachedConfig = this.safeDict(this.options, 'systemConfig');
if (cachedConfig !== undefined) {
return cachedConfig;
}
const response = await this.publicGetSystemConfig();
//
// {
// "starknet_gateway_url": "https://potc-testnet-sepolia.starknet.io",
// "starknet_fullnode_rpc_url": "https://pathfinder.api.testnet.paradex.trade/rpc/v0_7",
// "starknet_chain_id": "PRIVATE_SN_POTC_SEPOLIA",
// "block_explorer_url": "https://voyager.testnet.paradex.trade/",
// "paraclear_address": "0x286003f7c7bfc3f94e8f0af48b48302e7aee2fb13c23b141479ba00832ef2c6",
// "paraclear_decimals": 8,
// "paraclear_account_proxy_hash": "0x3530cc4759d78042f1b543bf797f5f3d647cde0388c33734cf91b7f7b9314a9",
// "paraclear_account_hash": "0x41cb0280ebadaa75f996d8d92c6f265f6d040bb3ba442e5f86a554f1765244e",
// "oracle_address": "0x2c6a867917ef858d6b193a0ff9e62b46d0dc760366920d631715d58baeaca1f",
// "bridged_tokens": [
// {
// "name": "TEST USDC",
// "symbol": "USDC",
// "decimals": 6,
// "l1_token_address": "0x29A873159D5e14AcBd63913D4A7E2df04570c666",
// "l1_bridge_address": "0x8586e05adc0C35aa11609023d4Ae6075Cb813b4C",
// "l2_token_address": "0x6f373b346561036d98ea10fb3e60d2f459c872b1933b50b21fe6ef4fda3b75e",
// "l2_bridge_address": "0x46e9237f5408b5f899e72125dd69bd55485a287aaf24663d3ebe00d237fc7ef"
// }
// ],
// "l1_core_contract_address": "0x582CC5d9b509391232cd544cDF9da036e55833Af",
// "l1_operator_address": "0x11bACdFbBcd3Febe5e8CEAa75E0Ef6444d9B45FB",
// "l1_chain_id": "11155111",
// "liquidation_fee": "0.2"
// }
//
this.options['systemConfig'] = response;
return response;
}
async prepareParadexDomain(l1 = false) {
const systemConfig = await this.getSystemConfig();
if (l1 === true) {
const l1D = {
'name': 'Paradex',
'chainId': systemConfig['l1_chain_id'],
'version': '1',
};
return l1D;
}
const domain = {
'name': 'Paradex',
'chainId': systemConfig['starknet_chain_id'],
'version': 1,
};
return domain;
}
async retrieveAccount() {
const cachedAccount = this.safeDict(this.options, 'paradexAccount');
if (cachedAccount !== undefined) {
return cachedAccount;
}
this.checkRequiredCredentials();
const systemConfig = await this.getSystemConfig();
const domain = await this.prepareParadexDomain(true);
const messageTypes = {
'Constant': [
{ 'name': 'action', 'type': 'string' },
],
};
const message = {
'action': 'STARK Key',
};
const msg = this.ethEncodeStructuredData(domain, messageTypes, message);
const signature = this.signMessage(msg, this.privateKey);
const account = this.retrieveStarkAccount(signature, systemConfig['paraclear_account_hash'], systemConfig['paraclear_account_proxy_hash']);
this.options['paradexAccount'] = account;
return account;
}
async onboarding(params = {}) {
const account = await this.retrieveAccount();
const req = {
'action': 'Onboarding',
};
const domain = await this.prepareParadexDomain();
const messageTypes = {
'Constant': [
{ 'name': 'action', 'type': 'felt' },
],
};
const msg = this.starknetEncodeStructuredData(domain, messageTypes, req, account['address']);
const signature = this.starknetSign(msg, account['privateKey']);
params['signature'] = signature;
params['account'] = account['address'];
params['public_key'] = account['publicKey'];
const response = await this.privatePostOnboarding(params);
return response;
}
async authenticateRest(params = {}) {
const cachedToken = this.safeString(this.options, 'authToken');
const now = this.nonce();
if (cachedToken !== undefined) {
const cachedExpires = this.safeInteger(this.options, 'expires');
if (now < cachedExpires) {
return cachedToken;
}
}
const account = await this.retrieveAccount();
// https://docs.paradex.trade/api-reference/general-information/authentication
const expires = now + 180;
const req = {
'method': 'POST',
'path': '/v1/auth',
'body': '',
'timestamp': now,
'expiration': expires,
};
const domain = await this.prepareParadexDomain();
const messageTypes = {
'Request': [
{ 'name': 'method', 'type': 'felt' },
{ 'name': 'path', 'type': 'felt' },
{ 'name': 'body', 'type': 'felt' },
{ 'name': 'timestamp', 'type': 'felt' },
{ 'name': 'expiration', 'type': 'felt' },
],
};
const msg = this.starknetEncodeStructuredData(domain, messageTypes, req, account['address']);
const signature = this.starknetSign(msg, account['privateKey']);
params['signature'] = signature;
params['account'] = account['address'];
params['timestamp'] = req['timestamp'];
params['expiration'] = req['expiration'];
const response = await this.privatePostAuth(params);
//
// {
// jwt_token: "ooooccxtooootoooootheoooomoonooooo"
// }
//
const token = this.safeString(response, 'jwt_token');
this.options['authToken'] = token;
this.options['expires'] = expires;
return token;
}
parseOrder(order, market = undefined) {
//
// {
// "account": "0x4638e3041366aa71720be63e32e53e1223316c7f0d56f7aa617542ed1e7512x",
// "avg_fill_price": "26000",
// "client_id": "x1234",
// "cancel_reason": "NOT_ENOUGH_MARGIN",
// "created_at": 1681493746016,
// "flags": [
// "REDUCE_ONLY"
// ],
// "id": "123456",
// "instruction": "GTC",
// "last_updated_at": 1681493746016,
// "market": "BTC-USD-PERP",
// "price": "26000",
// "published_at": 1681493746016,
// "received_at": 1681493746016,
// "remaining_size": "0",
// "seq_no": 1681471234972000000,
// "side": "BUY",
// "size": "0.05",
// "status"