diff options
author | Brandon Millman <brandon.millman@gmail.com> | 2018-08-24 01:52:09 +0800 |
---|---|---|
committer | Brandon Millman <brandon.millman@gmail.com> | 2018-08-24 01:52:09 +0800 |
commit | 7ad1a18a4aaa3da23fc013c55babea286a505f9d (patch) | |
tree | ae0e8267091b45dcdf661ec8cd76ce7699a82364 /packages/connect/src | |
parent | 572ad4615a3a0fa5649549cbd65eb912ea7a0e92 (diff) | |
parent | b013cf2e145825a9e250c61bc77825384b2af69e (diff) | |
download | dexon-sol-tools-7ad1a18a4aaa3da23fc013c55babea286a505f9d.tar dexon-sol-tools-7ad1a18a4aaa3da23fc013c55babea286a505f9d.tar.gz dexon-sol-tools-7ad1a18a4aaa3da23fc013c55babea286a505f9d.tar.bz2 dexon-sol-tools-7ad1a18a4aaa3da23fc013c55babea286a505f9d.tar.lz dexon-sol-tools-7ad1a18a4aaa3da23fc013c55babea286a505f9d.tar.xz dexon-sol-tools-7ad1a18a4aaa3da23fc013c55babea286a505f9d.tar.zst dexon-sol-tools-7ad1a18a4aaa3da23fc013c55babea286a505f9d.zip |
Merge branch 'development' into feature/forwarder-helper/init
* development: (31 commits)
Update CI config to check development instead of v2-prototype branch
fix: Make non-interactive npm login for publish tests more robust
Run prettier
Remove comment in updates json-schemas doc
Add documentation for json-schemas
Fix typo
Remove artifacts
Update variable names, make release candidate
Update more names in docs
Improve documentation
Pin sra-report to 0xproject/connect v1.0.4
Run prettier
lint everything
Update websocket for SRA v2
Remove default query parameters
Add networkId request param option
Rename websocket files and types
Use uuid as string for requestId
Update changelogs
Add fee recipients test
...
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; } |