Skip to content

Commit

Permalink
Merge pull request #22 from consenlabs/feature/add-rfq
Browse files Browse the repository at this point in the history
Support to sign RFQ order
  • Loading branch information
tailingchen authored Jun 10, 2021
2 parents 5a04499 + e2960e6 commit 1797093
Show file tree
Hide file tree
Showing 15 changed files with 545 additions and 132 deletions.
1 change: 1 addition & 0 deletions benchmark/MockServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ function createRPCHandler(): JsonRPC {
AllowanceTarget: '0x595d711189e48e766cc0cc011e85e40702764288',
AMMQuoter: '0x75a4f88deeed0ace489285d1695323ef49dbc2ab',
AMMWrapper: '0xcf011536f10e85e376e70905eed4ca9ea8cded34',
RFQ: '0xfD474E4809e690626C67ECb7A908de4b9c464b99',
},
}
})
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@consenlabs/tokenlon-mmsk",
"version": "5.1.2",
"version": "5.2.0-alpha.0",
"description": "Tokenlon market maker server kit",
"author": "imToken PTE. LTD.",
"license": "MIT",
Expand Down
102 changes: 63 additions & 39 deletions src/handler/newOrder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { validateNewOrderRequest, validateRequest } from '../validations'
import { ValidationError } from './errors'
import { addQuoteIdPrefix, constructQuoteResponse, preprocessQuote } from '../quoting'

import { assetDataUtils, SignedOrder } from '0x-v2-order-utils'
import { assetDataUtils } from '0x-v2-order-utils'
import { buildSignedOrder as buildRFQV1SignedOrder } from '../signer/rfqv1'
import { buildSignedOrder } from '../signer/pmmv5'
import { buildSignedOrder as buildLagacyOrder } from '../signer/pmmv4'
import { buildSignedOrder as buildAMMV1Order } from '../signer/ammv1'
import { FEE_RECIPIENT_ADDRESS } from '../constants'
import {
Expand All @@ -22,16 +22,49 @@ import {

type NumberOrString = number | string

interface Order {
// quoteId is from market maker backend quoter.
quoteId: string
// protocol represents the order type as enum, [PMMV4, PMMV5, AMMV1, RFQV1, AMMV2].
protocol: Protocol

// Common fields
makerAddress: string
makerAssetAmount: string
makerAssetAddress: string
takerAddress: string
takerAssetAmount: string
takerAssetAddress: string
expirationTimeSeconds: string
// feeFactor is tokenlon protocol field, works like BPS, should <= 10000.
feeFactor: number
// salt represents the uniqueness of order, is to prevent replay attack.
salt: string

// 0x protocol specific fields
makerAssetData: string
takerAssetData: string
senderAddress: string
// For PMMV5, we use this field as receiver address (user address).
feeRecipientAddress: string
exchangeAddress: string

// makerFee and takerFee are not used, but keep to make 0x order signature.
makerFee: string
takerFee: string

// PMM/RFQ market maker signature
makerWalletSignature: string

// Extra data
payload: string
}

interface Response {
rate: NumberOrString
minAmount: NumberOrString
maxAmount: NumberOrString
order?: {
quoteId: any
}
quoteId?: any
signedOrder?: SignedOrder
orderHash?: string
order?: Order
}

// use smallest decimals from [USDT/USDC: 6, BTC: 8, ETH: 18]
Expand Down Expand Up @@ -102,7 +135,6 @@ function getOrderAndFeeFactor(simpleOrder, rate, tokenList, tokenConfigs, config
// console.log('set fee factor from query string', { queryFeeFactor })
fFactor = +feefactor
}
const feeFactor = fFactor

// 针对用户买,对于做市商是提供卖单
// 用户用quote 买base,做市商要构建卖base 换quote的order
Expand All @@ -118,37 +150,36 @@ function getOrderAndFeeFactor(simpleOrder, rate, tokenList, tokenConfigs, config
toBN(amount)
)

const order = {
return {
makerAddress: config.mmProxyContractAddress.toLowerCase(),
makerAssetAmount,
makerAssetAddress: makerToken.contractAddress,
makerAssetData: assetDataUtils.encodeERC20AssetData(
getWethAddrIfIsEth(makerToken.contractAddress, config)
getWethAddrIfIsEth(makerToken.contractAddress, config.wethContractAddress)
),
makerFee: toBN(0),

takerAddress: config.userProxyContractAddress,
takerAssetAmount,
takerAssetAddress: takerToken.contractAddress,
takerAssetData: assetDataUtils.encodeERC20AssetData(
getWethAddrIfIsEth(takerToken.contractAddress, config)
getWethAddrIfIsEth(takerToken.contractAddress, config.wethContractAddress)
),
takerFee: toBN(0),

senderAddress: config.tokenlonExchangeContractAddress.toLowerCase(),
feeRecipientAddress: FEE_RECIPIENT_ADDRESS,
expirationTimeSeconds: toBN(getTimestamp() + +config.orderExpirationSeconds),
exchangeAddress: config.exchangeContractAddress,
}

return {
order,
feeFactor,
feeFactor: fFactor,
}
}

export const newOrder = async (ctx) => {
const { quoter, signer } = ctx
const { quoter, signer, chainID } = ctx
const query: QueryInterface = {
protocol: Protocol.PMMV4, // by default is v2 protocol
protocol: Protocol.PMMV5, // by default is v2 protocol
...ctx.query, // overwrite from request
}

Expand All @@ -160,19 +191,12 @@ export const newOrder = async (ctx) => {
if (errMsg) throw new ValidationError(errMsg)

const { simpleOrder, rateBody } = await requestMarketMaker(quoter, query)
const makerCfg = updaterStack.markerMakerConfigUpdater.cacheResult
const config = updaterStack.markerMakerConfigUpdater.cacheResult
const tokenConfigs = updaterStack.tokenConfigsFromImtokenUpdater.cacheResult
const tokenList = getSupportedTokens()

const { rate, minAmount, maxAmount, quoteId } = rateBody
const { order, feeFactor } = getOrderAndFeeFactor(
simpleOrder,
rate,
tokenList,
tokenConfigs,
config
)
const order = getOrderAndFeeFactor(simpleOrder, rate, tokenList, tokenConfigs, config)

const resp: Response = {
rate,
Expand All @@ -190,32 +214,32 @@ export const newOrder = async (ctx) => {
resp.minAmount = tokenConfig.minTradeAmount
resp.maxAmount = tokenConfig.maxTradeAmount
}
resp.order = buildAMMV1Order(
order,
feeFactor,
rateBody.makerAddress,
config.wethContractAddress
)
resp.order = buildAMMV1Order(order, rateBody.makerAddress, config.wethContractAddress)
break
case Protocol.PMMV5:
resp.order = await buildSignedOrder(
signer,
order,
userAddr.toLowerCase(),
feeFactor,
config.addressBookV5.PMM
)
break
default:
console.log(`unknown protocol ${protocol}, fallback to 0x v2`)
if (signer.address.toLowerCase() == makerCfg.mmProxyContractAddress.toLowerCase()) {
throw new Error('eoa_signer_not_work_with_tokenlon_v4_order')
}
resp.order = buildLagacyOrder(signer, order, userAddr, feeFactor)
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,
Expand Down
2 changes: 1 addition & 1 deletion src/handler/version.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const version = (ctx) => {
ctx.body = {
result: true,
version: '5.1.2',
version: '5.2.0-alpha.0',
}
}
16 changes: 10 additions & 6 deletions src/signer/ammv1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,27 @@ import * as cryptoRandomString from 'crypto-random-string'
import { orderBNToString } from '../utils'
import { NULL_ADDRESS } from '../constants'

export const buildSignedOrder = (order, feeFactor, makerAddress, wethAddress) => {
export const buildSignedOrder = (order, makerAddress, wethAddress) => {
const makerAssetAddress = order.makerAssetAddress.toLowerCase()
const takerAssetAddress = order.takerAssetAddress.toLowerCase()
// = Rewrite order fields
// 1. change maker address to LP pool address
order.makerAddress = makerAddress
// 2. convert weth to eth
const makerTokenAddress = assetDataUtils.decodeERC20AssetData(order.makerAssetData).tokenAddress
if (makerTokenAddress.toLowerCase() === wethAddress) {
if (makerAssetAddress === wethAddress.toLowerCase()) {
order.makerAssetData = assetDataUtils.encodeERC20AssetData(NULL_ADDRESS)
} else {
order.makerAssetData = assetDataUtils.encodeERC20AssetData(makerAssetAddress)
}
const takerTokenAddress = assetDataUtils.decodeERC20AssetData(order.takerAssetData).tokenAddress
if (takerTokenAddress.toLowerCase() === wethAddress) {

if (takerAssetAddress === wethAddress.toLowerCase()) {
order.takerAssetData = assetDataUtils.encodeERC20AssetData(NULL_ADDRESS)
} else {
order.takerAssetData = assetDataUtils.encodeERC20AssetData(takerAssetAddress)
}
// NOTE: for AMM order we don't do signing here
const signedOrder = {
...order,
feeFactor,
salt: generatePseudoRandomSalt(),
makerWalletSignature: cryptoRandomString({ length: 40 }),
}
Expand Down
37 changes: 37 additions & 0 deletions src/signer/orderHash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { utils } from 'ethers'
import { RFQOrder } from './types'

const EIP712_DOMAIN_NAME = 'Tokenlon'
const EIP712_DOMAIN_VERSION = 'v5'

var RFQ_ORDER_SCHEMA = {
Order: [
{ name: 'takerAddr', type: 'address' },
{ name: 'makerAddr', type: 'address' },
{ name: 'takerAssetAddr', type: 'address' },
{ name: 'makerAssetAddr', type: 'address' },
{ name: 'takerAssetAmount', type: 'uint256' },
{ name: 'makerAssetAmount', type: 'uint256' },
{ name: 'salt', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
{ name: 'feeFactor', type: 'uint256' },
],
}

export function getOrderSignDigest(order: RFQOrder, chainId: number, address: string): string {
const domain = {
name: EIP712_DOMAIN_NAME,
version: EIP712_DOMAIN_VERSION,
chainId: chainId,
verifyingContract: address,
}
// The data to sign
const value = {
...order,
takerAssetAmount: order.takerAssetAmount.toString(),
makerAssetAmount: order.makerAssetAmount.toString(),
salt: order.salt.toString(),
}

return utils._TypedDataEncoder.hash(domain, RFQ_ORDER_SCHEMA, value)
}
61 changes: 0 additions & 61 deletions src/signer/pmmv4.ts

This file was deleted.

Loading

0 comments on commit 1797093

Please sign in to comment.