diff --git a/src/handlers/swap_handlers.ts b/src/handlers/swap_handlers.ts index 3fbe2d6193..3e39748e70 100644 --- a/src/handlers/swap_handlers.ts +++ b/src/handlers/swap_handlers.ts @@ -92,8 +92,8 @@ export class SwapHandlers { res.status(HttpStatus.OK).send({ tokens: filteredTokens }); } // tslint:disable-next-line:prefer-function-over-method - public async getTokenPricesAsync(_req: express.Request, res: express.Response): Promise { - const prices = await this._swapService.getTokenPricesAsync(); + public async getTokenPricesAsync(req: express.Request, res: express.Response): Promise { + const prices = await this._swapService.getTokenPricesAsync(req.query.sellToken || 'WETH'); res.status(HttpStatus.OK).send({ prices }); } } diff --git a/src/services/orderbook_service.ts b/src/services/orderbook_service.ts index 534530ff3a..1854af304a 100644 --- a/src/services/orderbook_service.ts +++ b/src/services/orderbook_service.ts @@ -3,7 +3,7 @@ import { WSClient } from '@0x/mesh-rpc-client'; import { assetDataUtils } from '@0x/order-utils'; import { AssetPairsItem, OrdersRequestOpts, SignedOrder } from '@0x/types'; import * as _ from 'lodash'; -import { Connection } from 'typeorm'; +import { Connection, In } from 'typeorm'; import { SignedOrderEntity } from '../entities'; import { ValidationError } from '../errors'; @@ -157,6 +157,23 @@ export class OrderBookService { const paginatedApiOrders = paginationUtils.paginate(apiOrders, page, perPage); return paginatedApiOrders; } + public async getBatchOrdersAsync( + page: number, + perPage: number, + makerAssetDatas: string[], + takerAssetDatas: string[], + ): Promise> { + const filterObject = { + makerAssetData: In(makerAssetDatas), + takerAssetData: In(takerAssetDatas), + }; + const signedOrderEntities = (await this._connection.manager.find(SignedOrderEntity, { + where: filterObject, + })) as Array>; + const apiOrders = _.map(signedOrderEntities, orderUtils.deserializeOrderToAPIOrder); + const paginatedApiOrders = paginationUtils.paginate(apiOrders, page, perPage); + return paginatedApiOrders; + } constructor(connection: Connection, meshClient?: WSClient) { this._meshClient = meshClient; this._connection = connection; diff --git a/src/services/swap_service.ts b/src/services/swap_service.ts index 81c2af59ca..2f17b1c948 100644 --- a/src/services/swap_service.ts +++ b/src/services/swap_service.ts @@ -18,7 +18,7 @@ import { logger } from '../logger'; import { TokenMetadatasForChains } from '../token_metadatas_for_networks'; import { CalculateSwapQuoteParams, GetSwapQuoteResponse } from '../types'; import { orderUtils } from '../utils/order_utils'; -import { findTokenDecimalsIfExists } from '../utils/token_metadata_utils'; +import { findTokenDecimalsIfExists, getTokenMetadataIfExists } from '../utils/token_metadata_utils'; export class SwapService { private readonly _provider: SupportedProvider; @@ -48,9 +48,9 @@ export class SwapService { from, } = params; const assetSwapperOpts = { + ...ASSET_SWAPPER_MARKET_ORDERS_OPTS, slippagePercentage, gasPrice: providedGasPrice, - ...ASSET_SWAPPER_MARKET_ORDERS_OPTS, }; if (sellAmount !== undefined) { swapQuote = await this._swapQuoter.getMarketSellSwapQuoteAsync( @@ -104,9 +104,7 @@ export class SwapService { const buyTokenDecimals = await this._fetchTokenDecimalsIfRequiredAsync(buyTokenAddress); const sellTokenDecimals = await this._fetchTokenDecimalsIfRequiredAsync(sellTokenAddress); - const price = Web3Wrapper.toUnitAmount(makerAssetAmount, buyTokenDecimals) - .dividedBy(Web3Wrapper.toUnitAmount(totalTakerAssetAmount, sellTokenDecimals)) - .decimalPlaces(sellTokenDecimals); + const price = calculatePrice(makerAssetAmount, buyTokenDecimals, totalTakerAssetAmount, sellTokenDecimals); const apiSwapQuote: GetSwapQuoteResponse = { price, @@ -124,32 +122,40 @@ export class SwapService { return apiSwapQuote; } - public async getTokenPricesAsync(): Promise> { - const baseAssetSymbol = 'WETH'; - const unitAmount = new BigNumber(1); - const baseAsset = TokenMetadatasForChains.find(m => m.symbol === baseAssetSymbol); + public async getTokenPricesAsync(baseAssetSymbol: string): Promise> { + const baseAsset = getTokenMetadataIfExists(baseAssetSymbol, CHAIN_ID); if (!baseAsset) { throw new Error('Invalid Base Asset'); } - const takerAssetData = assetDataUtils.encodeERC20AssetData(baseAsset.tokenAddresses[CHAIN_ID]); // WETH + const unitAmount = new BigNumber(1); + const takerAssetData = assetDataUtils.encodeERC20AssetData(baseAsset.tokenAddress); const queryAssetData = TokenMetadatasForChains.filter(m => m.symbol !== baseAssetSymbol); - const tokenMetadataChunks = _.chunk(queryAssetData, 10); + const chunkSize = 10; + const assetDataChunks = _.chunk(queryAssetData, chunkSize); const allResults = _.flatten( await Promise.all( - tokenMetadataChunks.map(async chunk => { - const makerAssetDatas = chunk.map(m => + assetDataChunks.map(async a => { + const encodedAssetData = a.map(m => assetDataUtils.encodeERC20AssetData(m.tokenAddresses[CHAIN_ID]), ); - const amounts = chunk.map(m => Web3Wrapper.toBaseUnitAmount(unitAmount, m.decimals)); - return this._swapQuoter.getMultipleMarketBuySwapQuoteForAssetDataAsync( - makerAssetDatas, + const amounts = a.map(m => Web3Wrapper.toBaseUnitAmount(unitAmount, m.decimals)); + const quotes = await this._swapQuoter.getBatchMarketBuySwapQuoteForAssetDataAsync( + encodedAssetData, takerAssetData, amounts, - { slippagePercentage: 0 }, + { + ...ASSET_SWAPPER_MARKET_ORDERS_OPTS, + slippagePercentage: 0, + bridgeSlippage: 0, + numSamples: 1, + runLimit: 1, + }, ); + return quotes; }), ), ); + const prices = allResults.map((quote, i) => { if (!quote) { return { symbol: queryAssetData[i].symbol, price: ZERO }; @@ -157,9 +163,7 @@ export class SwapService { const buyTokenDecimals = queryAssetData[i].decimals; const sellTokenDecimals = baseAsset.decimals; const { makerAssetAmount, totalTakerAssetAmount } = quote.bestCaseQuoteInfo; - const makerAssetUnitAmount = Web3Wrapper.toUnitAmount(makerAssetAmount, buyTokenDecimals); - const takerAssetUnitAmount = Web3Wrapper.toUnitAmount(totalTakerAssetAmount, sellTokenDecimals); - const price = makerAssetUnitAmount.dividedBy(takerAssetUnitAmount).decimalPlaces(sellTokenDecimals); + const price = calculatePrice(makerAssetAmount, buyTokenDecimals, totalTakerAssetAmount, sellTokenDecimals); return { symbol: queryAssetData[i].symbol, price, @@ -255,6 +259,18 @@ export class SwapService { } } +const calculatePrice = ( + makerAssetAmount: BigNumber, + makerAssetDecimals: number, + takerAssetAmount: BigNumber, + takerAssetDecimals: number, +): BigNumber => { + const makerAssetUnitAmount = Web3Wrapper.toUnitAmount(makerAssetAmount, makerAssetDecimals); + const takerAssetUnitAmount = Web3Wrapper.toUnitAmount(takerAssetAmount, takerAssetDecimals); + const price = makerAssetUnitAmount.dividedBy(takerAssetUnitAmount).decimalPlaces(takerAssetDecimals); + return price; +}; + const throwIfRevertError = (result: string): void => { let revertError; try { diff --git a/src/token_metadatas_for_networks.ts b/src/token_metadatas_for_networks.ts index ce9ffe8515..c5aa2c6d6a 100644 --- a/src/token_metadatas_for_networks.ts +++ b/src/token_metadatas_for_networks.ts @@ -16,22 +16,22 @@ export interface TokenMetadataAndChainAddresses { // tslint:disable:max-file-line-count export const TokenMetadatasForChains: TokenMetadataAndChainAddresses[] = [ { - symbol: 'DAI', - name: 'Dai Stablecoin', + symbol: 'ZRX', + name: '0x Protocol Token', decimals: 18, tokenAddresses: { - [ChainId.Mainnet]: '0x6b175474e89094c44da98b954eedeac495271d0f', - [ChainId.Kovan]: '0xc4375b7de8af5a38a93548eb8453a498222c4ff2', - [ChainId.Ganache]: '0x34d402f14d58e001d8efbe6585051bf9706aa064', + [ChainId.Mainnet]: '0xe41d2489571d322189246dafa5ebde1f4699f498', + [ChainId.Kovan]: '0x2002d3812f58e35f0ea1ffbf80a75a38c32175fa', + [ChainId.Ganache]: '0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c', }, }, { - symbol: 'REP', - name: 'Augur Reputation', + symbol: 'DAI', + name: 'Dai Stablecoin', decimals: 18, tokenAddresses: { - [ChainId.Mainnet]: '0x1985365e9f78359a9B6AD760e32412f4a445E862', - [ChainId.Kovan]: '0xb18845c260f680d5b9d84649638813e342e4f8c9', + [ChainId.Mainnet]: '0x6b175474e89094c44da98b954eedeac495271d0f', + [ChainId.Kovan]: '0xc4375b7de8af5a38a93548eb8453a498222c4ff2', [ChainId.Ganache]: '0x34d402f14d58e001d8efbe6585051bf9706aa064', }, }, @@ -45,16 +45,6 @@ export const TokenMetadatasForChains: TokenMetadataAndChainAddresses[] = [ [ChainId.Ganache]: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082', }, }, - { - symbol: 'ZRX', - name: '0x Protocol Token', - decimals: 18, - tokenAddresses: { - [ChainId.Mainnet]: '0xe41d2489571d322189246dafa5ebde1f4699f498', - [ChainId.Kovan]: '0x2002d3812f58e35f0ea1ffbf80a75a38c32175fa', - [ChainId.Ganache]: '0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c', - }, - }, { symbol: 'USDC', name: 'USD Coin', @@ -65,6 +55,16 @@ export const TokenMetadatasForChains: TokenMetadataAndChainAddresses[] = [ [ChainId.Ganache]: NULL_ADDRESS, }, }, + { + symbol: 'REP', + name: 'Augur Reputation', + decimals: 18, + tokenAddresses: { + [ChainId.Mainnet]: '0x1985365e9f78359a9B6AD760e32412f4a445E862', + [ChainId.Kovan]: '0xb18845c260f680d5b9d84649638813e342e4f8c9', + [ChainId.Ganache]: '0x34d402f14d58e001d8efbe6585051bf9706aa064', + }, + }, { symbol: 'BAT', name: 'Basic Attention Token', @@ -95,26 +95,7 @@ export const TokenMetadatasForChains: TokenMetadataAndChainAddresses[] = [ [ChainId.Ganache]: NULL_ADDRESS, }, }, - { - decimals: 18, - symbol: 'SNX', - name: 'Synthetix Network Token', - tokenAddresses: { - [ChainId.Mainnet]: '0xc011a72400e58ecd99ee497cf89e3775d4bd732f', - [ChainId.Kovan]: NULL_ADDRESS, - [ChainId.Ganache]: NULL_ADDRESS, - }, - }, - { - decimals: 18, - symbol: 'SUSD', - name: 'sUSD', - tokenAddresses: { - [ChainId.Mainnet]: '0x57ab1e02fee23774580c119740129eac7081e9d3', - [ChainId.Kovan]: NULL_ADDRESS, - [ChainId.Ganache]: NULL_ADDRESS, - }, - }, + { decimals: 18, symbol: 'KNC', @@ -165,16 +146,6 @@ export const TokenMetadatasForChains: TokenMetadataAndChainAddresses[] = [ [ChainId.Ganache]: NULL_ADDRESS, }, }, - { - decimals: 18, - symbol: 'GNT', - name: 'Golem Network Token', - tokenAddresses: { - [ChainId.Mainnet]: '0xa74476443119a942de498590fe1f2454d7d4ac0d', - [ChainId.Kovan]: NULL_ADDRESS, - [ChainId.Ganache]: NULL_ADDRESS, - }, - }, { decimals: 18, symbol: 'OMG', @@ -245,16 +216,6 @@ export const TokenMetadatasForChains: TokenMetadataAndChainAddresses[] = [ [ChainId.Ganache]: NULL_ADDRESS, }, }, - { - decimals: 8, - symbol: 'AION', - name: 'Aion Network', - tokenAddresses: { - [ChainId.Mainnet]: '0x4ceda7906a5ed2179785cd3a40a69ee8bc99c466', - [ChainId.Kovan]: NULL_ADDRESS, - [ChainId.Ganache]: NULL_ADDRESS, - }, - }, { decimals: 18, symbol: 'GEN', @@ -300,7 +261,7 @@ export const TokenMetadatasForChains: TokenMetadataAndChainAddresses[] = [ symbol: 'MLN', name: 'Melon', tokenAddresses: { - [ChainId.Mainnet]: '0xbeb9ef514a379b997e0798fdcc901ee474b6d9a1', + [ChainId.Mainnet]: '0xec67005c4e498ec7f55e092bd1d35cbc47c91892', [ChainId.Kovan]: NULL_ADDRESS, [ChainId.Ganache]: NULL_ADDRESS, }, @@ -335,16 +296,6 @@ export const TokenMetadatasForChains: TokenMetadataAndChainAddresses[] = [ [ChainId.Ganache]: NULL_ADDRESS, }, }, - { - decimals: 18, - symbol: 'ICN', - name: 'ICONOMI', - tokenAddresses: { - [ChainId.Mainnet]: '0x888666ca69e0f178ded6d75b5726cee99a87d698', - [ChainId.Kovan]: NULL_ADDRESS, - [ChainId.Ganache]: NULL_ADDRESS, - }, - }, { decimals: 9, symbol: 'DGD', @@ -377,7 +328,7 @@ export const TokenMetadatasForChains: TokenMetadataAndChainAddresses[] = [ }, { decimals: 8, - symbol: 'cDAI', + symbol: 'cSAI', name: 'Compound Dai', tokenAddresses: { [ChainId.Mainnet]: '0x5d3a536e4d6dbd6114cc1ead35777bab948e3643', @@ -405,16 +356,6 @@ export const TokenMetadatasForChains: TokenMetadataAndChainAddresses[] = [ [ChainId.Ganache]: NULL_ADDRESS, }, }, - { - decimals: 8, - symbol: 'cREP', - name: 'Compound Augur', - tokenAddresses: { - [ChainId.Mainnet]: '0x158079ee67fce2f58472a96584a73c7ab9ac95c1', - [ChainId.Kovan]: NULL_ADDRESS, - [ChainId.Ganache]: NULL_ADDRESS, - }, - }, { decimals: 8, symbol: 'cUSDC', @@ -485,16 +426,6 @@ export const TokenMetadatasForChains: TokenMetadataAndChainAddresses[] = [ [ChainId.Ganache]: NULL_ADDRESS, }, }, - { - decimals: 18, - symbol: 'ICX', - name: 'ICON', - tokenAddresses: { - [ChainId.Mainnet]: '0xb5a5f22694352c15b00323844ad545abb2b11028', - [ChainId.Kovan]: NULL_ADDRESS, - [ChainId.Ganache]: NULL_ADDRESS, - }, - }, { decimals: 18, symbol: 'NMR', @@ -625,4 +556,44 @@ export const TokenMetadatasForChains: TokenMetadataAndChainAddresses[] = [ [ChainId.Ganache]: NULL_ADDRESS, }, }, + { + decimals: 18, + symbol: 'USDT', + name: 'Tether USD', + tokenAddresses: { + [ChainId.Mainnet]: '0xdac17f958d2ee523a2206206994597c13d831ec7', + [ChainId.Kovan]: NULL_ADDRESS, + [ChainId.Ganache]: NULL_ADDRESS, + }, + }, + { + decimals: 18, + symbol: 'SNX', + name: 'Synthetix Network Token', + tokenAddresses: { + [ChainId.Mainnet]: '0xc011a72400e58ecd99ee497cf89e3775d4bd732f', + [ChainId.Kovan]: NULL_ADDRESS, + [ChainId.Ganache]: NULL_ADDRESS, + }, + }, + { + decimals: 18, + symbol: 'SETH', + name: 'sETH', + tokenAddresses: { + [ChainId.Mainnet]: '0x5e74c9036fb86bd7ecdcb084a0673efc32ea31cb', + [ChainId.Kovan]: NULL_ADDRESS, + [ChainId.Ganache]: NULL_ADDRESS, + }, + }, + { + decimals: 18, + symbol: 'SUSD', + name: 'sUSD', + tokenAddresses: { + [ChainId.Mainnet]: '0x57ab1ec28d129707052df4df418d58a2d46d5f51', + [ChainId.Kovan]: NULL_ADDRESS, + [ChainId.Ganache]: NULL_ADDRESS, + }, + }, ]; diff --git a/src/utils/order_store_db_adapter.ts b/src/utils/order_store_db_adapter.ts index fe15480629..d0df7b27d3 100644 --- a/src/utils/order_store_db_adapter.ts +++ b/src/utils/order_store_db_adapter.ts @@ -33,20 +33,30 @@ export class OrderStoreDbAdapter extends OrderStore { } // Currently not handling deletes as this is handled by Mesh } + public async getBatchOrderSetsForAssetsAsync( + makerAssetDatas: string[], + takerAssetDatas: string[], + ): Promise { + const { records: apiOrders } = await this._orderbookService.getBatchOrdersAsync( + FIRST_PAGE, + MAX_QUERY_SIZE, + makerAssetDatas, + takerAssetDatas, + ); + const orderSets: { [makerAssetData: string]: OrderSet } = {}; + makerAssetDatas.forEach(m => + takerAssetDatas.forEach(t => (orderSets[OrderStore.getKeyForAssetPair(m, t)] = new OrderSet())), + ); + await Promise.all( + apiOrders.map(async o => + orderSets[OrderStore.getKeyForAssetPair(o.order.makerAssetData, o.order.takerAssetData)].addAsync(o), + ), + ); + return Object.values(orderSets); + } // tslint:disable-next-line:prefer-function-over-method public async hasAsync(_assetPairKey: string): Promise { return true; - // const [assetA, assetB] = OrderStore.assetPairKeyToAssets(assetPairKey); - // const { bids, asks } = await this._orderbookService.getOrderBookAsync( - // FIRST_PAGE, - // MAX_QUERY_SIZE, - // assetA, - // assetB, - // ); - // return bids.total !== 0 || asks.total !== 0; - // const [assetA, assetB] = OrderStore.assetPairKeyToAssets(assetPairKey); - // const pairs = await this._orderbookService.getAssetPairsAsync(FIRST_PAGE, MAX_QUERY_SIZE, assetA, assetB); - // return pairs.total !== 0; } public async valuesAsync(assetPairKey: string): Promise { return Array.from((await this.getOrderSetForAssetPairAsync(assetPairKey)).values());