From 8db69840f848237e51404a300b60b939f820063f Mon Sep 17 00:00:00 2001 From: DonDuala Date: Sat, 7 Mar 2026 18:14:55 -0400 Subject: [PATCH] add manifest exchange integration and gateway API contract --- doc/manifest-gateway-api.md | 191 ++++++++ ts/ccxt.ts | 3 + ts/src/abstract/manifest.ts | 36 ++ ts/src/manifest.ts | 859 ++++++++++++++++++++++++++++++++++++ 4 files changed, 1089 insertions(+) create mode 100644 doc/manifest-gateway-api.md create mode 100644 ts/src/abstract/manifest.ts create mode 100644 ts/src/manifest.ts diff --git a/doc/manifest-gateway-api.md b/doc/manifest-gateway-api.md new file mode 100644 index 0000000000000..6fba540562f74 --- /dev/null +++ b/doc/manifest-gateway-api.md @@ -0,0 +1,191 @@ +# Manifest Gateway API (for CCXT private methods) + +This document defines a minimal HTTP contract for the `manifest` CCXT adapter private methods. + +## Base URLs + +- Public stats API: `https://mfx-stats-mainnet.fly.dev` +- Private gateway API: configurable via `exchange.urls.api.private` + +## Authentication + +Recommended: + +- `Authorization: Bearer ` +- Optional: `X-Manifest-Secret: ` + +The current adapter forwards both headers when `apiKey`/`secret` are configured. + +## Endpoints + +### `POST /order` + +Creates an order. + +Request: + +```json +{ + "marketId": "SOL_USDC", + "type": "limit", + "side": "buy", + "amount": "1.25", + "price": "170.5", + "timeInForce": "GTC", + "clientOrderId": "my-id-123" +} +``` + +Response: + +```json +{ + "success": true, + "order": { + "orderId": "123456789", + "clientOrderId": "my-id-123", + "marketId": "SOL_USDC", + "status": "open", + "type": "limit", + "side": "buy", + "price": "170.5", + "amount": "1.25", + "filled": "0", + "remaining": "1.25", + "timestamp": 1762360000000 + } +} +``` + +### `DELETE /order` + +Cancels an order. + +Query params: + +- `orderId` (required) +- `marketId` (optional but recommended) + +Response: + +```json +{ + "success": true, + "order": { + "orderId": "123456789", + "status": "canceled", + "marketId": "SOL_USDC", + "timestamp": 1762360005000 + } +} +``` + +### `GET /order` + +Fetches a single order. + +Query params: + +- `orderId` (required) +- `marketId` (optional) + +Response: + +```json +{ + "success": true, + "order": { + "orderId": "123456789", + "clientOrderId": "my-id-123", + "marketId": "SOL_USDC", + "status": "partially_filled", + "type": "limit", + "side": "buy", + "price": "170.5", + "amount": "1.25", + "filled": "0.5", + "remaining": "0.75", + "timestamp": 1762360000000, + "lastUpdateTimestamp": 1762360010000 + } +} +``` + +### `GET /openOrders` + +Fetches open orders. + +Query params: + +- `marketId` (optional) +- `since` (optional, ms) +- `limit` (optional) + +Response: + +```json +{ + "success": true, + "orders": [ + { + "orderId": "123456789", + "marketId": "SOL_USDC", + "status": "open", + "type": "limit", + "side": "buy", + "price": "170.5", + "amount": "1.25", + "filled": "0", + "remaining": "1.25", + "timestamp": 1762360000000 + } + ] +} +``` + +### `GET /myTrades` + +Fetches user fills/trades. + +Query params: + +- `marketId` (optional) +- `since` (optional, ms) +- `limit` (optional) + +Response: + +```json +{ + "success": true, + "trades": [ + { + "tradeId": "fill-1", + "orderId": "123456789", + "marketId": "SOL_USDC", + "timestamp": 1762360020000, + "side": "buy", + "price": "170.5", + "amount": "0.5", + "cost": "85.25", + "fee": "0.042625", + "feeCurrency": "USDC", + "takerOrMaker": "taker" + } + ] +} +``` + +## Error shape + +Recommended error payload: + +```json +{ + "success": false, + "error": "ORDER_NOT_FOUND", + "message": "order not found" +} +``` + +The adapter maps `error`/`message` to CCXT exceptions via `handleErrors`. diff --git a/ts/ccxt.ts b/ts/ccxt.ts index 1e0efbb677986..db2fe09db2dd6 100644 --- a/ts/ccxt.ts +++ b/ts/ccxt.ts @@ -129,6 +129,7 @@ import latoken from './src/latoken.js' import lbank from './src/lbank.js' import lighter from './src/lighter.js' import luno from './src/luno.js' +import manifest from './src/manifest.js' import mercado from './src/mercado.js' import mexc from './src/mexc.js' import modetrade from './src/modetrade.js' @@ -323,6 +324,7 @@ const exchanges = { 'lbank': lbank, 'lighter': lighter, 'luno': luno, + 'manifest': manifest, 'mercado': mercado, 'mexc': mexc, 'modetrade': modetrade, @@ -660,6 +662,7 @@ export { lbank, lighter, luno, + manifest, mercado, mexc, modetrade, diff --git a/ts/src/abstract/manifest.ts b/ts/src/abstract/manifest.ts new file mode 100644 index 0000000000000..f3dd31cf597ef --- /dev/null +++ b/ts/src/abstract/manifest.ts @@ -0,0 +1,36 @@ +// ------------------------------------------------------------------------------- + +// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: +// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code + +// ------------------------------------------------------------------------------- + +import { implicitReturnType } from '../base/types.js'; +import { Exchange as _Exchange } from '../base/Exchange.js'; + +interface Exchange { + publicGetHealth (params?: {}): Promise; + publicGetTickers (params?: {}): Promise; + publicGetMetadata (params?: {}): Promise; + publicGetOrderbook (params?: {}): Promise; + publicGetVolume (params?: {}): Promise; + publicGetTraders (params?: {}): Promise; + publicGetRecentFills (params?: {}): Promise; + publicGetCompleteFills (params?: {}): Promise; + publicGetCheckpoints (params?: {}): Promise; + publicGetCheckpointStatus (params?: {}): Promise; + publicGetWrapper (params?: {}): Promise; + publicGetWrappers (params?: {}): Promise; + publicGetTradersDebug (params?: {}): Promise; + publicGetNotional (params?: {}): Promise; + publicGetAlts (params?: {}): Promise; + publicGetBackfill (params?: {}): Promise; + privateGetOrder (params?: {}): Promise; + privateGetOpenOrders (params?: {}): Promise; + privateGetMyTrades (params?: {}): Promise; + privatePostOrder (params?: {}): Promise; + privateDeleteOrder (params?: {}): Promise; +} +abstract class Exchange extends _Exchange {} + +export default Exchange diff --git a/ts/src/manifest.ts b/ts/src/manifest.ts new file mode 100644 index 0000000000000..636f0f33b381b --- /dev/null +++ b/ts/src/manifest.ts @@ -0,0 +1,859 @@ +// --------------------------------------------------------------------------- + +import Exchange from './abstract/manifest.js'; +import { ArgumentsRequired, BadSymbol, ExchangeError } from './base/errors.js'; +import { Precise } from './base/Precise.js'; +import type { Dict, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, int } from './base/types.js'; + +// --------------------------------------------------------------------------- + +/** + * @class manifest + * @augments Exchange + */ +export default class manifest extends Exchange { + describe (): any { + return this.deepExtend (super.describe (), { + 'id': 'manifest', + 'name': 'Manifest', + 'countries': [], + 'version': 'v1', + 'rateLimit': 200, + 'certified': false, + 'pro': false, + 'dex': true, + 'has': { + 'CORS': undefined, + 'spot': true, + 'margin': false, + 'swap': false, + 'future': false, + 'option': false, + 'addMargin': false, + 'borrowCrossMargin': false, + 'borrowIsolatedMargin': false, + 'borrowMargin': false, + 'cancelAllOrders': false, + 'cancelOrder': true, + 'cancelOrders': false, + 'closeAllPositions': false, + 'closePosition': false, + 'createOrder': true, + 'createOrders': false, + 'createOrderWithTakeProfitAndStopLoss': false, + 'createPostOnlyOrder': false, + 'createReduceOnlyOrder': false, + 'createStopOrder': false, + 'createTriggerOrder': false, + 'editOrder': false, + 'fetchAllGreeks': false, + 'fetchBalance': false, + 'fetchBorrowInterest': false, + 'fetchBorrowRate': false, + 'fetchBorrowRateHistories': false, + 'fetchBorrowRateHistory': false, + 'fetchBorrowRates': false, + 'fetchBorrowRatesPerSymbol': false, + 'fetchCanceledAndClosedOrders': false, + 'fetchCanceledOrders': false, + 'fetchClosedOrders': false, + 'fetchCrossBorrowRate': false, + 'fetchCrossBorrowRates': false, + 'fetchCurrencies': false, + 'fetchDepositAddress': false, + 'fetchDepositAddresses': false, + 'fetchDeposits': false, + 'fetchDepositWithdrawFee': false, + 'fetchDepositWithdrawFees': false, + 'fetchFundingHistory': false, + 'fetchFundingInterval': false, + 'fetchFundingIntervals': false, + 'fetchFundingRate': false, + 'fetchFundingRateHistory': false, + 'fetchFundingRates': false, + 'fetchGreeks': false, + 'fetchIndexOHLCV': false, + 'fetchIsolatedBorrowRate': false, + 'fetchIsolatedBorrowRates': false, + 'fetchLeverage': false, + 'fetchLeverages': false, + 'fetchLeverageTiers': false, + 'fetchLiquidations': false, + 'fetchLongShortRatio': false, + 'fetchLongShortRatioHistory': false, + 'fetchMarginAdjustmentHistory': false, + 'fetchMarginMode': false, + 'fetchMarginModes': false, + 'fetchMarketLeverageTiers': false, + 'fetchMarkets': true, + 'fetchMarkOHLCV': false, + 'fetchMarkPrice': false, + 'fetchMarkPrices': false, + 'fetchMyLiquidations': false, + 'fetchMySettlementHistory': false, + 'fetchMyTrades': true, + 'fetchOHLCV': false, + 'fetchOpenInterest': false, + 'fetchOpenInterestHistory': false, + 'fetchOpenInterests': false, + 'fetchOpenOrders': true, + 'fetchOption': false, + 'fetchOptionChain': false, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchPosition': false, + 'fetchPositionHistory': false, + 'fetchPositionMode': false, + 'fetchPositions': false, + 'fetchPositionsForSymbol': false, + 'fetchPositionsHistory': false, + 'fetchPositionsRisk': false, + 'fetchPremiumIndexOHLCV': false, + 'fetchSettlementHistory': false, + 'fetchStatus': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': false, + 'fetchTrades': true, + 'fetchTradingFee': false, + 'fetchTradingFees': false, + 'fetchTransfer': false, + 'fetchTransfers': false, + 'fetchUnderlyingAssets': false, + 'fetchVolatilityHistory': false, + 'fetchWithdrawal': false, + 'fetchWithdrawals': false, + 'reduceMargin': false, + 'repayCrossMargin': false, + 'repayIsolatedMargin': false, + 'repayMargin': false, + 'setLeverage': false, + 'setMargin': false, + 'setMarginMode': false, + 'setPositionMode': false, + 'transfer': false, + 'withdraw': false, + }, + 'urls': { + 'logo': 'https://raw.githubusercontent.com/Bonasa-Tech/manifest/main/docs/logo.png', + 'api': { + 'public': 'https://mfx-stats-mainnet.fly.dev', + // order-entry gateway; override this from constructor once deployed + 'private': 'https://mfx-stats-mainnet.fly.dev', + }, + 'www': 'https://manifest.trade/', + 'doc': [ + 'https://github.com/Bonasa-Tech/manifest', + 'https://github.com/Bonasa-Tech/manifest/blob/main/docs/WALLET_INTEGRATION.md', + ], + }, + 'api': { + 'public': { + 'get': { + 'health': 1, + 'tickers': 1, + 'metadata': 1, + 'orderbook': 1, + 'volume': 1, + 'traders': 1, + 'recentFills': 1, + 'completeFills': 1, + 'checkpoints': 1, + 'checkpointStatus': 1, + 'wrapper': 1, + 'wrappers': 1, + 'traders/debug': 1, + 'notional': 1, + 'alts': 1, + 'backfill': 1, + }, + }, + 'private': { + 'get': { + 'order': 1, + 'openOrders': 1, + 'myTrades': 1, + }, + 'post': { + 'order': 1, + }, + 'delete': { + 'order': 1, + }, + }, + }, + 'requiredCredentials': { + 'apiKey': false, + 'secret': false, + 'uid': false, + 'accountId': false, + 'login': false, + 'password': false, + 'twofa': false, + 'privateKey': false, + 'walletAddress': false, + }, + 'features': { + 'spot': { + 'sandbox': false, + 'createOrder': true, + 'createOrders': undefined, + 'fetchMyTrades': true, + 'fetchOrder': true, + 'fetchOpenOrders': true, + 'fetchOrders': undefined, + 'fetchClosedOrders': undefined, + 'fetchOHLCV': undefined, + }, + 'swap': { + 'linear': undefined, + 'inverse': undefined, + }, + 'future': { + 'linear': undefined, + 'inverse': undefined, + }, + }, + }); + } + + safeMetadata (response: any): Dict { + if (typeof response === 'string') { + try { + return this.parseJson (response); + } catch (e) { + return {}; + } + } + if ((response !== undefined) && (response !== null) && (typeof response === 'object') && !Array.isArray (response)) { + return response; + } + return {}; + } + + parseMarketFromTicker (ticker: Dict, metadata: Dict = {}): Market { + const id = this.safeString (ticker, 'ticker_id'); + const baseId = this.safeString (ticker, 'base_currency'); + const quoteId = this.safeString (ticker, 'target_currency'); + const pair = this.safeList (metadata, id, []); + const baseCodeFromMetadata = this.safeString (pair, 0); + const quoteCodeFromMetadata = this.safeString (pair, 1); + const base = (baseCodeFromMetadata !== undefined) ? baseCodeFromMetadata : this.safeCurrencyCode (baseId); + const quote = (quoteCodeFromMetadata !== undefined) ? quoteCodeFromMetadata : this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + return this.safeMarketStructure ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'baseId': baseId, + 'quote': quote, + 'quoteId': quoteId, + 'settle': undefined, + 'settleId': undefined, + 'type': 'spot', + 'spot': true, + 'margin': false, + 'swap': false, + 'future': false, + 'option': false, + 'active': true, + 'contract': false, + 'linear': undefined, + 'inverse': undefined, + 'taker': undefined, + 'maker': undefined, + 'contractSize': undefined, + 'expiry': undefined, + 'expiryDatetime': undefined, + 'strike': undefined, + 'optionType': undefined, + 'precision': { + 'amount': undefined, + 'price': undefined, + }, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'leverage': { + 'min': undefined, + 'max': undefined, + }, + }, + 'created': undefined, + 'info': ticker, + }); + } + + /** + * @method + * @name manifest#fetchStatus + * @description the latest known information on the availability of the exchange API + * @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.publicGetHealth (params); + const status = (response === 'OK') ? 'ok' : 'maintenance'; + return { + 'status': status, + 'updated': undefined, + 'eta': undefined, + 'url': undefined, + 'info': response, + }; + } + + /** + * @method + * @name manifest#fetchMarkets + * @description retrieves data on all markets for manifest + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object[]} an array of objects representing market data + */ + async fetchMarkets (params = {}): Promise { + const [ tickersResponse, metadataResponse ] = await Promise.all ([ + this.publicGetTickers (params), + this.publicGetMetadata (params), + ]); + const metadata = this.safeMetadata (metadataResponse); + const tickers = this.safeList (tickersResponse, undefined, []); + const result: Market[] = []; + for (let i = 0; i < tickers.length; i++) { + const ticker = this.safeDict (tickers, i, {}); + const market = this.parseMarketFromTicker (ticker, metadata); + result.push (market); + } + return result; + } + + /** + * @method + * @name manifest#fetchOrderBook + * @description fetches the public order book for a market + * @param {string} symbol unified market symbol + * @param {int} [limit] not used by manifest.fetchOrderBook (); exchange depth is set via params.depth or defaults to full depth + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @param {int|undefined} [params.depth] total number of levels to return (split across bids/asks by API) + * @returns {object} an [order book structure]{@link https://docs.ccxt.com/?id=order-book-structure} + */ + async fetchOrderBook (symbol: string, limit: Int = undefined, params = {}): Promise { + await this.loadMarkets (); + const market = this.market (symbol); + let depth: Int = this.safeInteger (params, 'depth'); + if (depth === undefined) { + depth = limit; + } + if (depth === undefined) { + depth = 0; // stats server expects depth=0 for full order book + } + params = this.omit (params, 'depth'); + const request: Dict = { + 'ticker_id': market['id'], + 'depth': depth, + }; + const response = await this.publicGetOrderbook (this.extend (request, params)); + const timestamp = this.safeIntegerProduct (response, 'timestamp', 1000); + return this.parseOrderBook (response, market['symbol'], timestamp, 'bids', 'asks', 0, 1); + } + + /** + * @method + * @name manifest#parseTicker + * @description parses a ticker object + * @param {object} ticker the raw ticker object from the exchange + * @param {object} [market] a market object + * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/?id=ticker-structure} + */ + parseTicker (ticker: Dict, market: Market = undefined): Ticker { + const marketId = this.safeString (ticker, 'ticker_id'); + const symbol = this.safeSymbol (marketId, market); + const last = this.safeString (ticker, 'last_price'); + const baseVolume = this.safeString (ticker, 'base_volume'); + const quoteVolume = this.safeString (ticker, 'target_volume'); + let vwap: Str = undefined; + if ((baseVolume !== undefined) && Precise.stringGt (baseVolume, '0') && (quoteVolume !== undefined)) { + vwap = Precise.stringDiv (quoteVolume, baseVolume); + } + return this.safeTicker ({ + 'symbol': symbol, + 'timestamp': undefined, + 'datetime': undefined, + 'high': undefined, + 'low': undefined, + 'bid': this.safeString (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeString (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'indexPrice': undefined, + 'markPrice': undefined, + 'info': ticker, + }, market); + } + + /** + * @method + * @name manifest#fetchTicker + * @description fetches a price ticker for a market symbol + * @param {string} symbol unified symbol of the market to fetch + * @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: string, params = {}): Promise { + await this.loadMarkets (); + const tickers = await this.fetchTickers ([ symbol ], params); + const ticker = this.safeValue (tickers, symbol); + if (ticker === undefined) { + throw new BadSymbol (this.id + ' fetchTicker() could not find market for symbol ' + symbol); + } + return ticker as Ticker; + } + + /** + * @method + * @name manifest#fetchTickers + * @description fetches price tickers for multiple symbols + * @param {string[]} [symbols] unified symbols of the markets to fetch + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object} an object of [ticker structures]{@link https://docs.ccxt.com/?id=ticker-structure} + */ + async fetchTickers (symbols: Strings = undefined, params = {}): Promise { + await this.loadMarkets (); + const response = await this.publicGetTickers (params); + const tickers = this.safeList (response, undefined, []); + const result: Tickers = {}; + symbols = this.marketSymbols (symbols); + for (let i = 0; i < tickers.length; i++) { + const rawTicker = this.safeDict (tickers, i, {}); + const marketId = this.safeString (rawTicker, 'ticker_id'); + const market = this.safeMarket (marketId, undefined, undefined, 'spot'); + const parsedTicker = this.parseTicker (rawTicker, market); + const parsedSymbol = parsedTicker['symbol']; + if (symbols !== undefined) { + if (!this.inArray (parsedSymbol, symbols)) { + continue; + } + } + result[parsedSymbol] = parsedTicker; + } + return result; + } + + /** + * @method + * @name manifest#fetchTrades + * @description get public trade history for a market from stats-server fills + * @param {string} symbol unified market symbol + * @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 {bool} [params.complete] true uses /completeFills (default), false uses /recentFills + * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/?id=public-trades} + */ + async fetchTrades (symbol: string, since: Int = undefined, limit: Int = undefined, params = {}): Promise { + await this.loadMarkets (); + const market = this.market (symbol); + const useComplete = this.safeBool (params, 'complete', true); + params = this.omit (params, 'complete'); + let trades = []; + if (useComplete) { + const request: Dict = { + 'market': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetCompleteFills (this.extend (request, params)); + trades = this.safeList (response, 'fills', []); + } else { + const request: Dict = { + 'market': market['id'], + }; + const response = await this.publicGetRecentFills (this.extend (request, params)); + trades = this.safeList (response, market['id'], []); + if ((trades.length === 0) && (response !== undefined) && (response !== null) && (typeof response === 'object') && !Array.isArray (response)) { + const markets = Object.keys (response); + if (markets.length > 0) { + const firstMarket = this.safeString (markets, 0); + trades = this.safeList (response, firstMarket, []); + } + } + } + return this.parseTrades (trades, market, since, limit); + } + + parseOrderStatus (status: Str) { + if (status === undefined) { + return status; + } + const statuses: Dict = { + 'open': 'open', + 'new': 'open', + 'accepted': 'open', + 'partially_filled': 'open', + 'pending': 'open', + 'filled': 'closed', + 'closed': 'closed', + 'canceled': 'canceled', + 'cancelled': 'canceled', + 'rejected': 'rejected', + 'expired': 'expired', + }; + const normalized = status.toLowerCase (); + return this.safeString (statuses, normalized, normalized); + } + + parseOrder (order: Dict, market: Market = undefined): Order { + const id = this.safeStringN (order, [ 'orderId', 'id' ]); + const clientOrderId = this.safeStringN (order, [ 'clientOrderId', 'client_order_id' ]); + const marketId = this.safeStringN (order, [ 'marketId', 'market', 'symbol', 'ticker_id' ]); + market = this.safeMarket (marketId, market, undefined, 'spot'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.safeTimestamp2 (order, 'timestamp', 'createdAt'); + const lastUpdateTimestamp = this.safeTimestamp2 (order, 'lastUpdateTimestamp', 'updatedAt'); + const amount = this.safeStringN (order, [ 'amount', 'origQty', 'size', 'quantity' ]); + const filled = this.safeStringN (order, [ 'filled', 'filledQty', 'executedQty' ]); + let remaining = this.safeString (order, 'remaining'); + if ((remaining === undefined) && (amount !== undefined) && (filled !== undefined)) { + remaining = Precise.stringSub (amount, filled); + } + return this.safeOrder ({ + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': this.safeTimestamp (order, 'lastTradeTimestamp'), + 'lastUpdateTimestamp': lastUpdateTimestamp, + 'status': this.parseOrderStatus (this.safeString (order, 'status')), + 'symbol': symbol, + 'type': this.safeStringLower (order, 'type'), + 'timeInForce': this.safeStringLower (order, 'timeInForce'), + 'postOnly': this.safeBool (order, 'postOnly'), + 'reduceOnly': this.safeBool (order, 'reduceOnly'), + 'side': this.safeStringLower (order, 'side'), + 'price': this.safeStringN (order, [ 'price', 'limitPrice' ]), + 'triggerPrice': this.safeStringN (order, [ 'triggerPrice', 'stopPrice' ]), + 'takeProfitPrice': this.safeString (order, 'takeProfitPrice'), + 'stopLossPrice': this.safeString (order, 'stopLossPrice'), + 'average': this.safeStringN (order, [ 'average', 'avgPrice' ]), + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'cost': this.safeStringN (order, [ 'cost', 'cumulativeQuote', 'quoteAmount' ]), + 'trades': undefined, + 'info': order, + }, market); + } + + parseTrade (trade: Dict, market: Market = undefined): Trade { + const marketId = this.safeStringN (trade, [ 'marketId', 'market', 'symbol', 'ticker_id' ]); + market = this.safeMarket (marketId, market, undefined, 'spot'); + const symbol = this.safeSymbol (marketId, market); + let timestamp = this.safeTimestamp2 (trade, 'timestamp', 'executedAt'); + if (timestamp === undefined) { + timestamp = this.safeIntegerProduct (trade, 'blockTime', 1000); + } + let side = this.safeStringLower (trade, 'side'); + const takerIsBuy = this.safeBool (trade, 'takerIsBuy'); + if ((side === undefined) && (takerIsBuy !== undefined)) { + side = takerIsBuy ? 'buy' : 'sell'; + } + const takerOrMakerRaw = this.safeStringN (trade, [ 'takerOrMaker', 'liquidity', 'role' ]); + const takerOrMaker = (takerOrMakerRaw === undefined) ? undefined : takerOrMakerRaw.toLowerCase (); + const feeCost = this.safeStringN (trade, [ 'fee', 'feeCost' ]); + const feeCurrency = this.safeCurrencyCode (this.safeStringN (trade, [ 'feeCurrency', 'feeAsset' ])); + let id = this.safeStringN (trade, [ 'tradeId', 'fillId', 'id' ]); + if (id === undefined) { + const signature = this.safeString (trade, 'signature'); + const takerSequenceNumber = this.safeString (trade, 'takerSequenceNumber'); + const makerSequenceNumber = this.safeString (trade, 'makerSequenceNumber'); + if ((signature !== undefined) && (takerSequenceNumber !== undefined) && (makerSequenceNumber !== undefined)) { + id = signature + ':' + takerSequenceNumber + ':' + makerSequenceNumber; + } + } + const price = this.safeStringN (trade, [ 'price', 'priceAtoms', 'px' ]); + const amount = this.safeStringN (trade, [ 'amount', 'size', 'qty', 'quantity', 'baseAtoms', 'sz' ]); + const cost = this.safeStringN (trade, [ 'cost', 'quoteAmount', 'quoteAtoms' ]); + let fee: Dict = undefined; + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + return this.safeTrade ({ + 'id': id, + 'order': this.safeStringN (trade, [ 'orderId', 'order' ]), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': this.safeStringLower (trade, 'type'), + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + 'info': trade, + }, market); + } + + /** + * @method + * @name manifest#createOrder + * @description create a trade order + * @param {string} symbol unified symbol of the market to create an order in + * @param {string} type 'limit' or 'market' + * @param {string} side 'buy' or 'sell' + * @param {float} amount how much of currency you want to trade in units of base currency + * @param {float} [price] the price to fulfill this order at + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object} an [order structure]{@link https://docs.ccxt.com/?id=order-structure} + */ + async createOrder (symbol: string, type: OrderType, side: OrderSide, amount: number, price: Num = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if ((type === 'limit') && (price === undefined)) { + throw new ArgumentsRequired (this.id + ' createOrder() requires a price argument for limit orders'); + } + const request: Dict = { + 'marketId': market['id'], + 'type': type, + 'side': side, + 'amount': this.amountToPrecision (symbol, amount), + }; + if (price !== undefined) { + request['price'] = this.priceToPrecision (symbol, price); + } + const clientOrderId = this.safeStringN (params, [ 'clientOrderId', 'client_order_id' ]); + if (clientOrderId !== undefined) { + request['clientOrderId'] = clientOrderId; + } + const timeInForce = this.safeStringUpper (params, 'timeInForce'); + if (timeInForce !== undefined) { + request['timeInForce'] = timeInForce; + } + params = this.omit (params, [ 'clientOrderId', 'client_order_id', 'timeInForce' ]); + const response = await this.privatePostOrder (this.extend (request, params)); + let order = this.safeDict (response, 'order'); + if (Object.keys (order).length === 0) { + order = this.safeDict (response, 'data'); + } + if (Object.keys (order).length === 0) { + order = this.safeDict (response, 'result'); + } + if (Object.keys (order).length === 0) { + order = response; + } + return this.parseOrder (order, market); + } + + /** + * @method + * @name manifest#cancelOrder + * @description cancels an open order + * @param {string} id order id + * @param {string} [symbol] unified symbol of the market the order was made in + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object} an [order structure]{@link https://docs.ccxt.com/?id=order-structure} + */ + async cancelOrder (id: string, symbol: Str = undefined, params = {}) { + await this.loadMarkets (); + const request: Dict = { + 'orderId': id, + }; + let market: Market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['marketId'] = market['id']; + } + const response = await this.privateDeleteOrder (this.extend (request, params)); + let order = this.safeDict (response, 'order'); + if (Object.keys (order).length === 0) { + order = this.safeDict (response, 'data'); + } + if (Object.keys (order).length === 0) { + order = { + 'orderId': id, + 'status': 'canceled', + 'marketId': this.safeString (market, 'id'), + 'timestamp': this.milliseconds (), + 'raw': response, + }; + } + return this.parseOrder (order, market); + } + + /** + * @method + * @name manifest#fetchOrder + * @description fetches information on an order made by the user + * @param {string} id order id + * @param {string} [symbol] unified market symbol + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object} an [order structure]{@link https://docs.ccxt.com/?id=order-structure} + */ + async fetchOrder (id: string, symbol: Str = undefined, params = {}) { + await this.loadMarkets (); + const request: Dict = { + 'orderId': id, + }; + let market: Market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['marketId'] = market['id']; + } + const response = await this.privateGetOrder (this.extend (request, params)); + let order = this.safeDict (response, 'order'); + if (Object.keys (order).length === 0) { + order = this.safeDict (response, 'data'); + } + if (Object.keys (order).length === 0) { + order = this.safeDict (response, 'result'); + } + if (Object.keys (order).length === 0) { + order = response; + } + return this.parseOrder (order, market); + } + + /** + * @method + * @name manifest#fetchOpenOrders + * @description fetch all unfilled currently open orders + * @param {string} [symbol] unified market symbol + * @param {int} [since] the earliest time in ms to fetch open orders for + * @param {int} [limit] the maximum number of open order structures to retrieve + * @param {object} [params] extra parameters specific to the exchange API endpoint + * @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/?id=order-structure} + */ + async fetchOpenOrders (symbol: Str = undefined, since: Int = undefined, limit: Int = undefined, params = {}): Promise { + await this.loadMarkets (); + const request: Dict = {}; + let market: Market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['marketId'] = market['id']; + } + if (since !== undefined) { + request['since'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetOpenOrders (this.extend (request, params)); + let orders = this.safeValue (response, 'orders'); + if (!Array.isArray (orders)) { + orders = this.safeValue (response, 'data'); + } + if (!Array.isArray (orders)) { + orders = this.safeValue (response, 'result'); + } + if (!Array.isArray (orders)) { + orders = []; + } + return this.parseOrders (orders, market, since, limit); + } + + /** + * @method + * @name manifest#fetchMyTrades + * @description fetch all trades made by the user + * @param {string} [symbol] unified market symbol + * @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 + * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/?id=trade-structure} + */ + async fetchMyTrades (symbol: Str = undefined, since: Int = undefined, limit: Int = undefined, params = {}): Promise { + await this.loadMarkets (); + const request: Dict = {}; + let market: Market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['marketId'] = market['id']; + } + if (since !== undefined) { + request['since'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetMyTrades (this.extend (request, params)); + let trades = this.safeValue (response, 'trades'); + if (!Array.isArray (trades)) { + trades = this.safeValue (response, 'data'); + } + if (!Array.isArray (trades)) { + trades = this.safeValue (response, 'result'); + } + if (!Array.isArray (trades)) { + trades = []; + } + return this.parseTrades (trades, market, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + const isDelete = method === 'DELETE'; + const isGet = method === 'GET'; + if (isGet || isDelete) { + if (Object.keys (query).length > 0) { + url += '?' + this.urlencode (query); + } + } else if (Object.keys (query).length > 0) { + body = this.json (query); + } + if (api === 'private') { + headers = { + 'Accept': 'application/json', + }; + if (!isGet && !isDelete) { + headers['Content-Type'] = 'application/json'; + } + if (this.apiKey !== undefined) { + headers['Authorization'] = 'Bearer ' + this.apiKey; + } + if (this.secret !== undefined) { + headers['X-Manifest-Secret'] = this.secret; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code: int, reason: string, url: string, method: string, headers: Dict, body: string, response, requestHeaders, requestBody) { + if (response === undefined) { + return undefined; + } + const success = this.safeBool (response, 'success'); + const error = this.safeStringN (response, [ 'error', 'message' ]); + if ((success === false) || (error !== undefined)) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], error, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback); + throw new ExchangeError (feedback); + } + return undefined; + } +}