diff options
Diffstat (limited to 'packages/connect/src')
19 files changed, 386 insertions, 322 deletions
diff --git a/packages/connect/src/http_client.ts b/packages/connect/src/http_client.ts index 03cc590e4..b90c2c35f 100644 --- a/packages/connect/src/http_client.ts +++ b/packages/connect/src/http_client.ts @@ -7,31 +7,25 @@ import * as queryString from 'query-string'; import { schemas as clientSchemas } from './schemas/schemas'; import { + APIOrder, + AssetPairsRequestOpts, + AssetPairsResponse, Client, - FeesRequest, - FeesResponse, + FeeRecipientsResponse, HttpRequestOptions, HttpRequestType, OrderbookRequest, OrderbookResponse, + OrderConfigRequest, + OrderConfigResponse, OrdersRequestOpts, + OrdersResponse, PagedRequestOpts, - TokenPairsItem, - TokenPairsRequestOpts, + RequestOpts, } from './types'; import { relayerResponseJsonParsers } from './utils/relayer_response_json_parsers'; const TRAILING_SLASHES_REGEX = /\/+$/; -const DEFAULT_PAGED_REQUEST_OPTS: PagedRequestOpts = { - page: 1, - perPage: 100, -}; -/** - * This mapping defines how an option property name gets converted into an HTTP request query field - */ -const OPTS_TO_QUERY_FIELD_MAP = { - perPage: 'per_page', -}; /** * This class includes all the functionality related to interacting with a set of HTTP endpoints @@ -47,12 +41,8 @@ export class HttpClient implements Client { if (_.isUndefined(params) || _.isEmpty(params)) { return ''; } - // format params into a form the api expects - const formattedParams = _.mapKeys(params, (_value: any, key: string) => { - return _.get(OPTS_TO_QUERY_FIELD_MAP, key, key); - }); // stringify the formatted object - const stringifiedParams = queryString.stringify(formattedParams); + const stringifiedParams = queryString.stringify(params); return `?${stringifiedParams}`; } /** @@ -65,34 +55,40 @@ export class HttpClient implements Client { this._apiEndpointUrl = url.replace(TRAILING_SLASHES_REGEX, ''); // remove trailing slashes } /** - * Retrieve token pair info from the API - * @param requestOpts Options specifying token information to retrieve and page information, defaults to { page: 1, perPage: 100 } - * @return The resulting TokenPairsItems that match the request + * Retrieve assetData pair info from the API + * @param requestOpts Options specifying assetData information to retrieve, page information, and network id. + * @return The resulting AssetPairsResponse that match the request */ - public async getTokenPairsAsync(requestOpts?: TokenPairsRequestOpts & PagedRequestOpts): Promise<TokenPairsItem[]> { + public async getAssetPairsAsync( + requestOpts?: RequestOpts & AssetPairsRequestOpts & PagedRequestOpts, + ): Promise<AssetPairsResponse> { if (!_.isUndefined(requestOpts)) { - assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.tokenPairsRequestOptsSchema); + assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.assetPairsRequestOptsSchema); assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.pagedRequestOptsSchema); + assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.requestOptsSchema); } const httpRequestOpts = { - params: _.defaults({}, requestOpts, DEFAULT_PAGED_REQUEST_OPTS), + params: requestOpts, }; - const responseJson = await this._requestAsync('/token_pairs', HttpRequestType.Get, httpRequestOpts); - const tokenPairs = relayerResponseJsonParsers.parseTokenPairsJson(responseJson); - return tokenPairs; + const responseJson = await this._requestAsync('/asset_pairs', HttpRequestType.Get, httpRequestOpts); + const assetDataPairs = relayerResponseJsonParsers.parseAssetDataPairsJson(responseJson); + return assetDataPairs; } /** * Retrieve orders from the API - * @param requestOpts Options specifying orders to retrieve and page information, defaults to { page: 1, perPage: 100 } - * @return The resulting SignedOrders that match the request + * @param requestOpts Options specifying orders to retrieve and page information, page information, and network id. + * @return The resulting OrdersResponse that match the request */ - public async getOrdersAsync(requestOpts?: OrdersRequestOpts & PagedRequestOpts): Promise<SignedOrder[]> { + public async getOrdersAsync( + requestOpts?: RequestOpts & OrdersRequestOpts & PagedRequestOpts, + ): Promise<OrdersResponse> { if (!_.isUndefined(requestOpts)) { assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.ordersRequestOptsSchema); assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.pagedRequestOptsSchema); + assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.requestOptsSchema); } const httpRequestOpts = { - params: _.defaults({}, requestOpts, DEFAULT_PAGED_REQUEST_OPTS), + params: requestOpts, }; const responseJson = await this._requestAsync(`/orders`, HttpRequestType.Get, httpRequestOpts); const orders = relayerResponseJsonParsers.parseOrdersJson(responseJson); @@ -101,30 +97,37 @@ export class HttpClient implements Client { /** * Retrieve a specific order from the API * @param orderHash An orderHash generated from the desired order - * @return The SignedOrder that matches the supplied orderHash + * @return The APIOrder that matches the supplied orderHash */ - public async getOrderAsync(orderHash: string): Promise<SignedOrder> { + public async getOrderAsync(orderHash: string, requestOpts?: RequestOpts): Promise<APIOrder> { + if (!_.isUndefined(requestOpts)) { + assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.requestOptsSchema); + } assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); - const responseJson = await this._requestAsync(`/order/${orderHash}`, HttpRequestType.Get); - const order = relayerResponseJsonParsers.parseOrderJson(responseJson); + const httpRequestOpts = { + params: requestOpts, + }; + const responseJson = await this._requestAsync(`/order/${orderHash}`, HttpRequestType.Get, httpRequestOpts); + const order = relayerResponseJsonParsers.parseAPIOrderJson(responseJson); return order; } /** * Retrieve an orderbook from the API * @param request An OrderbookRequest instance describing the specific orderbook to retrieve - * @param requestOpts Options specifying page information, defaults to { page: 1, perPage: 100 } + * @param requestOpts Options specifying page information, and network id. * @return The resulting OrderbookResponse that matches the request */ public async getOrderbookAsync( request: OrderbookRequest, - requestOpts?: PagedRequestOpts, + requestOpts?: RequestOpts & PagedRequestOpts, ): Promise<OrderbookResponse> { assert.doesConformToSchema('request', request, clientSchemas.orderBookRequestSchema); if (!_.isUndefined(requestOpts)) { assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.pagedRequestOptsSchema); + assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.requestOptsSchema); } const httpRequestOpts = { - params: _.defaults({}, request, requestOpts, DEFAULT_PAGED_REQUEST_OPTS), + params: _.defaults({}, request, requestOpts), }; const responseJson = await this._requestAsync('/orderbook', HttpRequestType.Get, httpRequestOpts); const orderbook = relayerResponseJsonParsers.parseOrderbookResponseJson(responseJson); @@ -132,28 +135,55 @@ export class HttpClient implements Client { } /** * Retrieve fee information from the API - * @param request A FeesRequest instance describing the specific fees to retrieve - * @return The resulting FeesResponse that matches the request + * @param request A OrderConfigRequest instance describing the specific fees to retrieve + * @param requestOpts Options specifying network id. + * @return The resulting OrderConfigResponse that matches the request */ - public async getFeesAsync(request: FeesRequest): Promise<FeesResponse> { - assert.doesConformToSchema('request', request, clientSchemas.feesRequestSchema); + public async getOrderConfigAsync( + request: OrderConfigRequest, + requestOpts?: RequestOpts, + ): Promise<OrderConfigResponse> { + if (!_.isUndefined(requestOpts)) { + assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.requestOptsSchema); + } + assert.doesConformToSchema('request', request, clientSchemas.orderConfigRequestSchema); const httpRequestOpts = { + params: requestOpts, payload: request, }; - const responseJson = await this._requestAsync('/fees', HttpRequestType.Post, httpRequestOpts); - const fees = relayerResponseJsonParsers.parseFeesResponseJson(responseJson); + const responseJson = await this._requestAsync('/order_config', HttpRequestType.Post, httpRequestOpts); + const fees = relayerResponseJsonParsers.parseOrderConfigResponseJson(responseJson); return fees; } /** + * Retrieve the list of fee recipient addresses used by the relayer. + * @param requestOpts Options specifying page information, and network id. + * @return The resulting FeeRecipientsResponse + */ + public async getFeeRecipientsAsync(requestOpts?: RequestOpts & PagedRequestOpts): Promise<FeeRecipientsResponse> { + if (!_.isUndefined(requestOpts)) { + assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.pagedRequestOptsSchema); + assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.requestOptsSchema); + } + const httpRequestOpts = { + params: requestOpts, + }; + const feeRecipients = await this._requestAsync('/fee_recipients', HttpRequestType.Get, httpRequestOpts); + assert.doesConformToSchema('feeRecipients', feeRecipients, schemas.relayerApiFeeRecipientsResponseSchema); + return feeRecipients; + } + /** * Submit a signed order to the API * @param signedOrder A SignedOrder instance to submit + * @param requestOpts Options specifying network id. */ - public async submitOrderAsync(signedOrder: SignedOrder): Promise<void> { + public async submitOrderAsync(signedOrder: SignedOrder, requestOpts?: RequestOpts): Promise<void> { assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - const requestOpts = { + const httpRequestOpts = { + params: requestOpts, payload: signedOrder, }; - await this._requestAsync('/order', HttpRequestType.Post, requestOpts); + await this._requestAsync('/order', HttpRequestType.Post, httpRequestOpts); } private async _requestAsync( path: string, diff --git a/packages/connect/src/index.ts b/packages/connect/src/index.ts index 7f5eb8ed3..33e1222b0 100644 --- a/packages/connect/src/index.ts +++ b/packages/connect/src/index.ts @@ -1,19 +1,19 @@ export { HttpClient } from './http_client'; -export { orderbookChannelFactory } from './orderbook_channel_factory'; +export { ordersChannelFactory } from './orders_channel_factory'; export { Client, - FeesRequest, - FeesResponse, - OrderbookChannel, - OrderbookChannelHandler, - OrderbookChannelSubscriptionOpts, + OrderConfigRequest, + OrderConfigResponse, + OrdersChannel, + OrdersChannelHandler, + OrdersChannelSubscriptionOpts, OrderbookRequest, OrderbookResponse, OrdersRequestOpts, PagedRequestOpts, - TokenPairsItem, - TokenPairsRequestOpts, - TokenTradeInfo, + AssetPairsItem, + AssetPairsRequestOpts, + Asset, } from './types'; export { Order, SignedOrder } from '@0xproject/types'; diff --git a/packages/connect/src/orderbook_channel_factory.ts b/packages/connect/src/orderbook_channel_factory.ts deleted file mode 100644 index 5134af323..000000000 --- a/packages/connect/src/orderbook_channel_factory.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as WebSocket from 'websocket'; - -import { OrderbookChannel, OrderbookChannelHandler } from './types'; -import { assert } from './utils/assert'; -import { WebSocketOrderbookChannel } from './ws_orderbook_channel'; - -export const orderbookChannelFactory = { - /** - * Instantiates a new WebSocketOrderbookChannel instance - * @param url The relayer API base WS url you would like to interact with - * @param handler An OrderbookChannelHandler instance that responds to various - * channel updates - * @return An OrderbookChannel Promise - */ - async createWebSocketOrderbookChannelAsync( - url: string, - handler: OrderbookChannelHandler, - ): Promise<OrderbookChannel> { - assert.isUri('url', url); - assert.isOrderbookChannelHandler('handler', handler); - return new Promise<OrderbookChannel>((resolve, reject) => { - const client = new WebSocket.w3cwebsocket(url); - client.onopen = () => { - const orderbookChannel = new WebSocketOrderbookChannel(client, handler); - resolve(orderbookChannel); - }; - client.onerror = err => { - reject(err); - }; - }); - }, -}; diff --git a/packages/connect/src/orders_channel_factory.ts b/packages/connect/src/orders_channel_factory.ts new file mode 100644 index 000000000..5986d2a77 --- /dev/null +++ b/packages/connect/src/orders_channel_factory.ts @@ -0,0 +1,29 @@ +import * as WebSocket from 'websocket'; + +import { OrdersChannel, OrdersChannelHandler } from './types'; +import { assert } from './utils/assert'; +import { WebSocketOrdersChannel } from './ws_orders_channel'; + +export const ordersChannelFactory = { + /** + * Instantiates a new WebSocketOrdersChannel instance + * @param url The relayer API base WS url you would like to interact with + * @param handler An OrdersChannelHandler instance that responds to various + * channel updates + * @return An OrdersChannel Promise + */ + async createWebSocketOrdersChannelAsync(url: string, handler: OrdersChannelHandler): Promise<OrdersChannel> { + assert.isUri('url', url); + assert.isOrdersChannelHandler('handler', handler); + return new Promise<OrdersChannel>((resolve, reject) => { + const client = new WebSocket.w3cwebsocket(url); + client.onopen = () => { + const ordersChannel = new WebSocketOrdersChannel(client, handler); + resolve(ordersChannel); + }; + client.onerror = err => { + reject(err); + }; + }); + }, +}; diff --git a/packages/connect/src/schemas/asset_pairs_request_opts_schema.ts b/packages/connect/src/schemas/asset_pairs_request_opts_schema.ts new file mode 100644 index 000000000..a9e3942a4 --- /dev/null +++ b/packages/connect/src/schemas/asset_pairs_request_opts_schema.ts @@ -0,0 +1,8 @@ +export const assetPairsRequestOptsSchema = { + id: '/AssetPairsRequestOpts', + type: 'object', + properties: { + assetDataA: { $ref: '/hexSchema' }, + assetDataB: { $ref: '/hexSchema' }, + }, +}; diff --git a/packages/connect/src/schemas/fees_request_schema.ts b/packages/connect/src/schemas/fees_request_schema.ts deleted file mode 100644 index ff3d7b9d3..000000000 --- a/packages/connect/src/schemas/fees_request_schema.ts +++ /dev/null @@ -1,26 +0,0 @@ -export const feesRequestSchema = { - id: '/FeesRequest', - type: 'object', - properties: { - exchangeContractAddress: { $ref: '/Address' }, - maker: { $ref: '/Address' }, - taker: { $ref: '/Address' }, - makerTokenAddress: { $ref: '/Address' }, - takerTokenAddress: { $ref: '/Address' }, - makerTokenAmount: { $ref: '/Number' }, - takerTokenAmount: { $ref: '/Number' }, - expirationUnixTimestampSec: { $ref: '/Number' }, - salt: { $ref: '/Number' }, - }, - required: [ - 'exchangeContractAddress', - 'maker', - 'taker', - 'makerTokenAddress', - 'takerTokenAddress', - 'makerTokenAmount', - 'takerTokenAmount', - 'expirationUnixTimestampSec', - 'salt', - ], -}; diff --git a/packages/connect/src/schemas/order_config_request_schema.ts b/packages/connect/src/schemas/order_config_request_schema.ts new file mode 100644 index 000000000..0eda430e8 --- /dev/null +++ b/packages/connect/src/schemas/order_config_request_schema.ts @@ -0,0 +1,24 @@ +export const orderConfigRequestSchema = { + id: '/OrderConfigRequest', + type: 'object', + properties: { + makerAddress: { $ref: '/addressSchema' }, + takerAddress: { $ref: '/addressSchema' }, + makerAssetAmount: { $ref: '/numberSchema' }, + takerAssetAmount: { $ref: '/numberSchema' }, + makerAssetData: { $ref: '/hexSchema' }, + takerAssetData: { $ref: '/hexSchema' }, + exchangeAddress: { $ref: '/addressSchema' }, + expirationTimeSeconds: { $ref: '/numberSchema' }, + }, + required: [ + 'makerAddress', + 'takerAddress', + 'makerAssetAmount', + 'takerAssetAmount', + 'makerAssetData', + 'takerAssetData', + 'exchangeAddress', + 'expirationTimeSeconds', + ], +}; diff --git a/packages/connect/src/schemas/orderbook_request_schema.ts b/packages/connect/src/schemas/orderbook_request_schema.ts index 5f3463242..0c9389d50 100644 --- a/packages/connect/src/schemas/orderbook_request_schema.ts +++ b/packages/connect/src/schemas/orderbook_request_schema.ts @@ -2,8 +2,8 @@ export const orderBookRequestSchema = { id: '/OrderBookRequest', type: 'object', properties: { - baseTokenAddress: { $ref: '/Address' }, - quoteTokenAddress: { $ref: '/Address' }, + baseAssetData: { $ref: '/hexSchema' }, + quoteAssetData: { $ref: '/hexSchema' }, }, - required: ['baseTokenAddress', 'quoteTokenAddress'], + required: ['baseAssetData', 'quoteAssetData'], }; diff --git a/packages/connect/src/schemas/orders_request_opts_schema.ts b/packages/connect/src/schemas/orders_request_opts_schema.ts index 5facbc959..71ce3d06f 100644 --- a/packages/connect/src/schemas/orders_request_opts_schema.ts +++ b/packages/connect/src/schemas/orders_request_opts_schema.ts @@ -2,15 +2,18 @@ export const ordersRequestOptsSchema = { id: '/OrdersRequestOpts', type: 'object', properties: { - exchangeContractAddress: { $ref: '/Address' }, - tokenAddress: { $ref: '/Address' }, - makerTokenAddress: { $ref: '/Address' }, - takerTokenAddress: { $ref: '/Address' }, - tokenA: { $ref: '/Address' }, - tokenB: { $ref: '/Address' }, - maker: { $ref: '/Address' }, - taker: { $ref: '/Address' }, - trader: { $ref: '/Address' }, - feeRecipient: { $ref: '/Address' }, + makerAssetProxyId: { $ref: '/hexSchema' }, + takerAssetProxyId: { $ref: '/hexSchema' }, + makerAssetAddress: { $ref: '/addressSchema' }, + takerAssetAddress: { $ref: '/addressSchema' }, + exchangeAddress: { $ref: '/addressSchema' }, + senderAddress: { $ref: '/addressSchema' }, + makerAssetData: { $ref: '/hexSchema' }, + takerAssetData: { $ref: '/hexSchema' }, + traderAssetData: { $ref: '/hexSchema' }, + makerAddress: { $ref: '/addressSchema' }, + takerAddress: { $ref: '/addressSchema' }, + traderAddress: { $ref: '/addressSchema' }, + feeRecipientAddress: { $ref: '/addressSchema' }, }, }; diff --git a/packages/connect/src/schemas/request_opts_schema.ts b/packages/connect/src/schemas/request_opts_schema.ts new file mode 100644 index 000000000..a51e98069 --- /dev/null +++ b/packages/connect/src/schemas/request_opts_schema.ts @@ -0,0 +1,7 @@ +export const requestOptsSchema = { + id: '/RequestOpts', + type: 'object', + properties: { + networkId: { type: 'number' }, + }, +}; diff --git a/packages/connect/src/schemas/schemas.ts b/packages/connect/src/schemas/schemas.ts index 0b8b798a9..8d101ed6f 100644 --- a/packages/connect/src/schemas/schemas.ts +++ b/packages/connect/src/schemas/schemas.ts @@ -1,13 +1,15 @@ -import { feesRequestSchema } from './fees_request_schema'; +import { assetPairsRequestOptsSchema } from './asset_pairs_request_opts_schema'; +import { orderConfigRequestSchema } from './order_config_request_schema'; import { orderBookRequestSchema } from './orderbook_request_schema'; import { ordersRequestOptsSchema } from './orders_request_opts_schema'; import { pagedRequestOptsSchema } from './paged_request_opts_schema'; -import { tokenPairsRequestOptsSchema } from './token_pairs_request_opts_schema'; +import { requestOptsSchema } from './request_opts_schema'; export const schemas = { - feesRequestSchema, + orderConfigRequestSchema, orderBookRequestSchema, ordersRequestOptsSchema, pagedRequestOptsSchema, - tokenPairsRequestOptsSchema, + requestOptsSchema, + assetPairsRequestOptsSchema, }; diff --git a/packages/connect/src/schemas/token_pairs_request_opts_schema.ts b/packages/connect/src/schemas/token_pairs_request_opts_schema.ts deleted file mode 100644 index 9b73a917b..000000000 --- a/packages/connect/src/schemas/token_pairs_request_opts_schema.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const tokenPairsRequestOptsSchema = { - id: '/TokenPairsRequestOpts', - type: 'object', - properties: { - tokenA: { $ref: '/Address' }, - tokenB: { $ref: '/Address' }, - }, -}; diff --git a/packages/connect/src/types.ts b/packages/connect/src/types.ts index fc7a4b24d..06ae732a5 100644 --- a/packages/connect/src/types.ts +++ b/packages/connect/src/types.ts @@ -2,73 +2,55 @@ import { SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; export interface Client { - getTokenPairsAsync: (requestOpts?: TokenPairsRequestOpts & PagedRequestOpts) => Promise<TokenPairsItem[]>; - getOrdersAsync: (requestOpts?: OrdersRequestOpts & PagedRequestOpts) => Promise<SignedOrder[]>; - getOrderAsync: (orderHash: string) => Promise<SignedOrder>; + getAssetPairsAsync: ( + requestOpts?: AssetPairsRequestOpts & PagedRequestOpts, + ) => Promise<PaginatedCollection<AssetPairsItem>>; + getOrdersAsync: (requestOpts?: OrdersRequestOpts & PagedRequestOpts) => Promise<PaginatedCollection<APIOrder>>; + getOrderAsync: (orderHash: string) => Promise<APIOrder>; getOrderbookAsync: (request: OrderbookRequest, requestOpts?: PagedRequestOpts) => Promise<OrderbookResponse>; - getFeesAsync: (request: FeesRequest) => Promise<FeesResponse>; + getOrderConfigAsync: (request: OrderConfigRequest) => Promise<OrderConfigResponse>; + getFeeRecipientsAsync: (requestOpts?: PagedRequestOpts) => Promise<FeeRecipientsResponse>; submitOrderAsync: (signedOrder: SignedOrder) => Promise<void>; } -export interface OrderbookChannel { - subscribe: (subscriptionOpts: OrderbookChannelSubscriptionOpts) => void; +export interface OrdersChannel { + subscribe: (subscriptionOpts: OrdersChannelSubscriptionOpts) => void; close: () => void; } /** - * baseTokenAddress: The address of token designated as the baseToken in the currency pair calculation of price - * quoteTokenAddress: The address of token designated as the quoteToken in the currency pair calculation of price - * snapshot: If true, a snapshot of the orderbook will be sent before the updates to the orderbook + * baseAssetData: The address of assetData designated as the baseToken in the currency pair calculation of price + * quoteAssetData: The address of assetData designated as the quoteToken in the currency pair calculation of price * limit: Maximum number of bids and asks in orderbook snapshot */ -export interface OrderbookChannelSubscriptionOpts { - baseTokenAddress: string; - quoteTokenAddress: string; - snapshot: boolean; +export interface OrdersChannelSubscriptionOpts { + baseAssetData: string; + quoteAssetData: string; limit: number; } -export interface OrderbookChannelHandler { - onSnapshot: ( - channel: OrderbookChannel, - subscriptionOpts: OrderbookChannelSubscriptionOpts, - snapshot: OrderbookResponse, - ) => void; - onUpdate: ( - channel: OrderbookChannel, - subscriptionOpts: OrderbookChannelSubscriptionOpts, - order: SignedOrder, - ) => void; - onError: (channel: OrderbookChannel, err: Error, subscriptionOpts?: OrderbookChannelSubscriptionOpts) => void; - onClose: (channel: OrderbookChannel) => void; -} - -export type OrderbookChannelMessage = - | SnapshotOrderbookChannelMessage - | UpdateOrderbookChannelMessage - | UnknownOrderbookChannelMessage; - -export enum OrderbookChannelMessageTypes { - Snapshot = 'snapshot', - Update = 'update', - Unknown = 'unknown', +export interface OrdersChannelHandler { + onUpdate: (channel: OrdersChannel, subscriptionOpts: OrdersChannelSubscriptionOpts, orders: APIOrder[]) => void; + onError: (channel: OrdersChannel, err: Error, subscriptionOpts?: OrdersChannelSubscriptionOpts) => void; + onClose: (channel: OrdersChannel) => void; } -export interface SnapshotOrderbookChannelMessage { - type: OrderbookChannelMessageTypes.Snapshot; - requestId: number; - payload: OrderbookResponse; +export type OrdersChannelMessage = UpdateOrdersChannelMessage | UnknownOrdersChannelMessage; + +export enum OrdersChannelMessageTypes { + Update = 'update', + Unknown = 'unknown', } -export interface UpdateOrderbookChannelMessage { - type: OrderbookChannelMessageTypes.Update; - requestId: number; - payload: SignedOrder; +export interface UpdateOrdersChannelMessage { + type: OrdersChannelMessageTypes.Update; + requestId: string; + payload: APIOrder[]; } -export interface UnknownOrderbookChannelMessage { - type: OrderbookChannelMessageTypes.Unknown; - requestId: number; +export interface UnknownOrdersChannelMessage { + type: OrdersChannelMessageTypes.Unknown; + requestId: string; payload: undefined; } @@ -83,60 +65,86 @@ export enum WebsocketClientEventType { ConnectFailed = 'connectFailed', } -export interface TokenPairsRequestOpts { - tokenA?: string; - tokenB?: string; +export type OrdersResponse = PaginatedCollection<APIOrder>; + +export interface APIOrder { + order: SignedOrder; + metaData: object; +} + +export interface AssetPairsRequestOpts { + assetDataA?: string; + assetDataB?: string; } -export interface TokenPairsItem { - tokenA: TokenTradeInfo; - tokenB: TokenTradeInfo; +export type AssetPairsResponse = PaginatedCollection<AssetPairsItem>; + +export interface AssetPairsItem { + assetDataA: Asset; + assetDataB: Asset; } -export interface TokenTradeInfo { - address: string; +export interface Asset { + assetData: string; minAmount: BigNumber; maxAmount: BigNumber; precision: number; } export interface OrdersRequestOpts { - exchangeContractAddress?: string; - tokenAddress?: string; - makerTokenAddress?: string; - takerTokenAddress?: string; - maker?: string; - taker?: string; - trader?: string; - feeRecipient?: string; + makerAssetProxyId?: string; + takerAssetProxyId?: string; + makerAssetAddress?: string; + takerAssetAddress?: string; + exchangeAddress?: string; + senderAddress?: string; + makerAssetData?: string; + takerAssetData?: string; + makerAddress?: string; + takerAddress?: string; + traderAddress?: string; + feeRecipientAddress?: string; } export interface OrderbookRequest { - baseTokenAddress: string; - quoteTokenAddress: string; + baseAssetData: string; + quoteAssetData: string; } export interface OrderbookResponse { - bids: SignedOrder[]; - asks: SignedOrder[]; + bids: PaginatedCollection<APIOrder>; + asks: PaginatedCollection<APIOrder>; +} + +export interface PaginatedCollection<T> { + total: number; + page: number; + perPage: number; + records: T[]; } -export interface FeesRequest { - exchangeContractAddress: string; - maker: string; - taker: string; - makerTokenAddress: string; - takerTokenAddress: string; - makerTokenAmount: BigNumber; - takerTokenAmount: BigNumber; - expirationUnixTimestampSec: BigNumber; - salt: BigNumber; +export interface OrderConfigRequest { + makerAddress: string; + takerAddress: string; + makerAssetAmount: string; + takerAssetAmount: string; + makerAssetData: string; + takerAssetData: string; + exchangeAddress: string; + expirationTimeSeconds: string; } -export interface FeesResponse { - feeRecipient: string; +export interface OrderConfigResponse { makerFee: BigNumber; takerFee: BigNumber; + feeRecipientAddress: string; + senderAddress: string; +} + +export type FeeRecipientsResponse = PaginatedCollection<string>; + +export interface RequestOpts { + networkId?: number; } export interface PagedRequestOpts { diff --git a/packages/connect/src/utils/assert.ts b/packages/connect/src/utils/assert.ts index a0fd12fbd..3d8f1c799 100644 --- a/packages/connect/src/utils/assert.ts +++ b/packages/connect/src/utils/assert.ts @@ -10,15 +10,14 @@ import * as _ from 'lodash'; export const assert = { ...sharedAssert, - isOrderbookChannelSubscriptionOpts(variableName: string, subscriptionOpts: any): void { + isOrdersChannelSubscriptionOpts(variableName: string, subscriptionOpts: any): void { sharedAssert.doesConformToSchema( variableName, subscriptionOpts, - schemas.relayerApiOrderbookChannelSubscribePayload, + schemas.relayerApiOrdersChannelSubscribePayload, ); }, - isOrderbookChannelHandler(variableName: string, handler: any): void { - sharedAssert.isFunction(`${variableName}.onSnapshot`, _.get(handler, 'onSnapshot')); + isOrdersChannelHandler(variableName: string, handler: any): void { sharedAssert.isFunction(`${variableName}.onUpdate`, _.get(handler, 'onUpdate')); sharedAssert.isFunction(`${variableName}.onError`, _.get(handler, 'onError')); sharedAssert.isFunction(`${variableName}.onClose`, _.get(handler, 'onClose')); diff --git a/packages/connect/src/utils/orderbook_channel_message_parser.ts b/packages/connect/src/utils/orderbook_channel_message_parser.ts deleted file mode 100644 index 593288078..000000000 --- a/packages/connect/src/utils/orderbook_channel_message_parser.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { assert } from '@0xproject/assert'; -import { schemas } from '@0xproject/json-schemas'; -import * as _ from 'lodash'; - -import { OrderbookChannelMessage, OrderbookChannelMessageTypes } from '../types'; - -import { relayerResponseJsonParsers } from './relayer_response_json_parsers'; - -export const orderbookChannelMessageParser = { - parse(utf8Data: string): OrderbookChannelMessage { - // parse the message - const messageObj = JSON.parse(utf8Data); - // ensure we have a type parameter to switch on - const type: string = _.get(messageObj, 'type'); - assert.assert(!_.isUndefined(type), `Message is missing a type parameter: ${utf8Data}`); - assert.isString('type', type); - // ensure we have a request id for the resulting message - const requestId: number = _.get(messageObj, 'requestId'); - assert.assert(!_.isUndefined(requestId), `Message is missing a requestId parameter: ${utf8Data}`); - assert.isNumber('requestId', requestId); - switch (type) { - case OrderbookChannelMessageTypes.Snapshot: { - assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelSnapshotSchema); - const orderbookJson = messageObj.payload; - const orderbook = relayerResponseJsonParsers.parseOrderbookResponseJson(orderbookJson); - return _.assign(messageObj, { payload: orderbook }); - } - case OrderbookChannelMessageTypes.Update: { - assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelUpdateSchema); - const orderJson = messageObj.payload; - const order = relayerResponseJsonParsers.parseOrderJson(orderJson); - return _.assign(messageObj, { payload: order }); - } - default: { - return { - type: OrderbookChannelMessageTypes.Unknown, - requestId, - payload: undefined, - }; - } - } - }, -}; diff --git a/packages/connect/src/utils/orders_channel_message_parser.ts b/packages/connect/src/utils/orders_channel_message_parser.ts new file mode 100644 index 000000000..1b6cda17b --- /dev/null +++ b/packages/connect/src/utils/orders_channel_message_parser.ts @@ -0,0 +1,37 @@ +import { assert } from '@0xproject/assert'; +import { schemas } from '@0xproject/json-schemas'; +import * as _ from 'lodash'; + +import { OrdersChannelMessage, OrdersChannelMessageTypes } from '../types'; + +import { relayerResponseJsonParsers } from './relayer_response_json_parsers'; + +export const ordersChannelMessageParser = { + parse(utf8Data: string): OrdersChannelMessage { + // parse the message + const messageObj = JSON.parse(utf8Data); + // ensure we have a type parameter to switch on + const type: string = _.get(messageObj, 'type'); + assert.assert(!_.isUndefined(type), `Message is missing a type parameter: ${utf8Data}`); + assert.isString('type', type); + // ensure we have a request id for the resulting message + const requestId: string = _.get(messageObj, 'requestId'); + assert.assert(!_.isUndefined(requestId), `Message is missing a requestId parameter: ${utf8Data}`); + assert.isString('requestId', requestId); + switch (type) { + case OrdersChannelMessageTypes.Update: { + assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrdersChannelUpdateSchema); + const ordersJson = messageObj.payload; + const orders = relayerResponseJsonParsers.parseAPIOrdersJson(ordersJson); + return _.assign(messageObj, { payload: orders }); + } + default: { + return { + type: OrdersChannelMessageTypes.Unknown, + requestId, + payload: undefined, + }; + } + } + }, +}; diff --git a/packages/connect/src/utils/relayer_response_json_parsers.ts b/packages/connect/src/utils/relayer_response_json_parsers.ts index ccae8b115..ebd877b70 100644 --- a/packages/connect/src/utils/relayer_response_json_parsers.ts +++ b/packages/connect/src/utils/relayer_response_json_parsers.ts @@ -1,37 +1,49 @@ import { assert } from '@0xproject/assert'; import { schemas } from '@0xproject/json-schemas'; -import { SignedOrder } from '@0xproject/types'; -import { FeesResponse, OrderbookResponse, TokenPairsItem } from '../types'; +import { + APIOrder, + AssetPairsItem, + AssetPairsResponse, + OrderbookResponse, + OrderConfigResponse, + OrdersResponse, +} from '../types'; import { typeConverters } from './type_converters'; export const relayerResponseJsonParsers = { - parseTokenPairsJson(json: any): TokenPairsItem[] { - assert.doesConformToSchema('tokenPairs', json, schemas.relayerApiTokenPairsResponseSchema); - return json.map((tokenPair: any) => { - return typeConverters.convertStringsFieldsToBigNumbers(tokenPair, [ - 'tokenA.minAmount', - 'tokenA.maxAmount', - 'tokenB.minAmount', - 'tokenB.maxAmount', + parseAssetDataPairsJson(json: any): AssetPairsResponse { + assert.doesConformToSchema('assetDataPairsResponse', json, schemas.relayerApiAssetDataPairsResponseSchema); + return { ...json, records: relayerResponseJsonParsers.parseAssetPairsItemsJson(json.records) }; + }, + parseAssetPairsItemsJson(json: any): AssetPairsItem[] { + return json.map((assetDataPair: any) => { + return typeConverters.convertStringsFieldsToBigNumbers(assetDataPair, [ + 'assetDataA.minAmount', + 'assetDataA.maxAmount', + 'assetDataB.minAmount', + 'assetDataB.maxAmount', ]); }); }, - parseOrdersJson(json: any): SignedOrder[] { - assert.doesConformToSchema('orders', json, schemas.signedOrdersSchema); - return json.map((order: object) => typeConverters.convertOrderStringFieldsToBigNumber(order)); + parseOrdersJson(json: any): OrdersResponse { + assert.doesConformToSchema('relayerApiOrdersResponse', json, schemas.relayerApiOrdersResponseSchema); + return { ...json, records: relayerResponseJsonParsers.parseAPIOrdersJson(json.records) }; + }, + parseAPIOrdersJson(json: any): APIOrder[] { + return json.map(relayerResponseJsonParsers.parseAPIOrderJson.bind(relayerResponseJsonParsers)); }, - parseOrderJson(json: any): SignedOrder { - assert.doesConformToSchema('order', json, schemas.signedOrderSchema); - return typeConverters.convertOrderStringFieldsToBigNumber(json); + parseAPIOrderJson(json: any): APIOrder { + assert.doesConformToSchema('relayerApiOrder', json, schemas.relayerApiOrderSchema); + return typeConverters.convertAPIOrderStringFieldsToBigNumber(json); }, parseOrderbookResponseJson(json: any): OrderbookResponse { - assert.doesConformToSchema('orderBook', json, schemas.relayerApiOrderBookResponseSchema); + assert.doesConformToSchema('orderBookResponse', json, schemas.relayerApiOrderbookResponseSchema); return typeConverters.convertOrderbookStringFieldsToBigNumber(json); }, - parseFeesResponseJson(json: any): FeesResponse { - assert.doesConformToSchema('fees', json, schemas.relayerApiFeesResponseSchema); + parseOrderConfigResponseJson(json: any): OrderConfigResponse { + assert.doesConformToSchema('orderConfigResponse', json, schemas.relayerApiOrderConfigResponseSchema); return typeConverters.convertStringsFieldsToBigNumbers(json, ['makerFee', 'takerFee']); }, }; diff --git a/packages/connect/src/utils/type_converters.ts b/packages/connect/src/utils/type_converters.ts index 210d452b9..4b211a0b2 100644 --- a/packages/connect/src/utils/type_converters.ts +++ b/packages/connect/src/utils/type_converters.ts @@ -1,29 +1,47 @@ import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; +import { APIOrder } from '../types'; + export const typeConverters = { convertOrderbookStringFieldsToBigNumber(orderbook: any): any { const bids = _.get(orderbook, 'bids', []); const asks = _.get(orderbook, 'asks', []); + const convertedBids = { + ...bids, + records: bids.records.map((order: any) => typeConverters.convertAPIOrderStringFieldsToBigNumber(order)), + }; + const convertedAsks = { + ...asks, + records: asks.records.map((order: any) => typeConverters.convertAPIOrderStringFieldsToBigNumber(order)), + }; return { - bids: bids.map((order: any) => typeConverters.convertOrderStringFieldsToBigNumber(order)), - asks: asks.map((order: any) => typeConverters.convertOrderStringFieldsToBigNumber(order)), + bids: convertedBids, + asks: convertedAsks, }; }, + convertAPIOrderStringFieldsToBigNumber(apiOrder: any): APIOrder { + return { ...apiOrder, order: typeConverters.convertOrderStringFieldsToBigNumber(apiOrder.order) }; + }, convertOrderStringFieldsToBigNumber(order: any): any { return typeConverters.convertStringsFieldsToBigNumbers(order, [ - 'makerTokenAmount', - 'takerTokenAmount', + 'makerAssetAmount', + 'takerAssetAmount', 'makerFee', 'takerFee', - 'expirationUnixTimestampSec', + 'expirationTimeSeconds', 'salt', ]); }, convertStringsFieldsToBigNumbers(obj: any, fields: string[]): any { const result = _.assign({}, obj); _.each(fields, field => { - _.update(result, field, (value: string) => new BigNumber(value)); + _.update(result, field, (value: string) => { + if (_.isUndefined(value)) { + throw new Error(`Could not find field '${field}' while converting string fields to BigNumber.`); + } + return new BigNumber(value); + }); }); return result; }, diff --git a/packages/connect/src/ws_orderbook_channel.ts b/packages/connect/src/ws_orders_channel.ts index fa9f5e37f..cde4acbc3 100644 --- a/packages/connect/src/ws_orderbook_channel.ts +++ b/packages/connect/src/ws_orders_channel.ts @@ -1,32 +1,32 @@ import * as _ from 'lodash'; +import { v4 as uuid } from 'uuid'; import * as WebSocket from 'websocket'; -import { - OrderbookChannel, - OrderbookChannelHandler, - OrderbookChannelMessageTypes, - OrderbookChannelSubscriptionOpts, -} from './types'; +import { OrdersChannel, OrdersChannelHandler, OrdersChannelMessageTypes, OrdersChannelSubscriptionOpts } from './types'; import { assert } from './utils/assert'; -import { orderbookChannelMessageParser } from './utils/orderbook_channel_message_parser'; +import { ordersChannelMessageParser } from './utils/orders_channel_message_parser'; + +export interface OrdersChannelSubscriptionOptsMap { + [key: string]: OrdersChannelSubscriptionOpts; +} /** * This class includes all the functionality related to interacting with a websocket endpoint * that implements the standard relayer API v0 */ -export class WebSocketOrderbookChannel implements OrderbookChannel { +export class WebSocketOrdersChannel implements OrdersChannel { private readonly _client: WebSocket.w3cwebsocket; - private readonly _handler: OrderbookChannelHandler; - private readonly _subscriptionOptsList: OrderbookChannelSubscriptionOpts[] = []; + private readonly _handler: OrdersChannelHandler; + private readonly _subscriptionOptsMap: OrdersChannelSubscriptionOptsMap = {}; /** - * Instantiates a new WebSocketOrderbookChannel instance + * Instantiates a new WebSocketOrdersChannel instance * @param client A WebSocket client - * @param handler An OrderbookChannelHandler instance that responds to various + * @param handler An OrdersChannelHandler instance that responds to various * channel updates - * @return An instance of WebSocketOrderbookChannel + * @return An instance of WebSocketOrdersChannel */ - constructor(client: WebSocket.w3cwebsocket, handler: OrderbookChannelHandler) { - assert.isOrderbookChannelHandler('handler', handler); + constructor(client: WebSocket.w3cwebsocket, handler: OrdersChannelHandler) { + assert.isOrdersChannelHandler('handler', handler); // set private members this._client = client; this._handler = handler; @@ -43,18 +43,18 @@ export class WebSocketOrderbookChannel implements OrderbookChannel { } /** * Subscribe to orderbook snapshots and updates from the websocket - * @param subscriptionOpts An OrderbookChannelSubscriptionOpts instance describing which - * token pair to subscribe to + * @param subscriptionOpts An OrdersChannelSubscriptionOpts instance describing which + * assetData pair to subscribe to */ - public subscribe(subscriptionOpts: OrderbookChannelSubscriptionOpts): void { - assert.isOrderbookChannelSubscriptionOpts('subscriptionOpts', subscriptionOpts); + public subscribe(subscriptionOpts: OrdersChannelSubscriptionOpts): void { + assert.isOrdersChannelSubscriptionOpts('subscriptionOpts', subscriptionOpts); assert.assert(this._client.readyState === WebSocket.w3cwebsocket.OPEN, 'WebSocket connection is closed'); - this._subscriptionOptsList.push(subscriptionOpts); - // TODO: update requestId management to use UUIDs for v2 + const requestId = uuid(); + this._subscriptionOptsMap[requestId] = subscriptionOpts; const subscribeMessage = { type: 'subscribe', - channel: 'orderbook', - requestId: this._subscriptionOptsList.length - 1, + channel: 'orders', + requestId, payload: subscriptionOpts, }; this._client.send(JSON.stringify(subscribeMessage)); @@ -72,8 +72,8 @@ export class WebSocketOrderbookChannel implements OrderbookChannel { } try { const data = message.data; - const parserResult = orderbookChannelMessageParser.parse(data); - const subscriptionOpts = this._subscriptionOptsList[parserResult.requestId]; + const parserResult = ordersChannelMessageParser.parse(data); + const subscriptionOpts = this._subscriptionOptsMap[parserResult.requestId]; if (_.isUndefined(subscriptionOpts)) { this._handler.onError( this, @@ -82,11 +82,7 @@ export class WebSocketOrderbookChannel implements OrderbookChannel { return; } switch (parserResult.type) { - case OrderbookChannelMessageTypes.Snapshot: { - this._handler.onSnapshot(this, subscriptionOpts, parserResult.payload); - break; - } - case OrderbookChannelMessageTypes.Update: { + case OrdersChannelMessageTypes.Update: { this._handler.onUpdate(this, subscriptionOpts, parserResult.payload); break; } |