UNPKG

preidman-ccxt

Version:

A JavaScript / Python / PHP cryptocurrency trading library with support for 100+ exchanges

1,021 lines (1,000 loc) 62.5 kB
'use strict'; const Exchange = require ('./base/Exchange'); const { ExchangeError, ExchangeNotAvailable, ArgumentsRequired, BadRequest, AuthenticationError, InvalidOrder, OrderNotFound, NotSupported, OrderImmediatelyFillable, OrderNotFillable, InvalidAddress, InsufficientFunds } = require ('./base/errors'); module.exports = class theocean extends Exchange { describe () { this.checkRequiredDependencies (); return this.deepExtend (super.describe (), { 'id': 'theocean', 'name': 'The Ocean', 'countries': [ 'US' ], 'rateLimit': 3000, 'version': 'v0', 'certified': true, 'requiresWeb3': true, // add GET https://api.staging.theocean.trade/api/v0/candlesticks/intervals to fetchMarkets 'timeframes': { '5m': '300', '15m': '900', '1h': '3600', '6h': '21600', '1d': '86400', }, 'has': { 'CORS': false, // ? 'fetchTickers': true, 'fetchOHLCV': false, 'fetchOrder': true, 'fetchOrders': true, 'fetchOpenOrders': true, 'fetchClosedOrders': true, }, 'urls': { 'logo': 'https://user-images.githubusercontent.com/1294454/43103756-d56613ce-8ed7-11e8-924e-68f9d4bcacab.jpg', 'api': 'https://api.theocean.trade/api', 'www': 'https://theocean.trade', 'doc': 'https://docs.theocean.trade', 'fees': 'https://theocean.trade/fees', }, 'api': { 'public': { 'get': [ 'fee_components', 'token_pairs', 'ticker', 'tickers', 'candlesticks', 'candlesticks/intervals', 'trade_history', 'order_book', 'order/{orderHash}', ], }, 'private': { 'get': [ 'balance', 'available_balance', 'user_history', ], 'post': [ 'limit_order/reserve', 'limit_order/place', 'market_order/reserve', 'market_order/place', ], 'delete': [ 'order/{orderHash}', 'orders', ], }, }, 'exceptions': { 'exact': { // "Schema validation failed for 'query'": ExchangeError, // { "message": "Schema validation failed for 'query'", "errors": ... } // "Logic validation failed for 'query'": ExchangeError, // { "message": "Logic validation failed for 'query'", "errors": ... } // "Schema validation failed for 'body'": ExchangeError, // { "message": "Schema validation failed for 'body'", "errors": ... } // "Logic validation failed for 'body'": ExchangeError, // { "message": "Logic validation failed for 'body'", "errors": ... } 'Order not found': OrderNotFound, // {"message":"Order not found","errors":...} }, 'broad': { "Price can't exceed 8 digits in precision.": InvalidOrder, // {"message":"Price can't exceed 8 digits in precision.","type":"paramPrice"} 'Order cannot be canceled': InvalidOrder, // {"message":"Order cannot be canceled","type":"General error"} 'Greater than available wallet balance.': InsufficientFunds, 'Orderbook exhausted for intent': OrderNotFillable, // {"message":"Orderbook exhausted for intent MARKET_INTENT:8yjjzd8b0e8yjjzd8b0fjjzd8b0g"} 'Fillable amount under minimum': InvalidOrder, // {"message":"Fillable amount under minimum WETH trade size.","type":"paramQuoteTokenAmount"} 'Fillable amount over maximum': InvalidOrder, // {"message":"Fillable amount over maximum TUSD trade size.","type":"paramQuoteTokenAmount"} "Schema validation failed for 'params'": BadRequest, // // {"message":"Schema validation failed for 'params'"} 'Service Temporarily Unavailable': ExchangeNotAvailable, }, }, 'options': { 'decimals': {}, 'fetchOrderMethod': 'fetch_order_from_history', }, }); } calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { let market = this.markets[symbol]; let key = 'quote'; let rate = market[takerOrMaker]; let cost = parseFloat (this.costToPrecision (symbol, amount * rate)); if (side === 'sell') { cost *= price; } else { key = 'base'; } return { 'type': takerOrMaker, 'currency': market[key], 'rate': rate, 'cost': cost, }; } async fetchMarkets (params = {}) { let markets = await this.publicGetTokenPairs (); // // [ // { // "baseToken": { // "address": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d", // "symbol": "ZRX", // "decimals": "18", // "minAmount": "1000000000000000000", // "maxAmount": "100000000000000000000000", // "precision": "18" // }, // "quoteToken": { // "address": "0xc00fd9820cd2898cc4c054b7bf142de637ad129a", // "symbol": "WETH", // "decimals": "18", // "minAmount": "5000000000000000", // "maxAmount": "100000000000000000000", // "precision": "18" // } // } // ] // let result = []; for (let i = 0; i < markets.length; i++) { let market = markets[i]; let baseToken = market['baseToken']; let quoteToken = market['quoteToken']; let baseId = baseToken['address']; let quoteId = quoteToken['address']; let base = baseToken['symbol']; let quote = quoteToken['symbol']; base = this.commonCurrencyCode (base); quote = this.commonCurrencyCode (quote); let symbol = base + '/' + quote; let id = baseId + '/' + quoteId; let baseDecimals = this.safeInteger (baseToken, 'decimals'); let quoteDecimals = this.safeInteger (quoteToken, 'decimals'); this.options['decimals'][base] = baseDecimals; this.options['decimals'][quote] = quoteDecimals; let precision = { 'amount': -parseInt (baseToken['precision']), 'price': -parseInt (quoteToken['precision']), }; let amountLimits = { 'min': this.fromWei (this.safeString (baseToken, 'minAmount'), 'ether', baseDecimals), 'max': this.fromWei (this.safeString (baseToken, 'maxAmount'), 'ether', baseDecimals), }; let priceLimits = { 'min': undefined, 'max': undefined, }; let costLimits = { 'min': this.fromWei (this.safeString (quoteToken, 'minAmount'), 'ether', quoteDecimals), 'max': this.fromWei (this.safeString (quoteToken, 'maxAmount'), 'ether', quoteDecimals), }; let limits = { 'amount': amountLimits, 'price': priceLimits, 'cost': costLimits, }; let active = true; result.push ({ 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'baseId': baseId, 'quoteId': quoteId, 'active': active, 'precision': precision, 'limits': limits, 'info': market, }); } return result; } parseOHLCV (ohlcv, market = undefined, timeframe = '5m', since = undefined, limit = undefined) { let baseDecimals = this.safeInteger (this.options['decimals'], market['base'], 18); return [ this.safeInteger (ohlcv, 'startTime') * 1000, this.safeFloat (ohlcv, 'open'), this.safeFloat (ohlcv, 'high'), this.safeFloat (ohlcv, 'low'), this.safeFloat (ohlcv, 'close'), this.fromWei (this.safeString (ohlcv, 'baseVolume'), 'ether', baseDecimals), // this.safeString (ohlcv, 'quoteVolume'), ]; } async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); let market = this.market (symbol); let request = { 'baseTokenAddress': market['baseId'], 'quoteTokenAddress': market['quoteId'], 'interval': this.timeframes[timeframe], // 'endTime': endTime, // (optional) Snapshot end time }; if (since === undefined) { throw new ExchangeError (this.id + ' fetchOHLCV requires a since argument'); } request['startTime'] = parseInt (since / 1000); let response = await this.publicGetCandlesticks (this.extend (request, params)); // // [ // { // "high": "100.52", // "low": "97.23", // "open": "98.45", // "close": "99.23", // "baseVolume": "2400000000000000000000", // "quoteVolume": "1200000000000000000000", // "startTime": "1512929323784" // }, // { // "high": "100.52", // "low": "97.23", // "open": "98.45", // "close": "99.23", // "volume": "2400000000000000000000", // "startTime": "1512929198980" // } // ] // return this.parseOHLCVs (response, market, timeframe, since, limit); } async fetchBalanceByCode (code, params = {}) { if (!this.walletAddress || (this.walletAddress.indexOf ('0x') !== 0)) { throw new InvalidAddress (this.id + ' fetchBalanceByCode() requires the .walletAddress to be a "0x"-prefixed hexstring like "0xbF2d65B3b2907214EEA3562f21B80f6Ed7220377"'); } await this.loadMarkets (); let currency = this.currency (code); let request = { 'walletAddress': this.walletAddress.toLowerCase (), 'tokenAddress': currency['id'], }; let response = await this.privateGetBalance (this.extend (request, params)); // // {"available":"0","committed":"0","total":"0"} // let decimals = this.safeInteger (this.options['decimals'], code, 18); let free = this.fromWei (this.safeString (response, 'available'), 'ether', decimals); let used = this.fromWei (this.safeString (response, 'committed'), 'ether', decimals); let total = this.fromWei (this.safeString (response, 'total'), 'ether', decimals); return { 'free': free, 'used': used, 'total': total, }; } async fetchBalance (params = {}) { if (!this.walletAddress || (this.walletAddress.indexOf ('0x') !== 0)) { throw new InvalidAddress (this.id + ' fetchBalance() requires the .walletAddress to be a "0x"-prefixed hexstring like "0xbF2d65B3b2907214EEA3562f21B80f6Ed7220377"'); } let codes = this.safeValue (this.options, 'fetchBalanceCurrencies'); if (codes === undefined) codes = this.safeValue (params, 'codes'); if ((codes === undefined) || (!Array.isArray (codes))) { throw new ExchangeError (this.id + ' fetchBalance() requires a `codes` parameter (an array of currency codes)'); } await this.loadMarkets (); let result = {}; for (let i = 0; i < codes.length; i++) { const code = codes[i]; result[code] = await this.fetchBalanceByCode (code); } return this.parseBalance (result); } parseBidAsk (bidask, priceKey = 0, amountKey = 1, market = undefined) { if (market === undefined) { throw new ArgumentsRequired (this.id + ' parseBidAsk requires a market argument'); } let price = parseFloat (bidask[priceKey]); let amountDecimals = this.safeInteger (this.options['decimals'], market['base'], 18); // // the following does not work with this bidask: {"orderHash":"0x8b5d8d34eded1cbf8519733401ae3ced8069089fd16d5431cb3d4b016d7788f2","price":"133.74013659","availableAmount":"4652691526891295598045.34542621578779823835103356911924523765168638519704923461215973053000214547556058831637954647252647510035865072314678676592576536328447541178082827906517347971793654011427890554542683570544867337525450220078254745116898401756810404232673589363421879924390066378804261951784","creationTimestamp":"1542743835","expirationTimestampInSec":"1545339435"} // therefore we apply a dirty string-based patch // // let amount = this.fromWei (bidask[amountKey], 'ether', amountDecimals); // let amountString = this.safeString (bidask, amountKey); let amountParts = amountString.split ('.'); let numParts = amountParts.length; if (numParts === 2) { amountString = amountParts[0]; } let amount = this.fromWei (amountString, 'ether', amountDecimals); // return [ price, amount, bidask ]; return [ price, amount ]; } parseOrderBook (orderbook, timestamp = undefined, bidsKey = 'bids', asksKey = 'asks', priceKey = 0, amountKey = 1, market = undefined) { let result = { 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'nonce': undefined, }; let sides = [ bidsKey, asksKey ]; for (let i = 0; i < sides.length; i++) { let side = sides[i]; let orders = []; let bidasks = this.safeValue (orderbook, side); for (let k = 0; k < bidasks.length; k++) { orders.push (this.parseBidAsk (bidasks[k], priceKey, amountKey, market)); } result[side] = orders; } result[bidsKey] = this.sortBy (result[bidsKey], 0, true); result[asksKey] = this.sortBy (result[asksKey], 0); return result; } async fetchOrderBook (symbol, limit = undefined, params = {}) { await this.loadMarkets (); let market = this.market (symbol); let request = { 'baseTokenAddress': market['baseId'], 'quoteTokenAddress': market['quoteId'], }; if (limit !== undefined) { request['depth'] = limit; } let response = await this.publicGetOrderBook (this.extend (request, params)); // // { // "bids": [ // { // "orderHash": "0x94629386298dee69ae63cd3e414336ae153b3f02cffb9ffc53ad71e166615618", // "price": "0.00050915", // "availableAmount": "100000000000000000000", // "creationTimestamp": "1512929327792", // "expirationTimestampInSec": "1534449466" // } // ], // "asks": [ // { // "orderHash": "0x94629386298dee69ae63cd3e414336ae153b3f02cffb9ffc53ad71e166615618", // "price": "0.00054134", // "availableAmount": "100000000000000000000", // "creationTimestamp": "1512929323784", // "expirationTimestampInSec": "1534449466" // } // ] // } // return this.parseOrderBook (response, undefined, 'bids', 'asks', 'price', 'availableAmount', market); } parseTicker (ticker, market = undefined) { // // { // "bid": "0.00050915", // "ask": "0.00054134", // "last": "0.00052718", // "volume": "3000000000000000000", // "timestamp": "1512929327792" // } // let timestamp = parseInt (this.safeFloat (ticker, 'timestamp') / 1000); let symbol = undefined; let base = undefined; if (market !== undefined) { symbol = market['symbol']; base = market['base']; } let baseDecimals = this.safeInteger (this.options['decimals'], base, 18); let baseVolume = this.fromWei (this.safeString (ticker, 'volume'), 'ether', baseDecimals); let last = this.safeFloat (ticker, 'last'); return { 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'high': undefined, 'low': undefined, 'bid': this.safeFloat (ticker, 'bid'), 'bidVolume': undefined, 'ask': this.safeFloat (ticker, 'ask'), 'askVolume': undefined, 'vwap': undefined, 'open': undefined, 'close': last, 'last': last, 'previousClose': undefined, 'change': undefined, 'percentage': this.safeFloat (ticker, 'priceChange'), 'average': undefined, 'baseVolume': baseVolume, 'quoteVolume': undefined, 'info': ticker, }; } async fetchTickers (symbols = undefined, params = {}) { await this.loadMarkets (); let tickers = await this.publicGetTickers (params); // // [{ // "baseTokenAddress": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d", // "quoteTokenAddress": "0xc00fd9820cd2898cc4c054b7bf142de637ad129a", // "ticker": { // "bid": "0.00050915", // "ask": "0.00054134", // "last": "0.00052718", // "volume": "3000000000000000000", // "timestamp": "1512929327792" // } // }] // let result = {}; for (let i = 0; i < tickers.length; i++) { let ticker = tickers[i]; let baseId = this.safeString (ticker, 'baseTokenAddress'); let quoteId = this.safeString (ticker, 'quoteTokenAddress'); let marketId = baseId + '/' + quoteId; let market = undefined; let symbol = marketId; if (marketId in this.markets_by_id) { market = this.markets_by_id[marketId]; symbol = market['symbol']; } result[symbol] = this.parseTicker (ticker['ticker'], market); } return result; } async fetchTicker (symbol, params = {}) { await this.loadMarkets (); let market = this.market (symbol); let request = { 'baseTokenAddress': market['baseId'], 'quoteTokenAddress': market['quoteId'], }; let response = await this.publicGetTicker (this.extend (request, params)); return this.parseTicker (response, market); } parseTrade (trade, market = undefined) { // // fetchTrades // // { // "id": "37212", // "transactionHash": "0x5e6e75e1aa681b51b034296f62ac19be7460411a2ad94042dd8ba637e13eac0c", // "amount": "300000000000000000", // "price": "0.00052718", // ------- they also have a "confirmed" status here ↓ ----------------- // "status": "filled", // filled | settled | failed // "lastUpdated": "1520265048996" // } // // parseOrder trades (timeline "actions", "fills") // // { action: "confirmed", // amount: "1000000000000000000", // intentID: "MARKET_INTENT:90jjw2s7gj90jjw2s7gkjjw2s7gl", // txHash: "0x043488fdc3f995bf9e632a32424e41ed126de90f8cb340a1ff006c2a74ca8336", // blockNumber: "8094822", // timestamp: "1532261686" } // let timestamp = this.safeInteger (trade, 'lastUpdated'); if (timestamp === undefined) { timestamp = this.safeInteger (trade, 'timestamp'); } if (timestamp !== undefined) { // their timestamps are in seconds, mostly timestamp = timestamp * 1000; } let price = this.safeFloat (trade, 'price'); let orderId = this.safeString (trade, 'order'); let id = this.safeString (trade, 'id'); if (id === undefined) { id = this.safeString2 (trade, 'transactionHash', 'txHash'); } let symbol = undefined; let base = undefined; if (market !== undefined) { symbol = market['symbol']; base = market['base']; } let baseDecimals = this.safeInteger (this.options['decimals'], base, 18); let amount = this.fromWei (this.safeString (trade, 'amount'), 'ether', baseDecimals); let cost = undefined; if (amount !== undefined) { if (price !== undefined) { cost = amount * price; } } let takerOrMaker = 'taker'; let fee = undefined; // let fee = this.calculateFee (symbol, type, side, amount, price, takerOrMaker); return { 'id': id, 'order': orderId, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'symbol': symbol, 'type': undefined, 'side': undefined, 'takerOrMaker': takerOrMaker, 'price': price, 'amount': amount, 'cost': cost, 'fee': fee, 'info': trade, }; } async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); let market = this.market (symbol); let request = { 'baseTokenAddress': market['baseId'], 'quoteTokenAddress': market['quoteId'], }; let response = await this.publicGetTradeHistory (this.extend (request, params)); // // [ // { // "id": "37212", // "transactionHash": "0x5e6e75e1aa681b51b034296f62ac19be7460411a2ad94042dd8ba637e13eac0c", // "amount": "300000000000000000", // "price": "0.00052718", // "status": "filled", // filled | settled | failed // "lastUpdated": "1520265048996" // } // ] // return this.parseTrades (response, market, since, limit); } async createOrder (symbol, type, side, amount, price = undefined, params = {}) { const errorMessage = this.id + ' createOrder() requires `exchange.walletAddress` and `exchange.privateKey`. The .walletAddress should be a "0x"-prefixed hexstring like "0xbF2d65B3b2907214EEA3562f21B80f6Ed7220377". The .privateKey for that wallet should be a "0x"-prefixed hexstring like "0xe4f40d465efa94c98aec1a51f574329344c772c1bce33be07fa20a56795fdd09".'; if (!this.walletAddress || (this.walletAddress.indexOf ('0x') !== 0)) { throw new InvalidAddress (errorMessage); } if (!this.privateKey || (this.privateKey.indexOf ('0x') !== 0)) { throw new InvalidAddress (errorMessage); } await this.loadMarkets (); const makerOrTaker = this.safeString (params, 'makerOrTaker'); const isMarket = (type === 'market'); const isMakerOrTakerUndefined = (makerOrTaker === undefined); const isTaker = (makerOrTaker === 'taker'); const isMaker = (makerOrTaker === 'maker'); if (isMarket && !isMakerOrTakerUndefined && !isTaker) { throw new InvalidOrder (this.id + ' createOrder() ' + type + ' order type cannot be a ' + makerOrTaker + '. The createOrder() method of ' + type + ' type can be used with taker orders only.'); } let query = this.omit (params, 'makerOrTaker'); let timestamp = this.milliseconds (); let market = this.market (symbol); let baseDecimals = this.safeInteger (this.options['decimals'], market['base'], 18); let reserveRequest = { 'walletAddress': this.walletAddress.toLowerCase (), // Your Wallet Address 'baseTokenAddress': market['baseId'], // Base token address 'quoteTokenAddress': market['quoteId'], // Quote token address 'side': side, // "buy" or "sell" 'orderAmount': this.toWei (this.amountToPrecision (symbol, amount), 'ether', baseDecimals), // Base token amount in wei 'feeOption': 'feeInNative', // Fees can be paid in native currency ("feeInNative"), or ZRX ("feeInZRX") }; if (type === 'limit') { reserveRequest['price'] = this.priceToPrecision (symbol, price); // Price denominated in quote tokens (limit orders only) } let method = 'privatePost' + this.capitalize (type) + 'Order'; let reserveMethod = method + 'Reserve'; let reserveResponse = await this[reserveMethod] (this.extend (reserveRequest, query)); // // ---- market orders ------------------------------------------------- // // let reserveResponse = // { matchingOrderID: "MARKET_INTENT:90jjw2s7gj90jjw2s7gkjjw2s7gl", // unsignedMatchingOrder: { maker: "", // taker: "0x00ba938cc0df182c25108d7bf2ee3d37bce07513", // makerTokenAddress: "0xd0a1e359811322d97991e03f863a0c30c2cf029c", // takerTokenAddress: "0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570", // makerTokenAmount: "27100000000000000", // takerTokenAmount: "874377028175459241", // makerFee: "0", // takerFee: "0", // expirationUnixTimestampSec: "1534809575", // feeRecipient: "0x88a64b5e882e5ad851bea5e7a3c8ba7c523fecbe", // salt: "3610846705800197954038657082705100176266402776121341340841167002345284333867", // exchangeContractAddress: "0x90fe2af704b34e0224bf2299c838e04d4dcf1364" } } // // ---- limit orders -------------------------------------------------- // // 1. if the order is completely fillable: // + unsignedMatchingOrder will be present // - unsignedTargetOrder will be missing // 2. if the order is partially fillable: // + unsignedMatchingOrder and // + unsignedTarget order will be present // 3. if the order is not fillable at the moment: // + unsignedTargetOrder will be present // - unsignedMatchingOrder will be missing // In other words, unsignedMatchingOrder is only present // if there is some fillable amount in the order book. // // Note: ecSignature is empty at this point and missing in the actual // response, there's no need for it here at this point anyway. // // let reserveResponse = // { unsignedTargetOrder: { maker: "", // taker: "0x00ba938cc0df182c25108d7bf2ee3d37bce07513", // makerTokenAddress: "0xd0a1e359811322d97991e03f863a0c30c2cf029c", // takerTokenAddress: "0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570", // makerTokenAmount: "2700000000000000", // takerTokenAmount: "937912044575392743", // makerFee: "0", // takerFee: "0", // expirationUnixTimestampSec: "1534813319", // feeRecipient: "0x88a64b5e882e5ad851bea5e7a3c8ba7c523fecbe", // salt: "54933934472162523007303314622614098849759889305199720392701919179357703099693", // exchangeContractAddress: "0x90fe2af704b34e0224bf2299c838e04d4dcf1364" } } // // let reserveResponse = // { // "unsignedTargetOrder": { // "exchangeContractAddress": "0x516bdc037df84d70672b2d140835833d3623e451", // "maker": "", // "taker": "0x00ba938cc0df182c25108d7bf2ee3d37bce07513", // "makerTokenAddress": "0x7cc7fdd065cfa9c7f4f6a3c1bfc6dfcb1a3177aa", // "takerTokenAddress": "0x17f15936ef3a2da5593033f84487cbe9e268f02f", // "feeRecipient": "0x88a64b5e882e5ad851bea5e7a3c8ba7c523fecbe", // "makerTokenAmount": "10000000000000000000", // "takerTokenAmount": "10000000000000000000", // "makerFee": "0", // "takerFee": "0", // "expirationUnixTimestampSec": "525600", // "salt": "37800593840622773016017857006417214310534675667008850948421364357744823963318", // "ecSignature": { // "v": 0, // "r": "", // "s": "" // } // }, // "unsignedMatchingOrder": { // "exchangeContractAddress": "0x516bdc037df84d70672b2d140835833d3623e451", // "maker": "", // "taker": "0x00ba938cc0df182c25108d7bf2ee3d37bce07513", // "makerTokenAddress": "0x7cc7fdd065cfa9c7f4f6a3c1bfc6dfcb1a3177aa", // "takerTokenAddress": "0x17f15936ef3a2da5593033f84487cbe9e268f02f", // "feeRecipient": "0x88a64b5e882e5ad851bea5e7a3c8ba7c523fecbe", // "makerTokenAmount": "10000000000000000000", // "takerTokenAmount": "10000000000000000000", // "makerFee": "0", // "takerFee": "0", // "expirationUnixTimestampSec": "525600", // "salt": "37800593840622773016017857006417214310534675667008850948421364357744823963318", // "ecSignature": { // "v": 0, // "r": "", // "s": "" // } // }, // "matchingOrderID": "MARKET_INTENT:8ajjh92s1r8ajjh92s1sjjh92s1t" // } // // -------------------------------------------------------------------- let unsignedMatchingOrder = this.safeValue (reserveResponse, 'unsignedMatchingOrder'); let unsignedTargetOrder = this.safeValue (reserveResponse, 'unsignedTargetOrder'); const isUnsignedMatchingOrderDefined = (unsignedMatchingOrder !== undefined); const isUnsignedTargetOrderDefined = (unsignedTargetOrder !== undefined); const makerAddress = { 'maker': this.walletAddress.toLowerCase (), }; let placeRequest = {}; let signedMatchingOrder = undefined; let signedTargetOrder = undefined; if (isUnsignedMatchingOrderDefined && isUnsignedTargetOrderDefined) { if (isTaker) { signedMatchingOrder = this.signZeroExOrder (this.extend (unsignedMatchingOrder, makerAddress), this.privateKey); placeRequest['signedMatchingOrder'] = signedMatchingOrder; placeRequest['matchingOrderID'] = reserveResponse['matchingOrderID']; } else if (isMaker) { signedTargetOrder = this.signZeroExOrder (this.extend (unsignedTargetOrder, makerAddress), this.privateKey); placeRequest['signedTargetOrder'] = signedTargetOrder; } else { signedMatchingOrder = this.signZeroExOrder (this.extend (unsignedMatchingOrder, makerAddress), this.privateKey); placeRequest['signedMatchingOrder'] = signedMatchingOrder; placeRequest['matchingOrderID'] = reserveResponse['matchingOrderID']; signedTargetOrder = this.signZeroExOrder (this.extend (unsignedTargetOrder, makerAddress), this.privateKey); placeRequest['signedTargetOrder'] = signedTargetOrder; } } else if (isUnsignedMatchingOrderDefined) { if (isMaker) { throw new OrderImmediatelyFillable (this.id + ' createOrder() ' + type + ' order to ' + side + ' ' + symbol + ' is not fillable as a maker order'); } else { signedMatchingOrder = this.signZeroExOrder (this.extend (unsignedMatchingOrder, makerAddress), this.privateKey); placeRequest['signedMatchingOrder'] = signedMatchingOrder; placeRequest['matchingOrderID'] = reserveResponse['matchingOrderID']; } } else if (isUnsignedTargetOrderDefined) { if (isTaker || isMarket) { throw new OrderNotFillable (this.id + ' createOrder() ' + type + ' order to ' + side + ' ' + symbol + ' is not fillable as a taker order'); } else { signedTargetOrder = this.signZeroExOrder (this.extend (unsignedTargetOrder, makerAddress), this.privateKey); placeRequest['signedTargetOrder'] = signedTargetOrder; } } else { throw new OrderNotFillable (this.id + ' ' + type + ' order to ' + side + ' ' + symbol + ' is not fillable at the moment'); } let placeMethod = method + 'Place'; let placeResponse = await this[placeMethod] (this.extend (placeRequest, query)); // // ---- market orders ------------------------------------------------- // // let placeResponse = // { matchingOrder: { transactionHash: "0x043488fdc3f995bf9e632a32424e41ed126de90f8cb340a1ff006c2a74ca8336", // amount: "1000000000000000000", // orderHash: "0xe815dc92933b68e7fc2b7102b8407ba7afb384e4080ac8d28ed42482933c5cf5" }, // parentID: "MARKET_INTENT:90jjw2s7gj90jjw2s7gkjjw2s7gl" } // // ---- limit orders ------------------------------------------------- // // let placeResponse = // { targetOrder: { amount: "1000000000000000000", // orderHash: "0x517aef1ce5027328c40204833b624f04a54c913e93cffcdd500fe9252c535251" }, // parentID: "MARKET_INTENT:90jjw50gpk90jjw50gpljjw50gpm" } // // let placeResponse = // { // "targetOrder": { // "orderHash": "0x94629386298dee69ae63cd3e414336ae153b3f02cffb9ffc53ad71e166615618", // "amount": "100000000000" // }, // "matchingOrder": { // "orderHash": "0x3d6b287c1dc79262d2391ae2ca9d050fdbbab2c8b3180e4a46f9f321a7f1d7a9", // "transactionHash": "0x5e6e75e1aa681b51b034296f62ac19be7460411a2ad94042dd8ba637e13eac0c", // "amount": "100000000000" // } // } // let matchingOrder = this.safeValue (placeResponse, 'matchingOrder'); let targetOrder = this.safeValue (placeResponse, 'targetOrder'); const orderParams = { 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'price': price, 'side': side, 'filled': 0, 'status': 'open', }; let taker = undefined; let maker = undefined; if (matchingOrder !== undefined) { matchingOrder = this.extend (signedMatchingOrder, matchingOrder); taker = this.parseOrder (matchingOrder, market); taker = this.extend (taker, { 'type': 'market', 'remaining': taker['amount'], }, orderParams); if (isTaker) return taker; } if (targetOrder !== undefined) { targetOrder = this.extend (signedTargetOrder, targetOrder); maker = this.parseOrder (targetOrder, market); maker = this.extend (maker, { 'type': 'limit', 'remaining': maker['amount'], }, orderParams); if (isMaker) return maker; } return { 'info': this.extend (reserveResponse, placeRequest, placeResponse), 'maker': maker, 'taker': taker, }; } async cancelOrder (id, symbol = undefined, params = {}) { await this.loadMarkets (); let request = { 'orderHash': id, }; let response = await this.privateDeleteOrderOrderHash (this.extend (request, params)); // // { // "canceledOrder": { // "orderHash": "0x3d6b287c1dc79262d2391ae2ca9d050fdbbab2c8b3180e4a46f9f321a7f1d7a9", // "amount": "100000000000" // } // } // let market = undefined; if (symbol !== undefined) { market = this.market (symbol); } return this.extend (this.parseOrder (response['canceledOrder'], market), { 'status': 'canceled', }); } async cancelAllOrders (symbols = undefined, params = {}) { const response = await this.privateDeleteOrders (params); // // [{ // "canceledOrder": { // "orderHash": "0x3d6b287c1dc79262d2391ae2ca9d050fdbbab2c8b3180e4a46f9f321a7f1d7a9", // "amount": "100000000000" // } // }] // return response; } parseOrderStatus (status) { let statuses = { 'placed': 'open', 'reserved': 'open', 'filled': 'closed', 'settled': 'closed', 'confirmed': 'closed', 'returned': 'open', 'canceled': 'canceled', 'pruned': 'failed', }; if (status in statuses) { return statuses[status]; } return status; } parseOrder (order, market = undefined) { // // fetchOrder, fetchOrderBook // // { // "baseTokenAddress": "0x7cc7fdd065cfa9c7f4f6a3c1bfc6dfcb1a3177aa", // "quoteTokenAddress": "0x17f15936ef3a2da5593033f84487cbe9e268f02f", // "side": "buy", // "amount": "10000000000000000000", // "price": "1.000", // "created": "1512929327792", // "expires": "1512929897118", // "zeroExOrder": { // "exchangeContractAddress": "0x516bdc037df84d70672b2d140835833d3623e451", // "maker": "0x006dc83e5b21854d4afc44c9b92a91e0349dda13", // "taker": "0x00ba938cc0df182c25108d7bf2ee3d37bce07513", // "makerTokenAddress": "0x7cc7fdd065cfa9c7f4f6a3c1bfc6dfcb1a3177aa", // "takerTokenAddress": "0x17f15936ef3a2da5593033f84487cbe9e268f02f", // "feeRecipient": "0x88a64b5e882e5ad851bea5e7a3c8ba7c523fecbe", // "makerTokenAmount": "10000000000000000000", // "takerTokenAmount": "10000000000000000000", // "makerFee": "0", // "takerFee": "0", // "expirationUnixTimestampSec": "525600", // "salt": "37800593840622773016017857006417214310534675667008850948421364357744823963318", // "orderHash": "0x94629386298dee69ae63cd3e414336ae153b3f02cffb9ffc53ad71e166615618", // "ecSignature": { // "v": 28, // "r": "0x5307b6a69e7cba8583e1de39efb93a9ae1afc11849e79d99f462e49c18c4d6e4", // "s": "0x5950e82364227ccca95c70b47375e8911a2039d3040ba0684329634ebdced160" // } // } // } // // fetchOrders // // { orderHash: "0xe815dc92933b68e7fc2b7102b8407ba7afb384e4080ac8d28ed42482933c5cf5", // baseTokenAddress: "0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570", // quoteTokenAddress: "0xd0a1e359811322d97991e03f863a0c30c2cf029c", // side: "buy", // price: "0.0271", // openAmount: "0", // reservedAmount: "0", // filledAmount: "0", // settledAmount: "0", // confirmedAmount: "1000000000000000000", // failedAmount: "0", // deadAmount: "0", // prunedAmount: "0", // feeAmount: "125622971824540759", // feeOption: "feeInNative", // parentID: "MARKET_INTENT:90jjw2s7gj90jjw2s7gkjjw2s7gl", // siblingTargetOrderHash: null, // timeline: [ { action: "filled", // amount: "1000000000000000000", // intentID: "MARKET_INTENT:90jjw2s7gj90jjw2s7gkjjw2s7gl", // txHash: null, // blockNumber: "0", // timestamp: "1532217579" }, // { action: "settled", // amount: "1000000000000000000", // intentID: "MARKET_INTENT:90jjw2s7gj90jjw2s7gkjjw2s7gl", // txHash: "0x043488fdc3f995bf9e632a32424441ed126de90f8cb340a1ff006c2a74ca8336", // blockNumber: "8094822", // timestamp: "1532261671" }, // { action: "confirmed", // amount: "1000000000000000000", // intentID: "MARKET_INTENT:90jjw2s7gj90jjw2s7gkjjw2s7gl", // txHash: "0x043488fdc3f995bf9e632a32424441ed126de90f8cb340a1ff006c2a74ca8336", // blockNumber: "8094822", // timestamp: "1532261686" } ] } // // // let zeroExOrder = this.safeValue (order, 'zeroExOrder'); let id = this.safeString (order, 'orderHash'); if ((id === undefined) && (zeroExOrder !== undefined)) { id = this.safeString (zeroExOrder, 'orderHash'); } let side = this.safeString (order, 'side'); let type = this.safeString (order, 'type'); // injected from outside let timestamp = this.safeInteger (order, 'created'); timestamp = (timestamp !== undefined) ? timestamp * 1000 : timestamp; let symbol = undefined; let baseId = this.safeString (order, 'baseTokenAddress'); let quoteId = this.safeString (order, 'quoteTokenAddress'); let marketId = undefined; if (baseId !== undefined && quoteId !== undefined) { marketId = baseId + '/' + quoteId; } market = this.safeValue (this.markets_by_id, marketId, market); let base = undefined; if (market !== undefined) { symbol = market['symbol']; base = market['base']; } let baseDecimals = this.safeInteger (this.options['decimals'], base, 18); let price = this.safeFloat (order, 'price'); let openAmount = this.fromWei (this.safeString (order, 'openAmount'), 'ether', baseDecimals); let reservedAmount = this.fromWei (this.safeString (order, 'reservedAmount'), 'ether', baseDecimals); let filledAmount = this.fromWei (this.safeString (order, 'filledAmount'), 'ether', baseDecimals); let settledAmount = this.fromWei (this.safeString (order, 'settledAmount'), 'ether', baseDecimals); let confirmedAmount = this.fromWei (this.safeString (order, 'confirmedAmount'), 'ether', baseDecimals); let failedAmount = this.fromWei (this.safeString (order, 'failedAmount'), 'ether', baseDecimals); let deadAmount = this.fromWei (this.safeString (order, 'deadAmount'), 'ether', baseDecimals); let prunedAmount = this.fromWei (this.safeString (order, 'prunedAmount'), 'ether', baseDecimals); let amount = this.fromWei (this.safeString (order, 'amount'), 'ether', baseDecimals); if (amount === undefined) { amount = this.sum (openAmount, reservedAmount, filledAmount, settledAmount, confirmedAmount, failedAmount, deadAmount, prunedAmount); } let filled = this.sum (filledAmount, settledAmount, confirmedAmount); let remaining = undefined; let lastTradeTimestamp = undefined; let timeline = this.safeValue (order, 'timeline'); let trades = undefined; let status = undefined; if (timeline !== undefined) { let numEvents = timeline.length; if (numEvents > 0) { // status = this.parseOrderStatus (this.safeString (timeline[numEvents - 1], 'action')); let timelineEventsGroupedByAction = this.groupBy (timeline, 'action'); if ('error' in timelineEventsGroupedByAction) { status = 'failed'; } if ('placed' in timelineEventsGroupedByAction) { let placeEvents = this.safeValue (timelineEventsGroupedByAction, 'placed'); if (amount === undefined) { amount = this.fromWei (this.safeString (placeEvents[0], 'amount'), 'ether', baseDecimals); } timestamp = this.safeInteger (placeEvents[0], 'timestamp'); timestamp = (timestamp !== undefined) ? timestamp * 1000 : timestamp; } else { if ('filled' in timelineEventsGroupedByAction) { timestamp = this.safeInteger (timelineEventsGroupedByAction['filled'][0], 'timestamp'); timestamp = (timestamp !== undefined) ? timestamp * 1000 : timestamp; } } if ('filled' in timelineEventsGroupedByAction) { let fillEvents = this.safeValue (timelineEventsGroupedByAction, 'filled'); let numFillEvents = fillEvents.length; if (timestamp === undefined) { timestamp = this.safeInteger (fillEvents[0], 'timestamp'); timestamp = (timestamp !== undefined) ? timestamp * 1000 : timestamp; } lastTradeTimestamp = this.safeInteger (fillEvents[numFillEvents - 1], 'timestamp'); lastTradeTimestamp = (lastTradeTimestamp !== undefined) ? lastTradeTimestamp * 1000 : lastTradeTimestamp; trades = []; for (let i = 0; i < numFillEvents; i++) { let trade = this.parseTrade (this.extend (fillEvents[i], { 'price': price, }), market); trades.push (this.extend (trade, { 'order': id, 'type': type, 'side': side, })); } } } } let cost = undefined; if (filled !== undefined) { if (remaining === undefined) { if (amount !== undefined) { remaining = amount - filled; } } if (price !== undefined) { cost = filled * price; } } let fee = undefined; let feeCost = this.safeString (order, 'feeAmount'); if (feeCost !== undefined) { let feeOption = this.safeString (order, 'feeOptio