import { BigNumber } from '@0x/utils' ; import * as express from 'express' ; import * as HttpStatus from 'http-status-codes' ; import * as isValidUUID from 'uuid-validate' ; import { FEE_RECIPIENT_ADDRESS , TAKER_FEE_UNIT_AMOUNT , WHITELISTED_TOKENS } from '../config' ; import { NULL_ADDRESS , SRA_DOCS_URL , ZERO } from '../constants' ; import { SignedOrderV4Entity } from '../entities' ; import { InvalidAPIKeyError , NotFoundError , ValidationError , ValidationErrorCodes } from '../errors' ; import { schemas } from '../schemas' ; import { OrderBookService } from '../services/orderbook_service' ; import { OrderConfigResponse , SignedLimitOrder } from '../types' ; import { paginationUtils } from '../utils/pagination_utils' ; import { schemaUtils } from '../utils/schema_utils' ; export class SRAHandlers { private readonly _orderBook : OrderBookService ; public static rootAsync ( _req , res ) { const message = `This is the root of the Standard Relayer API. Visit ${ SRA_DOCS_URL } for details about this API.` ; res . status ( HttpStatus . OK ) . send ( { message } ) ; } public static feeRecipients ( req , res ) { const { page , perPage } = paginationUtils . parsePaginationConfig ( req ) ; const normalizedFeeRecipient = FEE_RECIPIENT_ADDRESS . toLowerCase ( ) ; const feeRecipients = [ normalizedFeeRecipient ] ; const paginatedFeeRecipients = paginationUtils . paginate ( feeRecipients , page , perPage ) ; res . status ( HttpStatus . OK ) . send ( paginatedFeeRecipients ) ; } public static orderConfig ( req , res ) { schemaUtils . validateSchema ( req . body , schemas . sraOrderConfigPayloadSchema ) ; const orderConfigResponse = { sender : NULL_ADDRESS , feeRecipient : FEE_RECIPIENT_ADDRESS . toLowerCase ( ) , takerTokenFeeAmount : TAKER_FEE_UNIT_AMOUNT , } ; res . status ( HttpStatus . OK ) . send ( orderConfigResponse ) ; } constructor ( orderBook ) { this . _orderBook = orderBook ; } public async getOrderByHashAsync ( req , res ) { const orderIfExists = await this . _orderBook . getOrderByHashIfExistsAsync ( req . params . orderHash ) ; if ( orderIfExists === undefined ) { throw new NotFoundError ( ) ; } else { res . status ( HttpStatus . OK ) . send ( orderIfExists ) ; } } public async ordersAsync ( req , res ) { schemaUtils . validateSchema ( req . query , schemas . sraOrdersQuerySchema ) ; const orderFieldFilters = new SignedOrderV4Entity ( req . query ) ; const additionalFilters = { trader : req . query . trader ? req . query . trader . toString ( ) : undefined , isUnfillable : req . query . unfillable === 'true' , } ; const { page , perPage } = paginationUtils . parsePaginationConfig ( req ) ; const paginatedOrders = await this . _orderBook . getOrdersAsync ( page , perPage , orderFieldFilters , additionalFilters ) ; res . status ( HttpStatus . OK ) . send ( paginatedOrders ) ; } public async orderbookAsync ( req , res ) { schemaUtils . validateSchema ( req . query , schemas . sraOrderbookQuerySchema ) ; const { page , perPage } = paginationUtils . parsePaginationConfig ( req ) ; const baseToken = ( req . query . baseToken as string ) . toLowerCase ( ) ; const quoteToken = ( req . query . quoteToken as string ) . toLowerCase ( ) ; const orderbookResponse = await this . _orderBook . getOrderBookAsync ( page , perPage , baseToken , quoteToken ) ; res . status ( HttpStatus . OK ) . send ( orderbookResponse ) ; } public async postOrderAsync ( req , res ) { const shouldSkipConfirmation = req . query . skipConfirmation === 'true' ; schemaUtils . validateSchema ( req . body , schemas . sraPostOrderPayloadSchema ) ; const signedOrder = unmarshallOrder ( req . body ) ; if ( WHITELISTED_TOKENS !== '*' ) { const allowedTokens = WHITELISTED_TOKENS ; validateAssetTokenOrThrow ( allowedTokens , signedOrder . makerToken , 'makerToken' ) ; validateAssetTokenOrThrow ( allowedTokens , signedOrder . takerToken , 'takerToken' ) ; } if ( shouldSkipConfirmation ) { res . status ( HttpStatus . OK ) . send ( ) ; } await this . _orderBook . addOrderAsync ( signedOrder ) ; if ( ! shouldSkipConfirmation ) { res . status ( HttpStatus . OK ) . send ( ) ; } } public async postOrdersAsync ( req , res ) { const shouldSkipConfirmation = req . query . skipConfirmation === 'true' ; schemaUtils . validateSchema ( req . body , schemas . sraPostOrdersPayloadSchema ) ; const signedOrders = unmarshallOrders ( req . body ) ; if ( WHITELISTED_TOKENS !== '*' ) { const allowedTokens = WHITELISTED_TOKENS ; for ( const signedOrder of signedOrders ) { validateAssetTokenOrThrow ( allowedTokens , signedOrder . makerToken , 'makerToken' ) ; validateAssetTokenOrThrow ( allowedTokens , signedOrder . takerToken , 'takerToken' ) ; } } if ( shouldSkipConfirmation ) { res . status ( HttpStatus . OK ) . send ( ) ; } await this . _orderBook . addOrdersAsync ( signedOrders ) ; if ( ! shouldSkipConfirmation ) { res . status ( HttpStatus . OK ) . send ( ) ; } } public async postPersistentOrderAsync ( req , res ) { const shouldSkipConfirmation = req . query . skipConfirmation === 'true' ; const apiKey = req . header ( '0x-api-key' ) ; if ( apiKey === undefined || ! isValidUUID ( apiKey ) || ! OrderBookService . isAllowedPersistentOrders ( apiKey ) ) { throw new InvalidAPIKeyError ( ) ; } schemaUtils . validateSchema ( req . body , schemas . sraPostOrderPayloadSchema ) ; const signedOrder = unmarshallOrder ( req . body ) ; if ( WHITELISTED_TOKENS !== '*' ) { const allowedTokens = WHITELISTED_TOKENS ; validateAssetTokenOrThrow ( allowedTokens , signedOrder . makerToken , 'makerToken' ) ; validateAssetTokenOrThrow ( allowedTokens , signedOrder . takerToken , 'takerToken' ) ; } if ( shouldSkipConfirmation ) { res . status ( HttpStatus . OK ) . send ( ) ; } await this . _orderBook . addPersistentOrdersAsync ( [ signedOrder ] ) ; if ( ! shouldSkipConfirmation ) { res . status ( HttpStatus . OK ) . send ( ) ; } } } function validateAssetTokenOrThrow ( allowedTokens , tokenAddress , field ) { if ( ! allowedTokens . includes ( tokenAddress ) ) { throw new ValidationError ( [ { field , code : ValidationErrorCodes . ValueOutOfRange , reason : `${ tokenAddress } not supported` , } , ] ) ; } } function unmarshallOrder ( signedOrderRaw ) { const signedOrder = { taker : NULL_ADDRESS , feeRecipient : NULL_ADDRESS , pool : '0x0000000000000000000000000000000000000000000000000000000000000000' , ... signedOrderRaw , sender : NULL_ADDRESS , takerTokenFeeAmount : signedOrderRaw . takerTokenFeeAmount ? new BigNumber ( signedOrderRaw . takerTokenFeeAmount ) : ZERO , makerAmount : new BigNumber ( signedOrderRaw . makerAmount ) , takerAmount : new BigNumber ( signedOrderRaw . takerAmount ) , expiry : new BigNumber ( signedOrderRaw . expiry ) , salt : new BigNumber ( signedOrderRaw . salt ) , } ; return signedOrder ; } function unmarshallOrders ( signedOrdersRaw ) { return signedOrdersRaw . map ( ( signedOrderRaw ) => { return unmarshallOrder ( signedOrderRaw ) ; } ) ; }