Skip to content

Commit a7176f4

Browse files
authored
Merge pull request #48 from consenlabs/feature/forward_orders_to_external_service
Forwarding unsigned orders if SIGNING_URL is set by market maker
2 parents d91593d + 36a8bff commit a7176f4

File tree

13 files changed

+164
-54
lines changed

13 files changed

+164
-54
lines changed

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ See [docs](https://docs.token.im/tokenlon-mmsk/)
66

77
## Setup
88

9-
Require Node.JS v10 as runtime.
9+
Require Node.JS v12 as runtime.
1010

1111
Program setup,
1212
- Create a wallet as order signer, and save it as keystore or private key
@@ -20,8 +20,15 @@ Program setup,
2020
- PROVIDER_URL, point to ethereum node, like your infura endpoint
2121
- WALLET_ADDRESS, as your signer wallet address
2222
- WALLET_PRIVATE_KEY, private key of above wallet, or use WALLET_KEYSTORE
23+
- WALLET_TYPE, a market maker's wallet smart contract.
24+
- types.WalletType.MMP_VERSION_4 (compatible with PMM protocol, see [example contract](https://gist.github.com/NIC619/a3db1a743175bf592f2db983f17680dd#file-mmpv4-sol-L1236))
25+
- types.WalletType.MMP_VERSION_5
26+
- types.WalletType.ERC1271
27+
- types.WalletType.EOA
28+
- SIGNING_URL, If you wanna sign orders in your own service instead of the mmsk,
29+
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.
2330
- HTTP_SERVER_ENDPOINT, your backend http server
24-
- CHAIN_ID, 1 for mainnet, 42 for testnet(kovan)
31+
- CHAIN_ID, 1 for mainnet, 5 for testnet(Goerli)
2532
- Testing with `node app/check.js`
2633
- Register contract address & signer address & MMSK server url to Tokenlon team
2734

app/mmConfig.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,37 @@
11
const types = require('../lib/signer/types')
22

33
module.exports = {
4-
//
54
// Tokenlon server address
65
EXCHANGE_URL: process.env.EXCHANGE_URL,
76
PROVIDER_URL: process.env.PROVIDER_URL,
87

9-
//
10-
// Wallet
8+
// Signing
9+
/**
10+
* If you wanna sign orders in your own service instead of the mmsk,
11+
* please set the SIGNING_URL to your service endpoint.
12+
* the mmsk would post every unsigned orders to your service.
13+
* Remember to set the WALLET_ADDRESS as well.
14+
*/
15+
SIGNING_URL: process.env.SIGNING_URL,
1116
WALLET_ADDRESS: process.env.WALLET_ADDRESS,
12-
WALLET_TYPE: types.WalletType.ERC1271,
17+
WALLET_TYPE: types.WalletType.MMP_VERSION_4,
1318
USE_KEYSTORE: false,
1419
WALLET_KEYSTORE: {},
20+
/**
21+
* If you set the SIGNING_URL and WALLET_ADDRESS, it's unnecessary to set the WALLET_PRIVATE_KEY.
22+
* It would forward evey unsigned order to SIGNING_URL instead of signing orders with WALLET_PRIVATE_KEY
23+
*/
1524
WALLET_PRIVATE_KEY: process.env.WALLET_PRIVATE_KEY,
1625

1726
// AMM
1827
AMMWRAPPER_CONTRACT_ADDRESS: process.env.AMMWRAPPER_CONTRACT_ADDRESS,
1928

20-
//
2129
// MM backend config
2230
HTTP_SERVER_ENDPOINT: process.env.HTTP_SERVER_ENDPOINT,
2331
// ZERORPC_SERVER_ENDPOINT: process.env.ZERORPC_SERVER_ENDPOINT,
2432

25-
//
2633
// Server config
27-
CHAIN_ID: process.env.CHAIN_ID || 42,
34+
CHAIN_ID: process.env.CHAIN_ID || 5,
2835
MMSK_SERVER_PORT: process.env.MMSK_SERVER_PORT || 80,
2936
SENTRY_DSN: '',
3037
NODE_ENV: 'PRODUCTION',

src/check/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const checkMMSK = async (config: ConfigForStart) => {
3131
quoter = new QuoteDispatcher(config.HTTP_SERVER_ENDPOINT, QuoterProtocol.HTTP)
3232
}
3333
const wallet = getWallet()
34-
await startUpdater(quoter, wallet)
34+
await startUpdater(quoter, wallet.address)
3535

3636
for (let i = 0; i < arr.length; i += 1) {
3737
const item = arr[i]

src/handler/newOrder.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,15 +186,13 @@ function getOrderAndFeeFactor(query: QueryInterface, rate, tokenList, tokenConfi
186186
}
187187

188188
const _getBaseTokenByAddress = (baseTokenAddr, tokenList) => {
189-
return tokenList.find(
190-
(token) => token.contractAddress.toLowerCase() === baseTokenAddr
191-
)
189+
return tokenList.find((token) => token.contractAddress.toLowerCase() === baseTokenAddr)
192190
}
193191

194192
const getBaseTokenByAddress = memoize(_getBaseTokenByAddress)
195193

196194
export const newOrder = async (ctx) => {
197-
const { quoter, signer, chainID, walletType } = ctx
195+
const { quoter, signer, chainID, walletType, signingUrl } = ctx
198196
const req: QueryInterface = {
199197
protocol: Protocol.PMMV5, // by default is v2 protocol
200198
...ctx.query, // overwrite from request
@@ -213,9 +211,8 @@ export const newOrder = async (ctx) => {
213211
const tokenConfigs = updaterStack.tokenConfigsFromImtokenUpdater.cacheResult
214212
const tokenList = getSupportedTokens()
215213

216-
const { rate, minAmount, maxAmount, quoteId } = rateBody
214+
const { rate, minAmount, maxAmount, quoteId, salt } = rateBody
217215
const order = getOrderAndFeeFactor(query, rate, tokenList, tokenConfigs, config)
218-
219216
const resp: Response = {
220217
rate,
221218
minAmount,
@@ -261,7 +258,11 @@ export const newOrder = async (ctx) => {
261258
userAddr.toLowerCase(),
262259
chainID,
263260
config.addressBookV5.RFQ,
264-
walletType
261+
walletType,
262+
{
263+
signingUrl,
264+
salt,
265+
}
265266
)
266267
break
267268
default:

src/quoting.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const removeQuoteIdPrefix = (quoteId: string): string => {
1818
}
1919

2020
export const constructQuoteResponse = (indicativePrice: IndicativePriceApiResult, side: SIDE) => {
21-
const { minAmount, maxAmount, message, makerAddress } = indicativePrice
21+
const { minAmount, maxAmount, message, makerAddress, salt } = indicativePrice
2222
if (indicativePrice.exchangeable === false || !indicativePrice.price) {
2323
throw new BackendError(
2424
message || `Can't support this trade: ${JSON.stringify(indicativePrice)}`
@@ -27,6 +27,7 @@ export const constructQuoteResponse = (indicativePrice: IndicativePriceApiResult
2727

2828
const rate = side === 'BUY' ? 1 / indicativePrice.price : indicativePrice.price
2929
return {
30+
salt,
3031
minAmount,
3132
maxAmount,
3233
rate: toBN((+rate).toFixed(DISPLAY_PRECEISION)).toNumber(),

src/request/marketMaker/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export interface IndicativePriceApiResult {
2929
price: number
3030
makerAddress?: string
3131
message?: string
32+
salt?: string
3233
}
3334

3435
export interface PriceApiParams extends IndicativePriceApiParams {

src/signer/pmmv5.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,24 @@ const EIP712_ORDER_SCHEMA = {
3333
// - fee factor from salt
3434
// - user address from fee recipient
3535

36-
export const generateSaltWithFeeFactor = (feeFactor: number) => {
37-
const feeHex = utils.hexZeroPad('0x' + feeFactor.toString(16), 2)
36+
export const generateSaltWithFeeFactor = (feeFactor: number, prefixSalt?: string) => {
3837
// append 001e = 30 (fee factor to salt)
39-
return new BigNumber(generatePseudoRandomSalt().toString(16).slice(0, -4) + feeHex.slice(2), 16)
38+
const feeHex = utils.hexZeroPad('0x' + feeFactor.toString(16), 2)
39+
if (prefixSalt) {
40+
if (!(prefixSalt.toString().length === 32 || prefixSalt.toString().length === 34)) {
41+
throw new Error('Invalid salt from market maker')
42+
}
43+
if (prefixSalt.toString().startsWith('0x')) {
44+
prefixSalt = prefixSalt.toString().slice(2)
45+
}
46+
const postfixSalt = `${generatePseudoRandomSalt()
47+
.toString(16)
48+
.slice(0, 32)
49+
.slice(0, -4)}${feeHex.slice(2)}`
50+
return new BigNumber(`${prefixSalt}${postfixSalt}`, 16)
51+
} else {
52+
return new BigNumber(generatePseudoRandomSalt().toString(16).slice(0, -4) + feeHex.slice(2), 16)
53+
}
4054
}
4155

4256
// Signature:

src/signer/rfqv1.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getOrderHash, getOrderSignDigest } from './orderHash'
55
import { RFQOrder, WalletType } from './types'
66
import * as ethUtils from 'ethereumjs-util'
77
import { SignatureType } from './types'
8+
import axios from 'axios'
89

910
// spec of RFQV1
1011
// - taker address point to userAddr
@@ -36,7 +37,7 @@ export async function signByMMPSigner(
3637
wallet: Wallet,
3738
walletType: WalletType
3839
): Promise<string> {
39-
if (walletType === WalletType.MMP_VERSOIN_4) {
40+
if (walletType === WalletType.MMP_VERSION_4) {
4041
// For V4 Maket Maker Proxy (MMP)
4142
// Signature:
4243
// +------|---------|---------|---------|---------|---------+
@@ -80,28 +81,55 @@ export async function signByMMPSigner(
8081
}
8182
}
8283

84+
export const forwardUnsignedOrder = async (signingUrl: string, orderInfo: any): Promise<string> => {
85+
const resp = await axios.post(signingUrl, orderInfo)
86+
const body = resp.data
87+
if (body.signature) {
88+
return body.signature
89+
} else {
90+
throw new Error('Invalid signature')
91+
}
92+
}
93+
8394
export const buildSignedOrder = async (
8495
signer: Wallet,
8596
order,
8697
userAddr: string,
8798
chainId: number,
8899
rfqAddr: string,
89-
walletType: WalletType
100+
walletType: WalletType,
101+
options?: {
102+
signingUrl?: string
103+
salt?: string
104+
}
90105
): Promise<any> => {
91106
// inject fee factor to salt
92107
const feeFactor = order.feeFactor
93108
order.takerAddress = userAddr.toLowerCase()
94-
order.salt = generateSaltWithFeeFactor(feeFactor)
109+
const salt = options ? options.salt : undefined
110+
const signingUrl = options ? options.signingUrl : undefined
111+
order.salt = generateSaltWithFeeFactor(feeFactor, salt)
95112

96113
const rfqOrer = toRFQOrder(order)
97114
const orderHash = getOrderHash(rfqOrer)
98115
console.log(`orderHash: ${orderHash}`)
99116
const orderSignDigest = getOrderSignDigest(rfqOrer, chainId, rfqAddr)
100117
console.log(`orderSignDigest: ${orderSignDigest}`)
101-
const makerWalletSignature =
102-
signer.address.toLowerCase() == order.makerAddress.toLowerCase()
103-
? await signByEOA(orderSignDigest, signer)
104-
: await signByMMPSigner(orderSignDigest, userAddr, feeFactor, signer, walletType)
118+
let makerWalletSignature
119+
if (!signingUrl) {
120+
makerWalletSignature =
121+
signer.address.toLowerCase() == order.makerAddress.toLowerCase()
122+
? await signByEOA(orderSignDigest, signer)
123+
: await signByMMPSigner(orderSignDigest, userAddr, feeFactor, signer, walletType)
124+
} else {
125+
makerWalletSignature = await forwardUnsignedOrder(signingUrl, {
126+
rfqOrer: rfqOrer,
127+
userAddr: userAddr,
128+
signer: signer.address,
129+
chainId: chainId,
130+
rfqAddr: rfqAddr,
131+
})
132+
}
105133

106134
const signedOrder = {
107135
...order,

src/signer/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ export enum SignatureType {
4545
}
4646

4747
export enum WalletType {
48-
EOA = 0,
49-
MMP_VERSOIN_4 = 1, // https://gist.github.com/NIC619/a3db1a743175bf592f2db983f17680dd#file-mmpv4-sol-L1236
48+
MMP_VERSION_4 = 1, // https://gist.github.com/NIC619/a3db1a743175bf592f2db983f17680dd#file-mmpv4-sol-L1236
5049
MMP_VERSION_5 = 2, // https://github.com/consenlabs/tokenlon-contracts/blob/e2edf7581b69bc8a40e61ff7fc1cd29674ae4887/contracts/MarketMakerProxy.sol#L19
5150
ERC1271 = 3, // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.6.0/contracts/utils/cryptography/SignatureChecker.sol#L36
51+
EOA = 4, // less security for market makers
5252
}

src/start.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { VERSION } from './handler/version'
2828
// FIXME: construct wallet(signer), quoter and worker separately
2929
// FIXME: better retry implementation
3030
const beforeStart = async (config: ConfigForStart, triedTimes?: number) => {
31-
const wallet = getWallet()
31+
// const wallet = getWallet()
3232
triedTimes = triedTimes || 0
3333
try {
3434
let quoter: Quoter
@@ -37,7 +37,7 @@ const beforeStart = async (config: ConfigForStart, triedTimes?: number) => {
3737
} else {
3838
quoter = new QuoteDispatcher(config.HTTP_SERVER_ENDPOINT, QuoterProtocol.HTTP)
3939
}
40-
await startUpdater(quoter, wallet)
40+
await startUpdater(quoter, config.WALLET_ADDRESS)
4141
return quoter
4242
} catch (e) {
4343
triedTimes += 1
@@ -69,21 +69,25 @@ export const startMMSK = async (config: ConfigForStart) => {
6969
const app = new Koa()
7070
const router = new Router()
7171
const MMSK_SERVER_PORT = config.MMSK_SERVER_PORT || 80
72-
72+
let wallet
7373
setConfig(config)
7474
try {
75-
const wallet = getWallet()
76-
if (wallet.address.toLowerCase() != config.WALLET_ADDRESS.toLowerCase()) {
77-
throw `wallet's address${wallet.address} and ${
78-
config.USE_KEYSTORE ? 'keystore' : 'privateKey'
79-
}(${config.WALLET_ADDRESS}) not matched`
75+
console.log(config.SIGNING_URL)
76+
if (!config.SIGNING_URL) {
77+
wallet = getWallet()
78+
if (!wallet) {
79+
throw new Error(`Please set either WALLET_PRIVATE_KEY or SIGNING_URL`)
80+
}
81+
if (wallet.address.toLowerCase() != config.WALLET_ADDRESS.toLowerCase()) {
82+
throw `wallet's address${wallet.address} and ${
83+
config.USE_KEYSTORE ? 'keystore' : 'privateKey'
84+
}(${config.WALLET_ADDRESS}) not matched`
85+
}
8086
}
81-
8287
console.log({
8388
version: VERSION,
84-
signerAddress: wallet.address,
85-
mmpAddress: config.WALLET_ADDRESS,
86-
mmpType: config.WALLET_TYPE || WalletType.MMP_VERSOIN_4,
89+
signerAddress: config.WALLET_ADDRESS,
90+
mmpType: config.WALLET_TYPE || WalletType.MMP_VERSION_4,
8791
chainId: config.CHAIN_ID,
8892
exchangeUrl: config.EXCHANGE_URL,
8993
})
@@ -108,8 +112,13 @@ export const startMMSK = async (config: ConfigForStart) => {
108112

109113
app.context.chainID = config.CHAIN_ID || 5
110114
app.context.quoter = quoter
111-
app.context.signer = wallet
112-
app.context.walletType = config.WALLET_TYPE || WalletType.MMP_VERSOIN_4
115+
if (wallet) {
116+
app.context.signer = wallet
117+
}
118+
if (config.SIGNING_URL) {
119+
app.context.signingUrl = config.SIGNING_URL
120+
}
121+
app.context.walletType = config.WALLET_TYPE || WalletType.MMP_VERSION_4
113122

114123
app
115124
.use(async (ctx, next) => {

0 commit comments

Comments
 (0)