import { memoize } from 'lodash' ; import { Quoter } from '../request/marketMaker' ; import { updaterStack } from '../worker' ; import { Protocol , QueryInterface } from '../types' ; import { validateNewOrderRequest , validateRequest } from '../validations' ; import { ValidationError } from './errors' ; import { addQuoteIdPrefix , constructQuoteResponse , preprocessQuote } from '../quoting' ; import { assetDataUtils } from '0x-v2-order-utils' ; import { buildSignedOrder as buildRFQV1SignedOrder } from '../signer/rfqv1' ; import { buildSignedOrder } from '../signer/pmmv5' ; import { buildSignedOrder as buildAMMV1Order } from '../signer/ammv1' ; import { buildSignedOrder as buildAMMV2Order } from '../signer/ammv2' ; import { FEE_RECIPIENT_ADDRESS } from '../constants' ; import { BigNumber , fromUnitToDecimalBN , toBN , getSupportedTokens , getTokenByAddress , getWethAddrIfIsEth , getTimestamp , } from '../utils' ; type NumberOrString = number | string ; interface Order { quoteId : string ; protocol : Protocol ; makerAddress : string ; makerAssetAmount : string ; makerAssetAddress : string ; takerAddress : string ; takerAssetAmount : string ; takerAssetAddress : string ; expirationTimeSeconds : string ; feeFactor : number ; salt : string ; makerAssetData : string ; takerAssetData : string ; senderAddress : string ; feeRecipientAddress : string ; exchangeAddress : string ; makerFee : string ; takerFee : string ; makerWalletSignature : string ; payload : string ; } interface Response { rate : NumberOrString ; minAmount : NumberOrString ; maxAmount : NumberOrString ; order ? : Order ; } const TRUNCATE_PRECISION = 6 ; const findSuitablePrecision = ( decimals ) : number => { return decimals < 8 ? TRUNCATE_PRECISION : 8 ; } ; async function requestMarketMaker ( quoter , query ) { const { side } = query ; const priceResult = await quoter . getPrice ( query as any ) ; console . log ( 'got result from market maker' , { query , priceResult } ) ; const rateBody = { ... constructQuoteResponse ( priceResult , side ) , quoteId : addQuoteIdPrefix ( priceResult . quoteId ) , payload : priceResult . payload , } ; return rateBody ; } function extractAssetAmounts ( makerToken , takerToken , side , rate , amountBN ) { let makerAssetAmount , takerAssetAmount ; if ( side === 'BUY' ) { makerAssetAmount = fromUnitToDecimalBN ( amountBN . toFixed ( makerToken . precision ) , makerToken . decimal ) ; takerAssetAmount = fromUnitToDecimalBN ( amountBN . dividedBy ( rate ) . toFixed ( findSuitablePrecision ( takerToken . decimal ) ) , takerToken . decimal ) ; } else { makerAssetAmount = fromUnitToDecimalBN ( amountBN . times ( rate ) . toFixed ( findSuitablePrecision ( makerToken . decimal ) ) , makerToken . decimal ) ; takerAssetAmount = fromUnitToDecimalBN ( amountBN . toFixed ( takerToken . precision ) , takerToken . decimal ) ; } return { makerAssetAmount , takerAssetAmount } ; } function getOrderAndFeeFactor ( query , rate , tokenList , tokenConfigs , config ) { const { side , amount , feefactor } = query ; const baseToken = getTokenByAddress ( tokenList , query . baseAddress ) ; const quoteToken = getTokenByAddress ( tokenList , query . quoteAddress ) ; const makerToken = side === 'BUY' ? baseToken : quoteToken ; const takerToken = side === 'BUY' ? quoteToken : baseToken ; const foundTokenConfig = tokenConfigs . find ( ( t ) => t . symbol === makerToken . symbol ) ; let fFactor = config . feeFactor || 10 ; if ( foundTokenConfig ?. feeFactor ) { fFactor = foundTokenConfig . feeFactor ; } if ( feefactor && ! Number . isNaN ( + feefactor ) && + feefactor >= 0 ) { fFactor = + feefactor ; } const { makerAssetAmount , takerAssetAmount } = extractAssetAmounts ( makerToken , takerToken , side , rate , toBN ( amount ) ) ; const makerAssetAddress = getWethAddrIfIsEth ( makerToken . contractAddress , config . wethContractAddress ) ; const takerAssetAddress = getWethAddrIfIsEth ( takerToken . contractAddress , config . wethContractAddress ) ; return { makerAddress : config . mmProxyContractAddress . toLowerCase ( ) , makerAssetAmount , makerAssetAddress : makerAssetAddress , makerAssetData : assetDataUtils . encodeERC20AssetData ( makerAssetAddress ) , makerFee : toBN ( 0 ) , takerAddress : config . userProxyContractAddress , takerAssetAmount , takerAssetAddress : takerAssetAddress , takerAssetData : assetDataUtils . encodeERC20AssetData ( takerAssetAddress ) , takerFee : toBN ( 0 ) , senderAddress : config . tokenlonExchangeContractAddress . toLowerCase ( ) , feeRecipientAddress : FEE_RECIPIENT_ADDRESS , expirationTimeSeconds : toBN ( getTimestamp ( ) + + config . orderExpirationSeconds ) , exchangeAddress : config . exchangeContractAddress , feeFactor : fFactor , } ; } const _getBaseTokenByAddress = ( baseTokenAddr , tokenList ) => { return tokenList . find ( ( token ) => token . contractAddress . toLowerCase ( ) === baseTokenAddr ) ; } ; const getBaseTokenByAddress = memoize ( _getBaseTokenByAddress ) ; export const newOrder = async ( ctx ) => { const { quoter , signer , chainID } = ctx ; const req = { protocol : Protocol . PMMV5 , ... ctx . query , } ; try { const query = preprocessQuote ( req ) ; let errMsg = validateRequest ( query ) ; if ( errMsg ) throw new ValidationError ( errMsg ) ; const { amount , uniqId , userAddr , protocol } = query ; errMsg = validateNewOrderRequest ( amount , uniqId , userAddr ) ; if ( errMsg ) throw new ValidationError ( errMsg ) ; const rateBody = await requestMarketMaker ( quoter , query ) ; const config = updaterStack . markerMakerConfigUpdater . cacheResult ; const tokenConfigs = updaterStack . tokenConfigsFromImtokenUpdater . cacheResult ; const tokenList = getSupportedTokens ( ) ; const { rate , minAmount , maxAmount , quoteId } = rateBody ; const order = getOrderAndFeeFactor ( query , rate , tokenList , tokenConfigs , config ) ; const resp = { rate , minAmount , maxAmount , } ; switch ( protocol ) { case Protocol . AMMV1 : { const baseTokenAddr = query . baseAddress ; const baseToken = getBaseTokenByAddress ( baseTokenAddr . toLowerCase ( ) , tokenList ) ; resp . minAmount = baseToken . minTradeAmount ; resp . maxAmount = baseToken . maxTradeAmount ; } resp . order = buildAMMV1Order ( order , rateBody . makerAddress , config . wethContractAddress ) ; break ; case Protocol . AMMV2 : { const baseTokenAddr = query . baseAddress ; const baseToken = getBaseTokenByAddress ( baseTokenAddr . toLowerCase ( ) , tokenList ) ; resp . minAmount = baseToken . minTradeAmount ; resp . maxAmount = baseToken . maxTradeAmount ; } resp . order = buildAMMV2Order ( order , rateBody . payload , rateBody . makerAddress , config . wethContractAddress ) ; break ; case Protocol . PMMV5 : resp . order = await buildSignedOrder ( signer , order , userAddr . toLowerCase ( ) , config . addressBookV5 . PMM ) ; break ; case Protocol . RFQV1 : resp . order = await buildRFQV1SignedOrder ( signer , order , userAddr . toLowerCase ( ) , chainID , config . addressBookV5 . RFQ ) ; break ; default : console . log ( `unknown protocol ${ protocol }` ) ; throw new Error ( 'Unrecognized protocol: ' + protocol ) ; } resp . order . quoteId = quoteId ; resp . order . protocol = protocol ; ctx . body = { result : true , exchangeable : true , ... resp , } ; return resp ; } catch ( e ) { console . error ( e . stack ) ; ctx . body = { result : false , exchangeable : false , message : e . message , } ; return e . message ; } } ;