From d1a8b9b216eca9aed1dbf01ccca18db47ca9e228 Mon Sep 17 00:00:00 2001 From: BenjaminLu Date: Thu, 29 Sep 2022 17:48:59 +0800 Subject: [PATCH 1/4] Forwarding unsigned orders if SIGNING_URL is set by market maker --- app/mmConfig.js | 11 ++++++----- src/handler/newOrder.ts | 9 ++++----- src/signer/rfqv1.ts | 33 ++++++++++++++++++++++++++++----- src/signer/types.ts | 2 +- src/start.ts | 2 +- src/types/index.ts | 1 + test/new_order.spec.ts | 28 +++++++++++++++++++++++++++- 7 files changed, 68 insertions(+), 18 deletions(-) diff --git a/app/mmConfig.js b/app/mmConfig.js index 7109f42..9ba902b 100644 --- a/app/mmConfig.js +++ b/app/mmConfig.js @@ -1,12 +1,15 @@ const types = require('../lib/signer/types') module.exports = { - // // Tokenlon server address EXCHANGE_URL: process.env.EXCHANGE_URL, PROVIDER_URL: process.env.PROVIDER_URL, - // + // Signing service + SIGNING_URL: process.env.SIGNING_URL, + + // Static token list + // Wallet WALLET_ADDRESS: process.env.WALLET_ADDRESS, WALLET_TYPE: types.WalletType.ERC1271, @@ -17,14 +20,12 @@ module.exports = { // AMM AMMWRAPPER_CONTRACT_ADDRESS: process.env.AMMWRAPPER_CONTRACT_ADDRESS, - // // MM backend config HTTP_SERVER_ENDPOINT: process.env.HTTP_SERVER_ENDPOINT, // ZERORPC_SERVER_ENDPOINT: process.env.ZERORPC_SERVER_ENDPOINT, - // // Server config - CHAIN_ID: process.env.CHAIN_ID || 42, + CHAIN_ID: process.env.CHAIN_ID || 5, MMSK_SERVER_PORT: process.env.MMSK_SERVER_PORT || 80, SENTRY_DSN: '', NODE_ENV: 'PRODUCTION', diff --git a/src/handler/newOrder.ts b/src/handler/newOrder.ts index f835289..e03a545 100644 --- a/src/handler/newOrder.ts +++ b/src/handler/newOrder.ts @@ -186,15 +186,13 @@ function getOrderAndFeeFactor(query: QueryInterface, rate, tokenList, tokenConfi } const _getBaseTokenByAddress = (baseTokenAddr, tokenList) => { - return tokenList.find( - (token) => token.contractAddress.toLowerCase() === baseTokenAddr - ) + return tokenList.find((token) => token.contractAddress.toLowerCase() === baseTokenAddr) } const getBaseTokenByAddress = memoize(_getBaseTokenByAddress) export const newOrder = async (ctx) => { - const { quoter, signer, chainID, walletType } = ctx + const { quoter, signer, chainID, walletType, signingUrl } = ctx const req: QueryInterface = { protocol: Protocol.PMMV5, // by default is v2 protocol ...ctx.query, // overwrite from request @@ -261,7 +259,8 @@ export const newOrder = async (ctx) => { userAddr.toLowerCase(), chainID, config.addressBookV5.RFQ, - walletType + walletType, + signingUrl ) break default: diff --git a/src/signer/rfqv1.ts b/src/signer/rfqv1.ts index 39d175a..8b28b51 100644 --- a/src/signer/rfqv1.ts +++ b/src/signer/rfqv1.ts @@ -5,6 +5,7 @@ import { getOrderHash, getOrderSignDigest } from './orderHash' import { RFQOrder, WalletType } from './types' import * as ethUtils from 'ethereumjs-util' import { SignatureType } from './types' +import axios from 'axios' // spec of RFQV1 // - taker address point to userAddr @@ -80,13 +81,24 @@ export async function signByMMPSigner( } } +export const forwardUnsignedOrder = async (signingUrl: string, orderInfo: any): Promise => { + const resp = await axios.post(signingUrl, orderInfo) + const body = resp.data + if (body.signature) { + return body.signature + } else { + throw new Error('Invalid signature') + } +} + export const buildSignedOrder = async ( signer: Wallet, order, userAddr: string, chainId: number, rfqAddr: string, - walletType: WalletType + walletType: WalletType, + signingUrl?: string ): Promise => { // inject fee factor to salt const feeFactor = order.feeFactor @@ -98,10 +110,21 @@ export const buildSignedOrder = async ( console.log(`orderHash: ${orderHash}`) const orderSignDigest = getOrderSignDigest(rfqOrer, chainId, rfqAddr) console.log(`orderSignDigest: ${orderSignDigest}`) - const makerWalletSignature = - signer.address.toLowerCase() == order.makerAddress.toLowerCase() - ? await signByEOA(orderSignDigest, signer) - : await signByMMPSigner(orderSignDigest, userAddr, feeFactor, signer, walletType) + let makerWalletSignature + if (!signingUrl) { + makerWalletSignature = + signer.address.toLowerCase() == order.makerAddress.toLowerCase() + ? await signByEOA(orderSignDigest, signer) + : await signByMMPSigner(orderSignDigest, userAddr, feeFactor, signer, walletType) + } else { + makerWalletSignature = await forwardUnsignedOrder(signingUrl, { + rfqOrer: rfqOrer, + userAddr: userAddr, + signer: signer.address, + chainId: chainId, + rfqAddr: rfqAddr, + }) + } const signedOrder = { ...order, diff --git a/src/signer/types.ts b/src/signer/types.ts index 8112696..fa0de12 100644 --- a/src/signer/types.ts +++ b/src/signer/types.ts @@ -45,8 +45,8 @@ export enum SignatureType { } export enum WalletType { - EOA = 0, MMP_VERSOIN_4 = 1, // https://gist.github.com/NIC619/a3db1a743175bf592f2db983f17680dd#file-mmpv4-sol-L1236 MMP_VERSION_5 = 2, // https://github.com/consenlabs/tokenlon-contracts/blob/e2edf7581b69bc8a40e61ff7fc1cd29674ae4887/contracts/MarketMakerProxy.sol#L19 ERC1271 = 3, // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.6.0/contracts/utils/cryptography/SignatureChecker.sol#L36 + EOA = 4, // less security for market makers } diff --git a/src/start.ts b/src/start.ts index 63647e4..b3652ae 100644 --- a/src/start.ts +++ b/src/start.ts @@ -82,7 +82,6 @@ export const startMMSK = async (config: ConfigForStart) => { console.log({ version: VERSION, signerAddress: wallet.address, - mmpAddress: config.WALLET_ADDRESS, mmpType: config.WALLET_TYPE || WalletType.MMP_VERSOIN_4, chainId: config.CHAIN_ID, exchangeUrl: config.EXCHANGE_URL, @@ -109,6 +108,7 @@ export const startMMSK = async (config: ConfigForStart) => { app.context.chainID = config.CHAIN_ID || 5 app.context.quoter = quoter app.context.signer = wallet + app.context.signingUrl = config.SIGNING_URL app.context.walletType = config.WALLET_TYPE || WalletType.MMP_VERSOIN_4 app diff --git a/src/types/index.ts b/src/types/index.ts index 1fb13d0..f22df6d 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -34,6 +34,7 @@ export interface Wallet { export interface ConfigForStart { EXCHANGE_URL: string PROVIDER_URL: string + SIGNING_URL: string WALLET_ADDRESS: string WALLET_TYPE: WalletType diff --git a/test/new_order.spec.ts b/test/new_order.spec.ts index 396b62f..6b4b741 100644 --- a/test/new_order.spec.ts +++ b/test/new_order.spec.ts @@ -4,7 +4,7 @@ import { newOrder } from '../src/handler' import { updaterStack, Updater } from '../src/worker' import { NULL_ADDRESS } from '../src/constants' import { Protocol } from '../src/types' -import { toRFQOrder } from '../src/signer/rfqv1' +import { buildSignedOrder, toRFQOrder } from '../src/signer/rfqv1' import { SignatureType, WalletType } from '../src/signer/types' import { getOrderSignDigest } from '../src/signer/orderHash' import { BigNumber } from '../src/utils' @@ -795,4 +795,30 @@ describe('NewOrder', function () { const orderHash = getOrderSignDigest(order, 1, rfqAddr) expect(orderHash).eq('0x8d70993864d87daa0b2bae0c2be1c56067f45363680d0dca8657e1e51d1d6a40') }) + it('Should forward unsigned orders to signing service', async () => { + const url = `http://localhost:3000` + const rfqAddr = '0xfD6C2d2499b1331101726A8AC68CCc9Da3fAB54F' + const order = { + takerAddress: '0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69', + makerAddress: '0x86B9F429C3Ef44c599EB560Eb531A0E3f2E36f64', + takerAssetAddress: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + makerAssetAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7', + takerAssetAmount: new BigNumber('0x0de0b6b3a7640000'), + makerAssetAmount: new BigNumber('0x05f5e100'), + salt: new BigNumber('0x44df74b1c54e9792989c61fedcef6f94b534b58933cde70bc456ec74cf4d3610'), + expirationTimeSeconds: 1620444917, + feeFactor: 30, + } + const signature = await buildSignedOrder( + signer, + order, + Wallet.createRandom().address.toLowerCase(), + chainId, + rfqAddr, + WalletType.MMP_VERSOIN_4, + url + ) + console.log(signature) + expect(signature).not.null + }) }) From 213ee38abff780d0e3ed585eb421ce1029aea801 Mon Sep 17 00:00:00 2001 From: BenjaminLu Date: Thu, 29 Sep 2022 17:53:50 +0800 Subject: [PATCH 2/4] Fix tests --- test/new_order.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/new_order.spec.ts b/test/new_order.spec.ts index 6b4b741..8c42204 100644 --- a/test/new_order.spec.ts +++ b/test/new_order.spec.ts @@ -796,7 +796,7 @@ describe('NewOrder', function () { expect(orderHash).eq('0x8d70993864d87daa0b2bae0c2be1c56067f45363680d0dca8657e1e51d1d6a40') }) it('Should forward unsigned orders to signing service', async () => { - const url = `http://localhost:3000` + // const url = `http://localhost:3000` const rfqAddr = '0xfD6C2d2499b1331101726A8AC68CCc9Da3fAB54F' const order = { takerAddress: '0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69', @@ -815,8 +815,8 @@ describe('NewOrder', function () { Wallet.createRandom().address.toLowerCase(), chainId, rfqAddr, - WalletType.MMP_VERSOIN_4, - url + WalletType.MMP_VERSOIN_4 + // url ) console.log(signature) expect(signature).not.null From 9d83de692de5d6457210f7ef2fdac9b1cba6a7d9 Mon Sep 17 00:00:00 2001 From: BenjaminLu Date: Mon, 3 Oct 2022 15:12:42 +0800 Subject: [PATCH 3/4] Fix typo and bypass private key checks if the SIGNING_URL is set --- README.md | 11 +++++++++-- app/mmConfig.js | 18 ++++++++++++------ src/check/index.ts | 2 +- src/signer/rfqv1.ts | 2 +- src/signer/types.ts | 2 +- src/start.ts | 37 +++++++++++++++++++++++-------------- src/worker/index.ts | 11 +++++++---- test/new_order.spec.ts | 12 ++++++------ 8 files changed, 60 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 7e562e5..3b7f38b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ See [docs](https://docs.token.im/tokenlon-mmsk/) ## Setup -Require Node.JS v10 as runtime. +Require Node.JS v12 as runtime. Program setup, - Create a wallet as order signer, and save it as keystore or private key @@ -20,8 +20,15 @@ Program setup, - PROVIDER_URL, point to ethereum node, like your infura endpoint - WALLET_ADDRESS, as your signer wallet address - WALLET_PRIVATE_KEY, private key of above wallet, or use WALLET_KEYSTORE + - WALLET_TYPE, a market maker's wallet smart contract. + - types.WalletType.MMP_VERSION_4 (compatible with PMM protocol, see [example contract](https://gist.github.com/NIC619/a3db1a743175bf592f2db983f17680dd#file-mmpv4-sol-L1236)) + - types.WalletType.MMP_VERSION_5 + - types.WalletType.ERC1271 + - types.WalletType.EOA + - SIGNING_URL, If you wanna sign orders in your own service instead of the mmsk, + please set the SIGNING_URL to your service endpoint. the mmsk would post every unsigned RFQ orders to your service. Remember to set the WALLET_ADDRESS as well. - HTTP_SERVER_ENDPOINT, your backend http server - - CHAIN_ID, 1 for mainnet, 42 for testnet(kovan) + - CHAIN_ID, 1 for mainnet, 5 for testnet(Goerli) - Testing with `node app/check.js` - Register contract address & signer address & MMSK server url to Tokenlon team diff --git a/app/mmConfig.js b/app/mmConfig.js index 9ba902b..35df245 100644 --- a/app/mmConfig.js +++ b/app/mmConfig.js @@ -5,16 +5,22 @@ module.exports = { EXCHANGE_URL: process.env.EXCHANGE_URL, PROVIDER_URL: process.env.PROVIDER_URL, - // Signing service + // Signing + /** + * If you wanna sign orders in your own service instead of the mmsk, + * please set the SIGNING_URL to your service endpoint. + * the mmsk would post every unsigned orders to your service. + * Remember to set the WALLET_ADDRESS as well. + */ SIGNING_URL: process.env.SIGNING_URL, - - // Static token list - - // Wallet WALLET_ADDRESS: process.env.WALLET_ADDRESS, - WALLET_TYPE: types.WalletType.ERC1271, + WALLET_TYPE: types.WalletType.MMP_VERSION_4, USE_KEYSTORE: false, WALLET_KEYSTORE: {}, + /** + * If you set the SIGNING_URL and WALLET_ADDRESS, it's unnecessary to set the WALLET_PRIVATE_KEY. + * It would forward evey unsigned order to SIGNING_URL instead of signing orders with WALLET_PRIVATE_KEY + */ WALLET_PRIVATE_KEY: process.env.WALLET_PRIVATE_KEY, // AMM diff --git a/src/check/index.ts b/src/check/index.ts index 2b919f0..cbe0467 100644 --- a/src/check/index.ts +++ b/src/check/index.ts @@ -31,7 +31,7 @@ export const checkMMSK = async (config: ConfigForStart) => { quoter = new QuoteDispatcher(config.HTTP_SERVER_ENDPOINT, QuoterProtocol.HTTP) } const wallet = getWallet() - await startUpdater(quoter, wallet) + await startUpdater(quoter, wallet.address) for (let i = 0; i < arr.length; i += 1) { const item = arr[i] diff --git a/src/signer/rfqv1.ts b/src/signer/rfqv1.ts index 8b28b51..b958701 100644 --- a/src/signer/rfqv1.ts +++ b/src/signer/rfqv1.ts @@ -37,7 +37,7 @@ export async function signByMMPSigner( wallet: Wallet, walletType: WalletType ): Promise { - if (walletType === WalletType.MMP_VERSOIN_4) { + if (walletType === WalletType.MMP_VERSION_4) { // For V4 Maket Maker Proxy (MMP) // Signature: // +------|---------|---------|---------|---------|---------+ diff --git a/src/signer/types.ts b/src/signer/types.ts index fa0de12..d24af07 100644 --- a/src/signer/types.ts +++ b/src/signer/types.ts @@ -45,7 +45,7 @@ export enum SignatureType { } export enum WalletType { - MMP_VERSOIN_4 = 1, // https://gist.github.com/NIC619/a3db1a743175bf592f2db983f17680dd#file-mmpv4-sol-L1236 + MMP_VERSION_4 = 1, // https://gist.github.com/NIC619/a3db1a743175bf592f2db983f17680dd#file-mmpv4-sol-L1236 MMP_VERSION_5 = 2, // https://github.com/consenlabs/tokenlon-contracts/blob/e2edf7581b69bc8a40e61ff7fc1cd29674ae4887/contracts/MarketMakerProxy.sol#L19 ERC1271 = 3, // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.6.0/contracts/utils/cryptography/SignatureChecker.sol#L36 EOA = 4, // less security for market makers diff --git a/src/start.ts b/src/start.ts index b3652ae..5c7699f 100644 --- a/src/start.ts +++ b/src/start.ts @@ -28,7 +28,7 @@ import { VERSION } from './handler/version' // FIXME: construct wallet(signer), quoter and worker separately // FIXME: better retry implementation const beforeStart = async (config: ConfigForStart, triedTimes?: number) => { - const wallet = getWallet() + // const wallet = getWallet() triedTimes = triedTimes || 0 try { let quoter: Quoter @@ -37,7 +37,7 @@ const beforeStart = async (config: ConfigForStart, triedTimes?: number) => { } else { quoter = new QuoteDispatcher(config.HTTP_SERVER_ENDPOINT, QuoterProtocol.HTTP) } - await startUpdater(quoter, wallet) + await startUpdater(quoter, config.WALLET_ADDRESS) return quoter } catch (e) { triedTimes += 1 @@ -69,20 +69,25 @@ export const startMMSK = async (config: ConfigForStart) => { const app = new Koa() const router = new Router() const MMSK_SERVER_PORT = config.MMSK_SERVER_PORT || 80 - + let wallet setConfig(config) try { - const wallet = getWallet() - if (wallet.address.toLowerCase() != config.WALLET_ADDRESS.toLowerCase()) { - throw `wallet's address${wallet.address} and ${ - config.USE_KEYSTORE ? 'keystore' : 'privateKey' - }(${config.WALLET_ADDRESS}) not matched` + console.log(config.SIGNING_URL) + if (!config.SIGNING_URL) { + wallet = getWallet() + if (!wallet) { + throw new Error(`Please set either WALLET_PRIVATE_KEY or SIGNING_URL`) + } + if (wallet.address.toLowerCase() != config.WALLET_ADDRESS.toLowerCase()) { + throw `wallet's address${wallet.address} and ${ + config.USE_KEYSTORE ? 'keystore' : 'privateKey' + }(${config.WALLET_ADDRESS}) not matched` + } } - console.log({ version: VERSION, - signerAddress: wallet.address, - mmpType: config.WALLET_TYPE || WalletType.MMP_VERSOIN_4, + signerAddress: config.WALLET_ADDRESS, + mmpType: config.WALLET_TYPE || WalletType.MMP_VERSION_4, chainId: config.CHAIN_ID, exchangeUrl: config.EXCHANGE_URL, }) @@ -107,9 +112,13 @@ export const startMMSK = async (config: ConfigForStart) => { app.context.chainID = config.CHAIN_ID || 5 app.context.quoter = quoter - app.context.signer = wallet - app.context.signingUrl = config.SIGNING_URL - app.context.walletType = config.WALLET_TYPE || WalletType.MMP_VERSOIN_4 + if (wallet) { + app.context.signer = wallet + } + if (config.SIGNING_URL) { + app.context.signingUrl = config.SIGNING_URL + } + app.context.walletType = config.WALLET_TYPE || WalletType.MMP_VERSION_4 app .use(async (ctx, next) => { diff --git a/src/worker/index.ts b/src/worker/index.ts index 9e42d77..15551ca 100644 --- a/src/worker/index.ts +++ b/src/worker/index.ts @@ -1,7 +1,7 @@ import { getMarketMakerConfig, getTokenList, getTokenConfigsForMM } from '../request/imToken' import { Quoter } from '../request/marketMaker' import Updater from './updater' -import { Wallet } from '../types' +import { utils } from 'ethers' const updaterStack = { markerMakerConfigUpdater: null as Updater, @@ -10,11 +10,14 @@ const updaterStack = { pairsFromMMUpdater: null as Updater, } -const startUpdater = async (quoter: Quoter, wallet: Wallet) => { +const startUpdater = async (quoter: Quoter, walletAddress: string) => { + if (!utils.isAddress(utils.getAddress(walletAddress))) { + throw new Error('WALLET_ADDRESS is not valid') + } updaterStack.markerMakerConfigUpdater = new Updater({ name: 'markerMakerConfig', updater() { - return getMarketMakerConfig(wallet.address) + return getMarketMakerConfig(walletAddress) }, }) @@ -28,7 +31,7 @@ const startUpdater = async (quoter: Quoter, wallet: Wallet) => { updaterStack.tokenConfigsFromImtokenUpdater = new Updater({ name: 'tokenConfigsFromImtoken', updater() { - return getTokenConfigsForMM(wallet.address) + return getTokenConfigsForMM(walletAddress) }, }) diff --git a/test/new_order.spec.ts b/test/new_order.spec.ts index 8c42204..2d4b566 100644 --- a/test/new_order.spec.ts +++ b/test/new_order.spec.ts @@ -222,7 +222,7 @@ describe('NewOrder', function () { it('should raise error for pmmv4 order', async function () { expect( await newOrder({ - walletType: WalletType.MMP_VERSOIN_4, + walletType: WalletType.MMP_VERSION_4, signer: signer, quoter: { getPrice: () => { @@ -252,7 +252,7 @@ describe('NewOrder', function () { it('should sign pmmv5 order for MMPv4', async function () { const userAddr = Wallet.createRandom().address.toLowerCase() const signedOrderResp = await newOrder({ - walletType: WalletType.MMP_VERSOIN_4, + walletType: WalletType.MMP_VERSION_4, signer: Wallet.createRandom(), quoter: { getPrice: () => { @@ -414,7 +414,7 @@ describe('NewOrder', function () { mockMarkerMakerConfigUpdater.cacheResult = cacheResult updaterStack['markerMakerConfigUpdater'] = mockMarkerMakerConfigUpdater const signedOrderResp = await newOrder({ - walletType: WalletType.MMP_VERSOIN_4, + walletType: WalletType.MMP_VERSION_4, signer: mmpSigner, chainID: 1, quoter: { @@ -712,7 +712,7 @@ describe('NewOrder', function () { describe('handle token precision and decimals', () => { it('should format taker asset amount', async function () { const signedOrderResp = await newOrder({ - walletType: WalletType.MMP_VERSOIN_4, + walletType: WalletType.MMP_VERSION_4, signer: Wallet.createRandom(), quoter: { getPrice: () => { @@ -748,7 +748,7 @@ describe('NewOrder', function () { }) it('should format maker asset amount', async function () { const signedOrderResp = await newOrder({ - walletType: WalletType.MMP_VERSOIN_4, + walletType: WalletType.MMP_VERSION_4, signer: Wallet.createRandom(), quoter: { getPrice: () => { @@ -815,7 +815,7 @@ describe('NewOrder', function () { Wallet.createRandom().address.toLowerCase(), chainId, rfqAddr, - WalletType.MMP_VERSOIN_4 + WalletType.MMP_VERSION_4 // url ) console.log(signature) From 36a8bffd600516bf8e54b0636edee4d4ee1acdeb Mon Sep 17 00:00:00 2001 From: BenjaminLu Date: Wed, 12 Oct 2022 16:43:53 +0800 Subject: [PATCH 4/4] Use 128 bits prefix salt if market maker return it --- src/handler/newOrder.ts | 8 +++++--- src/quoting.ts | 3 ++- src/request/marketMaker/types.ts | 1 + src/signer/pmmv5.ts | 20 +++++++++++++++++--- src/signer/rfqv1.ts | 9 +++++++-- src/types/index.ts | 1 + test/new_order.spec.ts | 21 ++++++++++++++++----- 7 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/handler/newOrder.ts b/src/handler/newOrder.ts index e03a545..f106ab6 100644 --- a/src/handler/newOrder.ts +++ b/src/handler/newOrder.ts @@ -211,9 +211,8 @@ export const newOrder = async (ctx) => { const tokenConfigs = updaterStack.tokenConfigsFromImtokenUpdater.cacheResult const tokenList = getSupportedTokens() - const { rate, minAmount, maxAmount, quoteId } = rateBody + const { rate, minAmount, maxAmount, quoteId, salt } = rateBody const order = getOrderAndFeeFactor(query, rate, tokenList, tokenConfigs, config) - const resp: Response = { rate, minAmount, @@ -260,7 +259,10 @@ export const newOrder = async (ctx) => { chainID, config.addressBookV5.RFQ, walletType, - signingUrl + { + signingUrl, + salt, + } ) break default: diff --git a/src/quoting.ts b/src/quoting.ts index 04c6997..1620d79 100644 --- a/src/quoting.ts +++ b/src/quoting.ts @@ -18,7 +18,7 @@ export const removeQuoteIdPrefix = (quoteId: string): string => { } export const constructQuoteResponse = (indicativePrice: IndicativePriceApiResult, side: SIDE) => { - const { minAmount, maxAmount, message, makerAddress } = indicativePrice + const { minAmount, maxAmount, message, makerAddress, salt } = indicativePrice if (indicativePrice.exchangeable === false || !indicativePrice.price) { throw new BackendError( message || `Can't support this trade: ${JSON.stringify(indicativePrice)}` @@ -27,6 +27,7 @@ export const constructQuoteResponse = (indicativePrice: IndicativePriceApiResult const rate = side === 'BUY' ? 1 / indicativePrice.price : indicativePrice.price return { + salt, minAmount, maxAmount, rate: toBN((+rate).toFixed(DISPLAY_PRECEISION)).toNumber(), diff --git a/src/request/marketMaker/types.ts b/src/request/marketMaker/types.ts index c752e20..d08b773 100644 --- a/src/request/marketMaker/types.ts +++ b/src/request/marketMaker/types.ts @@ -29,6 +29,7 @@ export interface IndicativePriceApiResult { price: number makerAddress?: string message?: string + salt?: string } export interface PriceApiParams extends IndicativePriceApiParams { diff --git a/src/signer/pmmv5.ts b/src/signer/pmmv5.ts index 2cddb5d..3f2c143 100644 --- a/src/signer/pmmv5.ts +++ b/src/signer/pmmv5.ts @@ -33,10 +33,24 @@ const EIP712_ORDER_SCHEMA = { // - fee factor from salt // - user address from fee recipient -export const generateSaltWithFeeFactor = (feeFactor: number) => { - const feeHex = utils.hexZeroPad('0x' + feeFactor.toString(16), 2) +export const generateSaltWithFeeFactor = (feeFactor: number, prefixSalt?: string) => { // append 001e = 30 (fee factor to salt) - return new BigNumber(generatePseudoRandomSalt().toString(16).slice(0, -4) + feeHex.slice(2), 16) + const feeHex = utils.hexZeroPad('0x' + feeFactor.toString(16), 2) + if (prefixSalt) { + if (!(prefixSalt.toString().length === 32 || prefixSalt.toString().length === 34)) { + throw new Error('Invalid salt from market maker') + } + if (prefixSalt.toString().startsWith('0x')) { + prefixSalt = prefixSalt.toString().slice(2) + } + const postfixSalt = `${generatePseudoRandomSalt() + .toString(16) + .slice(0, 32) + .slice(0, -4)}${feeHex.slice(2)}` + return new BigNumber(`${prefixSalt}${postfixSalt}`, 16) + } else { + return new BigNumber(generatePseudoRandomSalt().toString(16).slice(0, -4) + feeHex.slice(2), 16) + } } // Signature: diff --git a/src/signer/rfqv1.ts b/src/signer/rfqv1.ts index b958701..4aedc58 100644 --- a/src/signer/rfqv1.ts +++ b/src/signer/rfqv1.ts @@ -98,12 +98,17 @@ export const buildSignedOrder = async ( chainId: number, rfqAddr: string, walletType: WalletType, - signingUrl?: string + options?: { + signingUrl?: string + salt?: string + } ): Promise => { // inject fee factor to salt const feeFactor = order.feeFactor order.takerAddress = userAddr.toLowerCase() - order.salt = generateSaltWithFeeFactor(feeFactor) + const salt = options ? options.salt : undefined + const signingUrl = options ? options.signingUrl : undefined + order.salt = generateSaltWithFeeFactor(feeFactor, salt) const rfqOrer = toRFQOrder(order) const orderHash = getOrderHash(rfqOrer) diff --git a/src/types/index.ts b/src/types/index.ts index f22df6d..53cb985 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -96,6 +96,7 @@ export interface QueryInterface { uniqId?: number | string userAddr?: string protocol?: Protocol + salt?: string } export enum Protocol { diff --git a/test/new_order.spec.ts b/test/new_order.spec.ts index 2d4b566..866f47f 100644 --- a/test/new_order.spec.ts +++ b/test/new_order.spec.ts @@ -12,6 +12,7 @@ import * as ethUtils from 'ethereumjs-util' import { Signer as TokenlonSigner, AllowanceTarget, USDT, ABI, WETH } from '@tokenlon/sdk' import * as crypto from 'crypto' import { expect } from 'chai' +import { generateSaltWithFeeFactor } from '../src/signer/pmmv5' const usdtHolders = { 1: '0x15abb66bA754F05cBC0165A64A11cDed1543dE48', 5: '0x031BBFB9379c4e6E3F42fb93a9f09C060c7fA037', @@ -809,16 +810,26 @@ describe('NewOrder', function () { expirationTimeSeconds: 1620444917, feeFactor: 30, } - const signature = await buildSignedOrder( + const signedOrder = await buildSignedOrder( signer, order, Wallet.createRandom().address.toLowerCase(), chainId, rfqAddr, - WalletType.MMP_VERSION_4 - // url + WalletType.MMP_VERSION_4, + { + salt: '0x11111111111111111111111111111111', + } ) - console.log(signature) - expect(signature).not.null + console.log(signedOrder) + expect(signedOrder).not.null + }) + it('Should generate correct salt', async () => { + const givenPrefixSalt = generateSaltWithFeeFactor(30, '0x11111111111111111111111111111111') + const salt = generateSaltWithFeeFactor(30) + console.log(givenPrefixSalt.toString(16)) + console.log(salt.toString(16)) + expect(givenPrefixSalt.toString(16).length).is.eq(64) + expect(salt.toString(16).length).is.eq(64) }) })