diff --git a/packages/pipeline/migrations/1550163069315-TokenOrderBookSnapshotsAddMakerAddress.ts b/packages/pipeline/migrations/1550163069315-TokenOrderBookSnapshotsAddMakerAddress.ts new file mode 100644 index 0000000000..7eba2fab6f --- /dev/null +++ b/packages/pipeline/migrations/1550163069315-TokenOrderBookSnapshotsAddMakerAddress.ts @@ -0,0 +1,45 @@ +import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'; + +const TOKEN_ORDERBOOK_SNAPSHOT_TABLE = 'raw.token_orderbook_snapshots'; +const NEW_COLUMN_NAME = 'maker_address'; + +export class TokenOrderBookSnapshotsAddMakerAddress1550163069315 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + const snapshotTable = await queryRunner.getTable(TOKEN_ORDERBOOK_SNAPSHOT_TABLE); + if (snapshotTable) { + const newColumn = new TableColumn({ + name: NEW_COLUMN_NAME, + type: 'varchar', + isNullable: true, + }); + await queryRunner.addColumn(TOKEN_ORDERBOOK_SNAPSHOT_TABLE, newColumn); + // backfill null values + await queryRunner.query(` + UPDATE ${TOKEN_ORDERBOOK_SNAPSHOT_TABLE} + SET ${NEW_COLUMN_NAME}='unknown' + WHERE ${NEW_COLUMN_NAME} is NULL; + `); + await queryRunner.query(` + ALTER TABLE ${TOKEN_ORDERBOOK_SNAPSHOT_TABLE} + DROP CONSTRAINT "token_orderbook_snapshots_pkey", + ADD CONSTRAINT "token_orderbook_snapshots_pkey" PRIMARY KEY (observed_timestamp, source, order_type, price, base_asset_symbol, quote_asset_symbol, maker_address); + `); + } else { + throw new Error(`Could not find table with name ${TOKEN_ORDERBOOK_SNAPSHOT_TABLE}`); + } + } + + public async down(queryRunner: QueryRunner): Promise { + const snapshotTable = await queryRunner.getTable(TOKEN_ORDERBOOK_SNAPSHOT_TABLE); + if (snapshotTable) { + await queryRunner.query(` + ALTER TABLE ${TOKEN_ORDERBOOK_SNAPSHOT_TABLE} + DROP CONSTRAINT "token_orderbook_snapshots_pkey", + DROP COLUMN ${NEW_COLUMN_NAME}, + ADD CONSTRAINT "token_orderbook_snapshots_pkey" PRIMARY KEY (observed_timestamp, source, order_type, price, base_asset_symbol, quote_asset_symbol); + `); + } else { + throw new Error(`Could not find table with name ${TOKEN_ORDERBOOK_SNAPSHOT_TABLE}`); + } + } +} diff --git a/packages/pipeline/package.json b/packages/pipeline/package.json index 56d5ef10d9..21bea768e6 100644 --- a/packages/pipeline/package.json +++ b/packages/pipeline/package.json @@ -49,6 +49,7 @@ "@0x/types": "^2.0.2", "@0x/utils": "^4.1.0", "@0x/web3-wrapper": "^5.0.0", + "@radarrelay/types": "^1.2.1", "@types/dockerode": "^2.5.9", "@types/p-limit": "^2.0.0", "async-parallel": "^1.2.3", diff --git a/packages/pipeline/src/data_sources/radar/index.ts b/packages/pipeline/src/data_sources/radar/index.ts new file mode 100644 index 0000000000..873b0fefe2 --- /dev/null +++ b/packages/pipeline/src/data_sources/radar/index.ts @@ -0,0 +1,53 @@ +import { orderParsingUtils } from '@0x/order-utils'; +import { fetchAsync, logUtils } from '@0x/utils'; +import { RadarBook, RadarMarket, RadarSignedOrder } from '@radarrelay/types'; + +const RADAR_BASE_URL = 'https://api.radarrelay.com/v2/'; +const ACTIVE_MARKETS_URL = `${RADAR_BASE_URL}/markets`; +const MAX_PER_PAGE = 10000; + +export const RADAR_SOURCE = 'radar'; + +// tslint:disable:prefer-function-over-method +// ^ Keep consistency with other sources and help logical organization +export class RadarSource { + public static parseRadarOrderResponse(radarOrderResponse: any): RadarSignedOrder { + return { + ...radarOrderResponse, + ...orderParsingUtils.convertStringsFieldsToBigNumbers(radarOrderResponse, [ + 'remainingBaseTokenAmount', + 'remainingQuoteTokenAmount', + 'price', + ]), + signedOrder: orderParsingUtils.convertOrderStringFieldsToBigNumber(radarOrderResponse.signedOrder), + }; + } + /** + * Call Radar API to find out which markets they are maintaining orderbooks for. + */ + public async getActiveMarketsAsync(): Promise { + logUtils.log('Getting all active Radar markets'); + const resp = await fetchAsync(`${ACTIVE_MARKETS_URL}?perPage=${MAX_PER_PAGE}`); + const markets: RadarMarket[] = await resp.json(); + logUtils.log(`Got ${markets.length} markets.`); + return markets; + } + + /** + * Retrieve orderbook from Radar API for a given market. + * @param marketId String identifying the market we want data for. Eg. 'REP/AUG' + */ + public async getMarketOrderbookAsync(marketId: string): Promise { + logUtils.log(`${marketId}: Retrieving orderbook.`); + const marketOrderbookUrl = `${ACTIVE_MARKETS_URL}/${marketId}/book?perPage=${MAX_PER_PAGE}`; + const resp = await fetchAsync(marketOrderbookUrl); + const jsonResp = await resp.json(); + return { + ...jsonResp, + // tslint:disable-next-line:no-unbound-method + bids: jsonResp.bids.map(RadarSource.parseRadarOrderResponse), + // tslint:disable-next-line:no-unbound-method + asks: jsonResp.asks.map(RadarSource.parseRadarOrderResponse), + }; + } +} diff --git a/packages/pipeline/src/entities/token_order.ts b/packages/pipeline/src/entities/token_order.ts index 2709747cb3..a1f0006d6e 100644 --- a/packages/pipeline/src/entities/token_order.ts +++ b/packages/pipeline/src/entities/token_order.ts @@ -15,12 +15,14 @@ export class TokenOrderbookSnapshot { public price!: BigNumber; @PrimaryColumn({ name: 'base_asset_symbol' }) public baseAssetSymbol!: string; + @PrimaryColumn({ name: 'quote_asset_symbol' }) + public quoteAssetSymbol!: string; + @PrimaryColumn({ type: String, name: 'maker_address', default: 'unknown' }) + public makerAddress!: string; @Column({ nullable: true, type: String, name: 'base_asset_address' }) public baseAssetAddress!: string | null; @Column({ name: 'base_volume', type: 'numeric', transformer: bigNumberTransformer }) public baseVolume!: BigNumber; - @PrimaryColumn({ name: 'quote_asset_symbol' }) - public quoteAssetSymbol!: string; @Column({ nullable: true, type: String, name: 'quote_asset_address' }) public quoteAssetAddress!: string | null; @Column({ name: 'quote_volume', type: 'numeric', transformer: bigNumberTransformer }) diff --git a/packages/pipeline/src/parsers/ddex_orders/index.ts b/packages/pipeline/src/parsers/ddex_orders/index.ts index 562f894ab6..6c98c32254 100644 --- a/packages/pipeline/src/parsers/ddex_orders/index.ts +++ b/packages/pipeline/src/parsers/ddex_orders/index.ts @@ -2,33 +2,27 @@ import { BigNumber } from '@0x/utils'; import { aggregateOrders } from '../utils'; -import { DdexMarket, DdexOrderbook } from '../../data_sources/ddex'; +import { DDEX_SOURCE, DdexMarket, DdexOrderbook } from '../../data_sources/ddex'; import { TokenOrderbookSnapshot as TokenOrder } from '../../entities'; import { OrderType } from '../../types'; /** - * Marque function of this file. + * Marquee function of this file. * 1) Takes in orders from an orderbook, * other information attached. * @param ddexOrderbook A raw orderbook that we pull from the Ddex API. * @param ddexMarket An object containing market data also directly from the API. * @param observedTimestamp Time at which the orders for the market were pulled. - * @param source The exchange where these orders are placed. In this case 'ddex'. */ export function parseDdexOrders( ddexOrderbook: DdexOrderbook, ddexMarket: DdexMarket, observedTimestamp: number, - source: string, ): TokenOrder[] { const aggregatedBids = aggregateOrders(ddexOrderbook.bids); const aggregatedAsks = aggregateOrders(ddexOrderbook.asks); - const parsedBids = aggregatedBids.map(order => - parseDdexOrder(ddexMarket, observedTimestamp, OrderType.Bid, source, order), - ); - const parsedAsks = aggregatedAsks.map(order => - parseDdexOrder(ddexMarket, observedTimestamp, OrderType.Ask, source, order), - ); + const parsedBids = aggregatedBids.map(order => parseDdexOrder(ddexMarket, observedTimestamp, OrderType.Bid, order)); + const parsedAsks = aggregatedAsks.map(order => parseDdexOrder(ddexMarket, observedTimestamp, OrderType.Ask, order)); return parsedBids.concat(parsedAsks); } @@ -46,14 +40,13 @@ export function parseDdexOrder( ddexMarket: DdexMarket, observedTimestamp: number, orderType: OrderType, - source: string, ddexOrder: [string, BigNumber], ): TokenOrder { const tokenOrder = new TokenOrder(); const price = new BigNumber(ddexOrder[0]); const amount = ddexOrder[1]; - tokenOrder.source = source; + tokenOrder.source = DDEX_SOURCE; tokenOrder.observedTimestamp = observedTimestamp; tokenOrder.orderType = orderType; tokenOrder.price = price; @@ -65,5 +58,7 @@ export function parseDdexOrder( tokenOrder.quoteAssetSymbol = ddexMarket.quoteToken; tokenOrder.quoteAssetAddress = ddexMarket.quoteTokenAddress; tokenOrder.quoteVolume = price.times(amount); + + tokenOrder.makerAddress = 'unknown'; return tokenOrder; } diff --git a/packages/pipeline/src/parsers/idex_orders/index.ts b/packages/pipeline/src/parsers/idex_orders/index.ts index 14b8711958..b151fda59d 100644 --- a/packages/pipeline/src/parsers/idex_orders/index.ts +++ b/packages/pipeline/src/parsers/idex_orders/index.ts @@ -2,28 +2,25 @@ import { BigNumber } from '@0x/utils'; import { aggregateOrders } from '../utils'; -import { IdexOrderbook, IdexOrderParam } from '../../data_sources/idex'; +import { IDEX_SOURCE, IdexOrderbook, IdexOrderParam } from '../../data_sources/idex'; import { TokenOrderbookSnapshot as TokenOrder } from '../../entities'; import { OrderType } from '../../types'; /** - * Marque function of this file. + * Marquee function of this file. * 1) Takes in orders from an orderbook, * 2) Aggregates them by price point, * 3) Parses them into entities which are then saved into the database. * @param idexOrderbook raw orderbook that we pull from the Idex API. * @param observedTimestamp Time at which the orders for the market were pulled. - * @param source The exchange where these orders are placed. In this case 'idex'. */ -export function parseIdexOrders(idexOrderbook: IdexOrderbook, observedTimestamp: number, source: string): TokenOrder[] { +export function parseIdexOrders(idexOrderbook: IdexOrderbook, observedTimestamp: number): TokenOrder[] { const aggregatedBids = aggregateOrders(idexOrderbook.bids); // Any of the bid orders' params will work const idexBidOrder = idexOrderbook.bids[0]; const parsedBids = aggregatedBids.length > 0 - ? aggregatedBids.map(order => - parseIdexOrder(idexBidOrder.params, observedTimestamp, OrderType.Bid, source, order), - ) + ? aggregatedBids.map(order => parseIdexOrder(idexBidOrder.params, observedTimestamp, OrderType.Bid, order)) : []; const aggregatedAsks = aggregateOrders(idexOrderbook.asks); @@ -31,9 +28,7 @@ export function parseIdexOrders(idexOrderbook: IdexOrderbook, observedTimestamp: const idexAskOrder = idexOrderbook.asks[0]; const parsedAsks = aggregatedAsks.length > 0 - ? aggregatedAsks.map(order => - parseIdexOrder(idexAskOrder.params, observedTimestamp, OrderType.Ask, source, order), - ) + ? aggregatedAsks.map(order => parseIdexOrder(idexAskOrder.params, observedTimestamp, OrderType.Ask, order)) : []; return parsedBids.concat(parsedAsks); } @@ -45,26 +40,25 @@ export function parseIdexOrders(idexOrderbook: IdexOrderbook, observedTimestamp: * trades have been placed. * @param observedTimestamp The time when the API response returned back to us. * @param orderType 'bid' or 'ask' enum. - * @param source Exchange where these orders were placed. * @param idexOrder A tuple which we will convert to volume-basis. */ export function parseIdexOrder( idexOrderParam: IdexOrderParam, observedTimestamp: number, orderType: OrderType, - source: string, idexOrder: [string, BigNumber], ): TokenOrder { const tokenOrder = new TokenOrder(); const price = new BigNumber(idexOrder[0]); const amount = idexOrder[1]; - tokenOrder.source = source; + tokenOrder.source = IDEX_SOURCE; tokenOrder.observedTimestamp = observedTimestamp; tokenOrder.orderType = orderType; tokenOrder.price = price; tokenOrder.baseVolume = amount; tokenOrder.quoteVolume = price.times(amount); + tokenOrder.makerAddress = 'unknown'; if (orderType === OrderType.Bid) { tokenOrder.baseAssetSymbol = idexOrderParam.buySymbol; diff --git a/packages/pipeline/src/parsers/oasis_orders/index.ts b/packages/pipeline/src/parsers/oasis_orders/index.ts index b71fb65b90..25ec002d34 100644 --- a/packages/pipeline/src/parsers/oasis_orders/index.ts +++ b/packages/pipeline/src/parsers/oasis_orders/index.ts @@ -3,33 +3,31 @@ import * as R from 'ramda'; import { aggregateOrders } from '../utils'; -import { OasisMarket, OasisOrder } from '../../data_sources/oasis'; +import { OASIS_SOURCE, OasisMarket, OasisOrder } from '../../data_sources/oasis'; import { TokenOrderbookSnapshot as TokenOrder } from '../../entities'; import { OrderType } from '../../types'; /** - * Marque function of this file. + * Marquee function of this file. * 1) Takes in orders from an orderbook, * 2) Aggregates them according to price point, * 3) Builds TokenOrder entity with other information attached. * @param oasisOrderbook A raw orderbook that we pull from the Oasis API. * @param oasisMarket An object containing market data also directly from the API. * @param observedTimestamp Time at which the orders for the market were pulled. - * @param source The exchange where these orders are placed. In this case 'oasis'. */ export function parseOasisOrders( oasisOrderbook: OasisOrder[], oasisMarket: OasisMarket, observedTimestamp: number, - source: string, ): TokenOrder[] { const aggregatedBids = aggregateOrders(R.filter(R.propEq('act', OrderType.Bid), oasisOrderbook)); const aggregatedAsks = aggregateOrders(R.filter(R.propEq('act', OrderType.Ask), oasisOrderbook)); const parsedBids = aggregatedBids.map(order => - parseOasisOrder(oasisMarket, observedTimestamp, OrderType.Bid, source, order), + parseOasisOrder(oasisMarket, observedTimestamp, OrderType.Bid, order), ); const parsedAsks = aggregatedAsks.map(order => - parseOasisOrder(oasisMarket, observedTimestamp, OrderType.Ask, source, order), + parseOasisOrder(oasisMarket, observedTimestamp, OrderType.Ask, order), ); return parsedBids.concat(parsedAsks); } @@ -48,14 +46,13 @@ export function parseOasisOrder( oasisMarket: OasisMarket, observedTimestamp: number, orderType: OrderType, - source: string, oasisOrder: [string, BigNumber], ): TokenOrder { const tokenOrder = new TokenOrder(); const price = new BigNumber(oasisOrder[0]); const amount = oasisOrder[1]; - tokenOrder.source = source; + tokenOrder.source = OASIS_SOURCE; tokenOrder.observedTimestamp = observedTimestamp; tokenOrder.orderType = orderType; tokenOrder.price = price; @@ -67,5 +64,6 @@ export function parseOasisOrder( tokenOrder.quoteAssetSymbol = oasisMarket.quote; tokenOrder.quoteAssetAddress = null; // Oasis doesn't provide address information tokenOrder.quoteVolume = price.times(amount); + tokenOrder.makerAddress = 'unknown'; return tokenOrder; } diff --git a/packages/pipeline/src/parsers/paradex_orders/index.ts b/packages/pipeline/src/parsers/paradex_orders/index.ts index 85990dae4f..756bb353f1 100644 --- a/packages/pipeline/src/parsers/paradex_orders/index.ts +++ b/packages/pipeline/src/parsers/paradex_orders/index.ts @@ -1,30 +1,28 @@ import { BigNumber } from '@0x/utils'; -import { ParadexMarket, ParadexOrder, ParadexOrderbookResponse } from '../../data_sources/paradex'; +import { PARADEX_SOURCE, ParadexMarket, ParadexOrder, ParadexOrderbookResponse } from '../../data_sources/paradex'; import { TokenOrderbookSnapshot as TokenOrder } from '../../entities'; import { OrderType } from '../../types'; /** - * Marque function of this file. + * Marquee function of this file. * 1) Takes in orders from an orderbook (orders are already aggregated by price point), * 2) For each aggregated order, forms a TokenOrder entity with market data and * other information attached. * @param paradexOrderbookResponse An orderbook response from the Paradex API. * @param paradexMarket An object containing market data also directly from the API. * @param observedTimestamp Time at which the orders for the market were pulled. - * @param source The exchange where these orders are placed. In this case 'paradex'. */ export function parseParadexOrders( paradexOrderbookResponse: ParadexOrderbookResponse, paradexMarket: ParadexMarket, observedTimestamp: number, - source: string, ): TokenOrder[] { const parsedBids = paradexOrderbookResponse.bids.map(order => - parseParadexOrder(paradexMarket, observedTimestamp, OrderType.Bid, source, order), + parseParadexOrder(paradexMarket, observedTimestamp, OrderType.Bid, order), ); const parsedAsks = paradexOrderbookResponse.asks.map(order => - parseParadexOrder(paradexMarket, observedTimestamp, OrderType.Ask, source, order), + parseParadexOrder(paradexMarket, observedTimestamp, OrderType.Ask, order), ); return parsedBids.concat(parsedAsks); } @@ -36,21 +34,19 @@ export function parseParadexOrders( * orders have been placed. * @param observedTimestamp The time when the API response returned back to us. * @param orderType 'bid' or 'ask' enum. - * @param source Exchange where these orders were placed. * @param paradexOrder A ParadexOrder object; basically price, amount tuple. */ export function parseParadexOrder( paradexMarket: ParadexMarket, observedTimestamp: number, orderType: OrderType, - source: string, paradexOrder: ParadexOrder, ): TokenOrder { const tokenOrder = new TokenOrder(); const price = new BigNumber(paradexOrder.price); const amount = new BigNumber(paradexOrder.amount); - tokenOrder.source = source; + tokenOrder.source = PARADEX_SOURCE; tokenOrder.observedTimestamp = observedTimestamp; tokenOrder.orderType = orderType; tokenOrder.price = price; @@ -62,5 +58,6 @@ export function parseParadexOrder( tokenOrder.quoteAssetSymbol = paradexMarket.quoteToken; tokenOrder.quoteAssetAddress = paradexMarket.quoteTokenAddress as string; tokenOrder.quoteVolume = price.times(amount); + tokenOrder.makerAddress = 'unknown'; return tokenOrder; } diff --git a/packages/pipeline/src/parsers/radar_orders/index.ts b/packages/pipeline/src/parsers/radar_orders/index.ts new file mode 100644 index 0000000000..6ecedc0c36 --- /dev/null +++ b/packages/pipeline/src/parsers/radar_orders/index.ts @@ -0,0 +1,118 @@ +import { ObjectMap } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { RadarBook, RadarMarket, RadarSignedOrder } from '@radarrelay/types'; +import * as R from 'ramda'; + +import { aggregateOrders, GenericRawOrder } from '../utils'; + +import { RADAR_SOURCE } from '../../data_sources/radar'; +import { TokenOrderbookSnapshot as TokenOrder } from '../../entities'; +import { OrderType } from '../../types'; + +export interface AggregateOrdersByMaker { + makerAddress: string; + price: string; + amount: BigNumber; +} + +/** + * Marquee function of this file. + * 1) Takes in orders from an orderbook, + * other information attached. + * @param radarOrderbook A raw orderbook that we pull from the radar API. + * @param radarMarket An object containing market data also directly from the API. + * @param observedTimestamp Time at which the orders for the market were pulled. + */ +export function parseRadarOrders( + radarOrderbook: RadarBook, + radarMarket: RadarMarket, + observedTimestamp: number, +): TokenOrder[] { + const aggregatedBids = _aggregateOrdersByMaker(radarMarket, radarOrderbook.bids); + const aggregatedAsks = _aggregateOrdersByMaker(radarMarket, radarOrderbook.asks); + const parsedBids = aggregatedBids.map(order => + parseRadarOrder(radarMarket, observedTimestamp, OrderType.Bid, order), + ); + const parsedAsks = aggregatedAsks.map(order => + parseRadarOrder(radarMarket, observedTimestamp, OrderType.Ask, order), + ); + return parsedBids.concat(parsedAsks); +} + +/** + * Parse a single aggregated radar order in order to form a tokenOrder entity + * which can be saved into the database. + * @param radarMarket An object containing information about the market where these + * trades have been placed. + * @param observedTimestamp The time when the API response returned back to us. + * @param orderType 'bid' or 'ask' enum. + * @param aggregateOrder An AggregateOrdersByMaker instance which we will convert to volume-basis. + */ +export function parseRadarOrder( + radarMarket: RadarMarket, + observedTimestamp: number, + orderType: OrderType, + aggregateOrder: AggregateOrdersByMaker, +): TokenOrder { + const tokenOrder = new TokenOrder(); + const price = new BigNumber(aggregateOrder.price); + const amount = aggregateOrder.amount; + const splitId = radarMarket.id.split('-'); + + tokenOrder.source = RADAR_SOURCE; + tokenOrder.observedTimestamp = observedTimestamp; + tokenOrder.orderType = orderType; + tokenOrder.price = price; + + tokenOrder.baseAssetSymbol = splitId[0]; + tokenOrder.baseAssetAddress = radarMarket.baseTokenAddress || null; + tokenOrder.baseVolume = amount; + + tokenOrder.quoteAssetSymbol = splitId[1]; + tokenOrder.quoteAssetAddress = radarMarket.quoteTokenAddress || null; + tokenOrder.quoteVolume = price.times(amount); + + tokenOrder.makerAddress = aggregateOrder.makerAddress; + return tokenOrder; +} + +function _toGeneric(radarMarket: RadarMarket, radarOrder: RadarSignedOrder): GenericRawOrder | undefined { + if (radarMarket.baseTokenDecimals === undefined) { + return undefined; + } + return { + price: radarOrder.price.toString(), + // Use the remaining fillable amount + amount: radarOrder.remainingBaseTokenAmount.toString(), + }; +} + +function _aggregateOrdersByMaker(radarMarket: RadarMarket, radarOrders: RadarSignedOrder[]): AggregateOrdersByMaker[] { + // group all orders by their maker + const ordersByMaker: ObjectMap = radarOrders.reduce( + (acc: ObjectMap, val: RadarSignedOrder) => { + const makerAddress = val.signedOrder.makerAddress; + if (acc[makerAddress]) { + acc[makerAddress].push(val); + } else { + acc[makerAddress] = []; + } + return acc; + }, + {}, + ); + const transformToGeneric = (radarOrder: RadarSignedOrder) => _toGeneric(radarMarket, radarOrder); + const aggregationTuples: AggregateOrdersByMaker[][] = (R.keys(ordersByMaker) as string[]).map((maker: string) => { + const generalizedOrders = _removeUndefined(R.map(transformToGeneric, ordersByMaker[maker])); + const aggregatedOrders = aggregateOrders(generalizedOrders); + return aggregatedOrders.map((order: [string, BigNumber]) => ({ + makerAddress: maker, + price: order[0], + amount: order[1], + })); + }); + return R.unnest(aggregationTuples); +} + +// tslint:disable-next-line:no-unbound-method +const _removeUndefined = R.reject(R.isNil); diff --git a/packages/pipeline/src/scripts/pull_ddex_orderbook_snapshots.ts b/packages/pipeline/src/scripts/pull_ddex_orderbook_snapshots.ts index 4e00f258f3..96d82be5a2 100644 --- a/packages/pipeline/src/scripts/pull_ddex_orderbook_snapshots.ts +++ b/packages/pipeline/src/scripts/pull_ddex_orderbook_snapshots.ts @@ -2,7 +2,7 @@ import { logUtils } from '@0x/utils'; import * as R from 'ramda'; import { Connection, ConnectionOptions, createConnection } from 'typeorm'; -import { DDEX_SOURCE, DdexMarket, DdexSource } from '../data_sources/ddex'; +import { DdexMarket, DdexSource } from '../data_sources/ddex'; import { TokenOrderbookSnapshot as TokenOrder } from '../entities'; import * as ormConfig from '../ormconfig'; import { parseDdexOrders } from '../parsers/ddex_orders'; @@ -43,7 +43,7 @@ async function getAndSaveMarketOrderbookAsync(ddexSource: DdexSource, market: Dd const observedTimestamp = Date.now(); logUtils.log(`${market.id}: Parsing orders.`); - const orders = parseDdexOrders(orderBook, market, observedTimestamp, DDEX_SOURCE); + const orders = parseDdexOrders(orderBook, market, observedTimestamp); if (orders.length > 0) { logUtils.log(`${market.id}: Saving ${orders.length} orders.`); diff --git a/packages/pipeline/src/scripts/pull_idex_orderbook_snapshots.ts b/packages/pipeline/src/scripts/pull_idex_orderbook_snapshots.ts index 490b17766c..42e9fa5f09 100644 --- a/packages/pipeline/src/scripts/pull_idex_orderbook_snapshots.ts +++ b/packages/pipeline/src/scripts/pull_idex_orderbook_snapshots.ts @@ -2,7 +2,7 @@ import { logUtils } from '@0x/utils'; import * as R from 'ramda'; import { Connection, ConnectionOptions, createConnection } from 'typeorm'; -import { IDEX_SOURCE, IdexSource } from '../data_sources/idex'; +import { IdexSource } from '../data_sources/idex'; import { TokenOrderbookSnapshot as TokenOrder } from '../entities'; import * as ormConfig from '../ormconfig'; import { parseIdexOrders } from '../parsers/idex_orders'; @@ -51,7 +51,7 @@ async function getAndSaveMarketOrderbookAsync(idexSource: IdexSource, marketId: } logUtils.log(`${marketId}: Parsing orders.`); - const orders = parseIdexOrders(orderBook, observedTimestamp, IDEX_SOURCE); + const orders = parseIdexOrders(orderBook, observedTimestamp); if (orders.length > 0) { logUtils.log(`${marketId}: Saving ${orders.length} orders.`); diff --git a/packages/pipeline/src/scripts/pull_oasis_orderbook_snapshots.ts b/packages/pipeline/src/scripts/pull_oasis_orderbook_snapshots.ts index c4dcf6c83d..c3c286a0f9 100644 --- a/packages/pipeline/src/scripts/pull_oasis_orderbook_snapshots.ts +++ b/packages/pipeline/src/scripts/pull_oasis_orderbook_snapshots.ts @@ -2,7 +2,7 @@ import { logUtils } from '@0x/utils'; import * as R from 'ramda'; import { Connection, ConnectionOptions, createConnection } from 'typeorm'; -import { OASIS_SOURCE, OasisMarket, OasisSource } from '../data_sources/oasis'; +import { OasisMarket, OasisSource } from '../data_sources/oasis'; import { TokenOrderbookSnapshot as TokenOrder } from '../entities'; import * as ormConfig from '../ormconfig'; import { parseOasisOrders } from '../parsers/oasis_orders'; @@ -46,7 +46,7 @@ async function getAndSaveMarketOrderbookAsync(oasisSource: OasisSource, market: const observedTimestamp = Date.now(); logUtils.log(`${market.id}: Parsing orders.`); - const orders = parseOasisOrders(orderBook, market, observedTimestamp, OASIS_SOURCE); + const orders = parseOasisOrders(orderBook, market, observedTimestamp); if (orders.length > 0) { logUtils.log(`${market.id}: Saving ${orders.length} orders.`); diff --git a/packages/pipeline/src/scripts/pull_paradex_orderbook_snapshots.ts b/packages/pipeline/src/scripts/pull_paradex_orderbook_snapshots.ts index 34345f3557..7fc565ca04 100644 --- a/packages/pipeline/src/scripts/pull_paradex_orderbook_snapshots.ts +++ b/packages/pipeline/src/scripts/pull_paradex_orderbook_snapshots.ts @@ -2,7 +2,6 @@ import { logUtils } from '@0x/utils'; import { Connection, ConnectionOptions, createConnection } from 'typeorm'; import { - PARADEX_SOURCE, ParadexActiveMarketsResponse, ParadexMarket, ParadexSource, @@ -75,7 +74,7 @@ async function getAndSaveMarketOrderbookAsync(paradexSource: ParadexSource, mark const observedTimestamp = Date.now(); logUtils.log(`${market.symbol}: Parsing orders.`); - const orders = parseParadexOrders(paradexOrderbookResponse, market, observedTimestamp, PARADEX_SOURCE); + const orders = parseParadexOrders(paradexOrderbookResponse, market, observedTimestamp); if (orders.length > 0) { logUtils.log(`${market.symbol}: Saving ${orders.length} orders.`); diff --git a/packages/pipeline/src/scripts/pull_radar_orderbook_snapshots.ts b/packages/pipeline/src/scripts/pull_radar_orderbook_snapshots.ts new file mode 100644 index 0000000000..18f5a54b20 --- /dev/null +++ b/packages/pipeline/src/scripts/pull_radar_orderbook_snapshots.ts @@ -0,0 +1,56 @@ +import { logUtils } from '@0x/utils'; +import { RadarMarket } from '@radarrelay/types'; +import * as R from 'ramda'; +import { Connection, ConnectionOptions, createConnection } from 'typeorm'; + +import { RadarSource } from '../data_sources/radar'; +import { TokenOrderbookSnapshot as TokenOrder } from '../entities'; +import * as ormConfig from '../ormconfig'; +import { parseRadarOrders } from '../parsers/radar_orders'; +import { handleError } from '../utils'; + +// Number of orders to save at once. +const BATCH_SAVE_SIZE = 1000; + +// Number of markets to retrieve orderbooks for at once. +const MARKET_ORDERBOOK_REQUEST_BATCH_SIZE = 50; + +// Delay between market orderbook requests. +const MILLISEC_MARKET_ORDERBOOK_REQUEST_DELAY = 5000; + +let connection: Connection; + +(async () => { + connection = await createConnection(ormConfig as ConnectionOptions); + const radarSource = new RadarSource(); + const markets = await radarSource.getActiveMarketsAsync(); + for (const marketsChunk of R.splitEvery(MARKET_ORDERBOOK_REQUEST_BATCH_SIZE, markets)) { + await Promise.all( + marketsChunk.map(async (market: RadarMarket) => getAndSaveMarketOrderbookAsync(radarSource, market)), + ); + await new Promise(resolve => setTimeout(resolve, MILLISEC_MARKET_ORDERBOOK_REQUEST_DELAY)); + } + process.exit(0); +})().catch(handleError); + +/** + * Retrieve orderbook from radar API for a given market. Parse orders and insert + * them into our database. + * @param radarSource Data source which can query radar API. + * @param market Object from radar API containing market data. + */ +async function getAndSaveMarketOrderbookAsync(radarSource: RadarSource, market: RadarMarket): Promise { + const orderBook = await radarSource.getMarketOrderbookAsync(market.id); + const observedTimestamp = Date.now(); + + logUtils.log(`${market.id}: Parsing orders.`); + const orders = parseRadarOrders(orderBook, market, observedTimestamp); + + if (orders.length > 0) { + logUtils.log(`${market.id}: Saving ${orders.length} orders.`); + const TokenOrderRepository = connection.getRepository(TokenOrder); + await TokenOrderRepository.save(orders, { chunk: Math.ceil(orders.length / BATCH_SAVE_SIZE) }); + } else { + logUtils.log(`${market.id}: 0 orders to save.`); + } +} diff --git a/packages/pipeline/test/data_sources/radar/index_test.ts b/packages/pipeline/test/data_sources/radar/index_test.ts new file mode 100644 index 0000000000..8d82ff6bf5 --- /dev/null +++ b/packages/pipeline/test/data_sources/radar/index_test.ts @@ -0,0 +1,76 @@ +import { BigNumber } from '@0x/utils'; +import { RadarOrderState, RadarOrderType } from '@radarrelay/types'; +import * as chai from 'chai'; +import 'mocha'; + +import { RadarSource } from '../../../src/data_sources/radar'; +import { chaiSetup } from '../../utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +const rawResponse = { + orderHash: '0x60bc235f7887a50801c8fc1fc18fb0625ac5f3962cdc1bd59567a6929db8b2ec', + type: 'BID', + state: 'OPEN', + baseTokenAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + quoteTokenAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + remainingBaseTokenAmount: '9.079731811797989766', + remainingQuoteTokenAmount: '1099.999999999999999889', + price: '121.14895272244560081697', + createdDate: '2019-02-13 21:35:53', + signedOrder: { + exchangeAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b', + senderAddress: '0x0000000000000000000000000000000000000000', + makerAddress: '0x56178a0d5f301baf6cf3e1cd53d9863437345bf9', + takerAddress: '0x0000000000000000000000000000000000000000', + makerAssetData: '0xf47261b000000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359', + takerAssetData: '0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + feeRecipientAddress: '0xa258b39954cef5cb142fd567a46cddb31a670124', + makerAssetAmount: '1099999999999999999889', + takerAssetAmount: '9079731811797989766', + makerFee: '0', + takerFee: '0', + expirationTimeSeconds: '1550094353', + signature: + '0x1ce161d02ad63fe7308e9cd5e97583a8873331d1b72d90e9f3863d9fcba2518cb91ab2fe7de94e4afb39742acdc820abbff2dc0622c8d3865917fade62f16322ae03', + salt: '1550093753237', + }, +}; + +const parsedResponse = { + orderHash: '0x60bc235f7887a50801c8fc1fc18fb0625ac5f3962cdc1bd59567a6929db8b2ec', + type: 'BID' as RadarOrderType, + state: 'OPEN' as RadarOrderState, + baseTokenAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + quoteTokenAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + remainingBaseTokenAmount: new BigNumber('9.079731811797989766'), + remainingQuoteTokenAmount: new BigNumber('1099.999999999999999889'), + price: new BigNumber('121.14895272244560081697'), + createdDate: '2019-02-13 21:35:53', + signedOrder: { + exchangeAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b', + senderAddress: '0x0000000000000000000000000000000000000000', + makerAddress: '0x56178a0d5f301baf6cf3e1cd53d9863437345bf9', + takerAddress: '0x0000000000000000000000000000000000000000', + makerAssetData: '0xf47261b000000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359', + takerAssetData: '0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + feeRecipientAddress: '0xa258b39954cef5cb142fd567a46cddb31a670124', + makerAssetAmount: new BigNumber('1099999999999999999889'), + takerAssetAmount: new BigNumber('9079731811797989766'), + makerFee: new BigNumber('0'), + takerFee: new BigNumber('0'), + expirationTimeSeconds: new BigNumber('1550094353'), + signature: + '0x1ce161d02ad63fe7308e9cd5e97583a8873331d1b72d90e9f3863d9fcba2518cb91ab2fe7de94e4afb39742acdc820abbff2dc0622c8d3865917fade62f16322ae03', + salt: new BigNumber('1550093753237'), + }, +}; + +describe('RadarSource', () => { + describe('parseRadarOrderResponse', () => { + it('Correctly parses a Radar orderbook response to a RadarBook', () => { + expect(RadarSource.parseRadarOrderResponse(rawResponse)).deep.equal(parsedResponse); + }); + }); +}); diff --git a/packages/pipeline/test/entities/token_order_test.ts b/packages/pipeline/test/entities/token_order_test.ts index c6057f5aae..8f2df569b0 100644 --- a/packages/pipeline/test/entities/token_order_test.ts +++ b/packages/pipeline/test/entities/token_order_test.ts @@ -20,6 +20,7 @@ const tokenOrderbookSnapshot: TokenOrderbookSnapshot = { quoteAssetSymbol: 'ABC', quoteAssetAddress: '0x00923b9a074762b93650716333b3e1473a15048e', quoteVolume: new BigNumber(12.3234234), + makerAddress: 'unknown', }; describe('TokenOrderbookSnapshot entity', () => { diff --git a/packages/pipeline/test/parsers/ddex_orders/index_test.ts b/packages/pipeline/test/parsers/ddex_orders/index_test.ts index d6f69e090e..de71c9c26f 100644 --- a/packages/pipeline/test/parsers/ddex_orders/index_test.ts +++ b/packages/pipeline/test/parsers/ddex_orders/index_test.ts @@ -31,7 +31,6 @@ describe('ddex_orders', () => { }; const observedTimestamp: number = Date.now(); const orderType: OrderType = OrderType.Bid; - const source: string = 'ddex'; const expected = new TokenOrder(); expected.source = 'ddex'; @@ -44,8 +43,8 @@ describe('ddex_orders', () => { expected.baseAssetSymbol = 'DEF'; expected.baseAssetAddress = '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81'; expected.baseVolume = new BigNumber(10); - - const actual = parseDdexOrder(ddexMarket, observedTimestamp, orderType, source, ddexOrder); + expected.makerAddress = 'unknown'; + const actual = parseDdexOrder(ddexMarket, observedTimestamp, orderType, ddexOrder); expect(actual).deep.equal(expected); }); }); diff --git a/packages/pipeline/test/parsers/idex_orders/index_test.ts b/packages/pipeline/test/parsers/idex_orders/index_test.ts index 48b019732a..7ba3adbaea 100644 --- a/packages/pipeline/test/parsers/idex_orders/index_test.ts +++ b/packages/pipeline/test/parsers/idex_orders/index_test.ts @@ -32,7 +32,6 @@ describe('idex_orders', () => { }; const observedTimestamp: number = Date.now(); const orderType: OrderType = OrderType.Bid; - const source: string = 'idex'; const expected = new TokenOrder(); expected.source = 'idex'; @@ -45,8 +44,8 @@ describe('idex_orders', () => { expected.quoteAssetSymbol = 'DEF'; expected.quoteAssetAddress = '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81'; expected.quoteVolume = new BigNumber(5); - - const actual = parseIdexOrder(idexOrderParam, observedTimestamp, orderType, source, idexOrder); + expected.makerAddress = 'unknown'; + const actual = parseIdexOrder(idexOrderParam, observedTimestamp, orderType, idexOrder); expect(actual).deep.equal(expected); }); it('correctly converts ask type idexOrder to TokenOrder entity', () => { @@ -66,7 +65,6 @@ describe('idex_orders', () => { }; const observedTimestamp: number = Date.now(); const orderType: OrderType = OrderType.Ask; - const source: string = 'idex'; const expected = new TokenOrder(); expected.source = 'idex'; @@ -79,8 +77,8 @@ describe('idex_orders', () => { expected.quoteAssetSymbol = 'DEF'; expected.quoteAssetAddress = '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81'; expected.quoteVolume = new BigNumber(5); - - const actual = parseIdexOrder(idexOrderParam, observedTimestamp, orderType, source, idexOrder); + expected.makerAddress = 'unknown'; + const actual = parseIdexOrder(idexOrderParam, observedTimestamp, orderType, idexOrder); expect(actual).deep.equal(expected); }); }); diff --git a/packages/pipeline/test/parsers/oasis_orders/index_test.ts b/packages/pipeline/test/parsers/oasis_orders/index_test.ts index 401fedff87..08ff1ef450 100644 --- a/packages/pipeline/test/parsers/oasis_orders/index_test.ts +++ b/packages/pipeline/test/parsers/oasis_orders/index_test.ts @@ -28,7 +28,6 @@ describe('oasis_orders', () => { }; const observedTimestamp: number = Date.now(); const orderType: OrderType = OrderType.Bid; - const source: string = 'oasis'; const expected = new TokenOrder(); expected.source = 'oasis'; @@ -41,8 +40,8 @@ describe('oasis_orders', () => { expected.quoteAssetSymbol = 'ABC'; expected.quoteAssetAddress = null; expected.quoteVolume = new BigNumber(5); - - const actual = parseOasisOrder(oasisMarket, observedTimestamp, orderType, source, oasisOrder); + expected.makerAddress = 'unknown'; + const actual = parseOasisOrder(oasisMarket, observedTimestamp, orderType, oasisOrder); expect(actual).deep.equal(expected); }); }); diff --git a/packages/pipeline/test/parsers/paradex_orders/index_test.ts b/packages/pipeline/test/parsers/paradex_orders/index_test.ts index c5dd8751b2..5379153387 100644 --- a/packages/pipeline/test/parsers/paradex_orders/index_test.ts +++ b/packages/pipeline/test/parsers/paradex_orders/index_test.ts @@ -33,7 +33,6 @@ describe('paradex_orders', () => { }; const observedTimestamp: number = Date.now(); const orderType: OrderType = OrderType.Bid; - const source: string = 'paradex'; const expected = new TokenOrder(); expected.source = 'paradex'; @@ -46,8 +45,8 @@ describe('paradex_orders', () => { expected.quoteAssetSymbol = 'ABC'; expected.quoteAssetAddress = '0x0000000000000000000000000000000000000000'; expected.quoteVolume = new BigNumber(412 * 0.1245); - - const actual = parseParadexOrder(paradexMarket, observedTimestamp, orderType, source, paradexOrder); + expected.makerAddress = 'unknown'; + const actual = parseParadexOrder(paradexMarket, observedTimestamp, orderType, paradexOrder); expect(actual).deep.equal(expected); }); }); diff --git a/packages/pipeline/test/parsers/radar_orders/index_test.ts b/packages/pipeline/test/parsers/radar_orders/index_test.ts new file mode 100644 index 0000000000..29ae41a00d --- /dev/null +++ b/packages/pipeline/test/parsers/radar_orders/index_test.ts @@ -0,0 +1,55 @@ +import { BigNumber } from '@0x/utils'; +import { RadarMarket } from '@radarrelay/types'; +import * as chai from 'chai'; +import 'mocha'; + +import { TokenOrderbookSnapshot as TokenOrder } from '../../../src/entities'; +import { AggregateOrdersByMaker, parseRadarOrder } from '../../../src/parsers/radar_orders'; +import { OrderType } from '../../../src/types'; +import { chaiSetup } from '../../utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +// tslint:disable:custom-no-magic-numbers +describe('radar_orders', () => { + describe('parseRadarOrder', () => { + it('converts radarOrder to TokenOrder entity', () => { + const radarOrder: AggregateOrdersByMaker = { + makerAddress: '0x6eC92694ea172ebC430C30fa31De87620967A082', + price: '0.01', + amount: new BigNumber(10000000000), + }; + const radarMarket = ({ + id: 'WETH-DAI', + displayName: 'WETH/DAI', + baseTokenAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + quoteTokenAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + baseTokenDecimals: 18, + quoteTokenDecimals: 18, + quoteIncrement: 8, + minOrderSize: new BigNumber('0.00692535'), + maxOrderSize: new BigNumber('1000000000'), + score: 99.66, + // Radar types are defined using an older version of BigNumber, so need to be force cast. + } as any) as RadarMarket; + const observedTimestamp: number = Date.now(); + const orderType: OrderType = OrderType.Bid; + + const expected = new TokenOrder(); + expected.source = 'radar'; + expected.observedTimestamp = observedTimestamp; + expected.orderType = OrderType.Bid; + expected.price = new BigNumber(0.01); + expected.quoteAssetSymbol = 'DAI'; + expected.quoteAssetAddress = '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359'; + expected.quoteVolume = new BigNumber(100000000); + expected.baseAssetSymbol = 'WETH'; + expected.baseAssetAddress = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; + expected.baseVolume = new BigNumber(10000000000); + expected.makerAddress = '0x6eC92694ea172ebC430C30fa31De87620967A082'; + const actual = parseRadarOrder(radarMarket, observedTimestamp, orderType, radarOrder); + expect(actual).deep.equal(expected); + }); + }); +}); diff --git a/packages/website/package.json b/packages/website/package.json index 5dff93c957..e5c5e97b6e 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -57,7 +57,6 @@ "rc-slider": "^8.6.3", "react": "^16.5.2", "react-copy-to-clipboard": "^5.0.0", - "react-document-title": "^2.0.3", "react-dom": "^16.5.2", "react-flickity-component": "^3.1.0", "react-headroom": "2.2.2", diff --git a/packages/website/ts/components/document_title.tsx b/packages/website/ts/components/document_title.tsx new file mode 100644 index 0000000000..81fb021a5f --- /dev/null +++ b/packages/website/ts/components/document_title.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import { Helmet } from 'react-helmet'; + +import { DocumentMetadata } from '../utils/document_meta_constants'; + +export interface DocumentTitleProps extends DocumentMetadata {} + +export const DocumentTitle: React.StatelessComponent = ({ title, description }) => ( + + {title} + + + + +); diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index 6ebbf8d1fb..3d7c672583 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -2,7 +2,6 @@ import { colors, Link } from '@0x/react-shared'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import * as React from 'react'; -import * as DocumentTitle from 'react-document-title'; import { Route, RouteComponentProps, Switch } from 'react-router-dom'; import { Blockchain } from 'ts/blockchain'; @@ -234,7 +233,6 @@ export class Portal extends React.Component { return ( - - - +
diff --git a/packages/website/ts/pages/about/mission.tsx b/packages/website/ts/pages/about/mission.tsx index ab8949faec..69fe07f15b 100644 --- a/packages/website/ts/pages/about/mission.tsx +++ b/packages/website/ts/pages/about/mission.tsx @@ -1,14 +1,15 @@ import * as _ from 'lodash'; import * as React from 'react'; -import DocumentTitle from 'react-document-title'; import styled from 'styled-components'; import { AboutPageLayout } from 'ts/components/aboutPageLayout'; import { Definition } from 'ts/components/definition'; +import { DocumentTitle } from 'ts/components/document_title'; import { Image } from 'ts/components/image'; import { Column, Section } from 'ts/components/newLayout'; import { Heading } from 'ts/components/text'; import { constants } from 'ts/utils/constants'; +import { documentConstants } from 'ts/utils/document_meta_constants'; const values = [ { @@ -38,7 +39,7 @@ export const NextAboutMission = () => ( linkLabel="Our mission and values" href={constants.URL_MISSION_AND_VALUES_BLOG_POST} > - +
0x Offices diff --git a/packages/website/ts/pages/about/press.tsx b/packages/website/ts/pages/about/press.tsx index 03003d6561..1ffa11d1fb 100644 --- a/packages/website/ts/pages/about/press.tsx +++ b/packages/website/ts/pages/about/press.tsx @@ -1,12 +1,13 @@ import * as _ from 'lodash'; import * as React from 'react'; -import DocumentTitle from 'react-document-title'; import styled from 'styled-components'; import { AboutPageLayout } from 'ts/components/aboutPageLayout'; import { Button } from 'ts/components/button'; +import { DocumentTitle } from 'ts/components/document_title'; import { Column, FlexWrap } from 'ts/components/newLayout'; import { Paragraph } from 'ts/components/text'; +import { documentConstants } from 'ts/utils/document_meta_constants'; interface HighlightProps { logo: string; @@ -66,7 +67,7 @@ export const NextAboutPress = () => ( } > - + ); diff --git a/packages/website/ts/pages/about/team.tsx b/packages/website/ts/pages/about/team.tsx index 808fea0496..82506a3cf0 100644 --- a/packages/website/ts/pages/about/team.tsx +++ b/packages/website/ts/pages/about/team.tsx @@ -1,14 +1,15 @@ import * as _ from 'lodash'; import * as React from 'react'; -import DocumentTitle from 'react-document-title'; import styled from 'styled-components'; import { colors } from 'ts/style/colors'; import { AboutPageLayout } from 'ts/components/aboutPageLayout'; +import { DocumentTitle } from 'ts/components/document_title'; import { Column, Section } from 'ts/components/newLayout'; import { Heading, Paragraph } from 'ts/components/text'; import { WebsitePaths } from 'ts/types'; +import { documentConstants } from 'ts/utils/document_meta_constants'; interface TeamMember { name: string; @@ -194,7 +195,7 @@ export const NextAboutTeam = () => ( linkLabel="Join the team" to={WebsitePaths.AboutJobs} > - +
0x Team diff --git a/packages/website/ts/pages/credits.tsx b/packages/website/ts/pages/credits.tsx index fa0aa3c57c..50dc185fc3 100644 --- a/packages/website/ts/pages/credits.tsx +++ b/packages/website/ts/pages/credits.tsx @@ -4,11 +4,13 @@ import * as React from 'react'; import { Banner } from 'ts/components/banner'; import { Button } from 'ts/components/button'; import { CenteredDefinition } from 'ts/components/centeredDefinition'; +import { DocumentTitle } from 'ts/components/document_title'; import { Hero } from 'ts/components/hero'; import { ModalContact, ModalContactType } from 'ts/components/modals/modal_contact'; import { FlexWrap, Section } from 'ts/components/newLayout'; import { SiteWrap } from 'ts/components/siteWrap'; import { Heading } from 'ts/components/text'; +import { documentConstants } from 'ts/utils/document_meta_constants'; export interface CreditsProps {} @@ -24,6 +26,7 @@ export class Credits extends React.Component { public render(): React.ReactNode { return ( + - + diff --git a/packages/website/ts/pages/ecosystem.tsx b/packages/website/ts/pages/ecosystem.tsx index 8e367b21f7..455fc3a099 100644 --- a/packages/website/ts/pages/ecosystem.tsx +++ b/packages/website/ts/pages/ecosystem.tsx @@ -1,16 +1,17 @@ import * as _ from 'lodash'; import * as React from 'react'; -import DocumentTitle from 'react-document-title'; import styled from 'styled-components'; import { colors } from 'ts/style/colors'; import { Button } from 'ts/components/button'; +import { DocumentTitle } from 'ts/components/document_title'; import { Icon } from 'ts/components/icon'; import { Column, Section, WrapGrid } from 'ts/components/newLayout'; import { SiteWrap } from 'ts/components/siteWrap'; import { Heading, Paragraph } from 'ts/components/text'; import { constants } from 'ts/utils/constants'; +import { documentConstants } from 'ts/utils/document_meta_constants'; interface BenefitProps { title: string; @@ -55,7 +56,7 @@ const benefits: BenefitProps[] = [ export const NextEcosystem = () => ( - +
diff --git a/packages/website/ts/pages/faq/faq.tsx b/packages/website/ts/pages/faq/faq.tsx index 548db1d1d1..b86a1ca322 100644 --- a/packages/website/ts/pages/faq/faq.tsx +++ b/packages/website/ts/pages/faq/faq.tsx @@ -1,7 +1,7 @@ import { colors, Styles } from '@0x/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; -import * as DocumentTitle from 'react-document-title'; +import { DocumentTitle } from 'ts/components/document_title'; import { Footer } from 'ts/components/old_footer'; import { TopBar } from 'ts/components/top_bar/top_bar'; import { Question } from 'ts/pages/faq/question'; @@ -9,6 +9,7 @@ import { Dispatcher } from 'ts/redux/dispatcher'; import { FAQQuestion, FAQSection, WebsitePaths } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; +import { documentConstants } from 'ts/utils/document_meta_constants'; import { Translate } from 'ts/utils/translate'; export interface FAQProps { @@ -412,7 +413,7 @@ export class FAQ extends React.Component { public render(): React.ReactNode { return (
- +

diff --git a/packages/website/ts/pages/governance/governance.tsx b/packages/website/ts/pages/governance/governance.tsx index e6f2ee812d..28604b2ba6 100644 --- a/packages/website/ts/pages/governance/governance.tsx +++ b/packages/website/ts/pages/governance/governance.tsx @@ -1,11 +1,11 @@ import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import * as React from 'react'; -import DocumentTitle from 'react-document-title'; import styled from 'styled-components'; import { Banner } from 'ts/components/banner'; import { Button } from 'ts/components/button'; +import { DocumentTitle } from 'ts/components/document_title'; import { ModalContact } from 'ts/components/modals/modal_contact'; import { Column, FlexWrap, Section } from 'ts/components/newLayout'; import { SiteWrap } from 'ts/components/siteWrap'; @@ -17,6 +17,7 @@ import { VoteInfo, VoteValue } from 'ts/pages/governance/vote_form'; import { VoteStats } from 'ts/pages/governance/vote_stats'; import { colors } from 'ts/style/colors'; import { configs } from 'ts/utils/configs'; +import { documentConstants } from 'ts/utils/document_meta_constants'; import { utils } from 'ts/utils/utils'; interface LabelInterface { @@ -109,7 +110,7 @@ export class Governance extends React.Component { const { isVoteReceived, tally } = this.state; return ( - +
diff --git a/packages/website/ts/pages/instant.tsx b/packages/website/ts/pages/instant.tsx index 586665d5ba..81785e59ca 100644 --- a/packages/website/ts/pages/instant.tsx +++ b/packages/website/ts/pages/instant.tsx @@ -1,12 +1,12 @@ import { utils as sharedUtils } from '@0x/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; -import DocumentTitle from 'react-document-title'; import styled, { keyframes } from 'styled-components'; import { Banner } from 'ts/components/banner'; import { Button } from 'ts/components/button'; import { Definition } from 'ts/components/definition'; +import { DocumentTitle } from 'ts/components/document_title'; import { Hero } from 'ts/components/hero'; import { Section, SectionProps } from 'ts/components/newLayout'; import { SiteWrap } from 'ts/components/siteWrap'; @@ -14,6 +14,7 @@ import { Heading, Paragraph } from 'ts/components/text'; import { Configurator } from 'ts/pages/instant/configurator'; import { colors } from 'ts/style/colors'; import { WebsitePaths } from 'ts/types'; +import { documentConstants } from 'ts/utils/document_meta_constants'; import { utils } from 'ts/utils/utils'; import { ModalContact } from '../components/modals/modal_contact'; @@ -88,7 +89,7 @@ export class Next0xInstant extends React.Component { public render(): React.ReactNode { return ( - + { public render(): React.ReactNode { return ( - + diff --git a/packages/website/ts/pages/launch_kit.tsx b/packages/website/ts/pages/launch_kit.tsx index dd4de4d999..b3f09e6d2a 100644 --- a/packages/website/ts/pages/launch_kit.tsx +++ b/packages/website/ts/pages/launch_kit.tsx @@ -1,17 +1,15 @@ import * as _ from 'lodash'; import * as React from 'react'; -import DocumentTitle from 'react-document-title'; - -import { Hero } from 'ts/components/hero'; - import { Banner } from 'ts/components/banner'; import { Button } from 'ts/components/button'; import { Definition } from 'ts/components/definition'; +import { DocumentTitle } from 'ts/components/document_title'; +import { Hero } from 'ts/components/hero'; import { Icon } from 'ts/components/icon'; -import { SiteWrap } from 'ts/components/siteWrap'; - import { Section } from 'ts/components/newLayout'; +import { SiteWrap } from 'ts/components/siteWrap'; import { constants } from 'ts/utils/constants'; +import { documentConstants } from 'ts/utils/document_meta_constants'; import { ModalContact } from '../components/modals/modal_contact'; @@ -36,7 +34,7 @@ export class NextLaunchKit extends React.Component { public render(): React.ReactNode { return ( - + { public render(): React.ReactNode { return ( + ( - +
diff --git a/packages/website/ts/pages/why.tsx b/packages/website/ts/pages/why.tsx index 48888d10a7..784d42bd8e 100644 --- a/packages/website/ts/pages/why.tsx +++ b/packages/website/ts/pages/why.tsx @@ -1,17 +1,18 @@ import * as _ from 'lodash'; import * as React from 'react'; -import DocumentTitle from 'react-document-title'; import ScrollableAnchor, { configureAnchors } from 'react-scrollable-anchor'; import styled from 'styled-components'; import { Banner } from 'ts/components/banner'; import { Button } from 'ts/components/button'; import { Definition } from 'ts/components/definition'; +import { DocumentTitle } from 'ts/components/document_title'; import { Hero } from 'ts/components/hero'; import { Column, Section, WrapSticky } from 'ts/components/newLayout'; import { SiteWrap } from 'ts/components/siteWrap'; import { Slide, Slider } from 'ts/components/slider/slider'; import { Heading } from 'ts/components/text'; +import { documentConstants } from 'ts/utils/document_meta_constants'; import { ModalContact } from '../components/modals/modal_contact'; @@ -99,7 +100,7 @@ export class NextWhy extends React.Component { ); return ( - + = 1.0.0": version "1.0.1" @@ -13478,6 +13504,15 @@ react-dom@^16.3.2: object-assign "^4.1.1" prop-types "^15.6.0" +react-dom@^16.4.2: + version "16.8.1" + resolved "https://registry.npmjs.org/react-dom/-/react-dom-16.8.1.tgz#ec860f98853d09d39bafd3a6f1e12389d283dbb4" + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.13.1" + react-dom@^16.5.2: version "16.5.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.5.2.tgz#b69ee47aa20bab5327b2b9d7c1fe2a30f2cfa9d7" @@ -13781,6 +13816,15 @@ react@^16.3.2: object-assign "^4.1.1" prop-types "^15.6.0" +react@^16.4.2: + version "16.8.1" + resolved "https://registry.npmjs.org/react/-/react-16.8.1.tgz#ae11831f6cb2a05d58603a976afc8a558e852c4a" + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.13.1" + react@^16.5.2: version "16.5.2" resolved "https://registry.yarnpkg.com/react/-/react-16.5.2.tgz#19f6b444ed139baa45609eee6dc3d318b3895d42" @@ -14652,6 +14696,13 @@ schedule@^0.5.0: dependencies: object-assign "^4.1.1" +scheduler@^0.13.1: + version "0.13.1" + resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.13.1.tgz#1a217df1bfaabaf4f1b92a9127d5d732d85a9591" + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + schema-utils@^0.4.4: version "0.4.7" resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187"