From dc3badfa7f0dca5e68154aa7937098eb8fb6676c Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 30 Jan 2022 01:58:35 +0100 Subject: [PATCH 001/160] Add object for multiple wallets, remove old ACCOUNT_STATE --- config.json.EXAMPLE | 4 ++- marketmaker.js | 65 +++++++++++++++++++++++++++++++++------------ 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/config.json.EXAMPLE b/config.json.EXAMPLE index 0d5a3bd..81260f1 100644 --- a/config.json.EXAMPLE +++ b/config.json.EXAMPLE @@ -1,6 +1,8 @@ { "cryptowatchApiKey": "aaaaxxx", - "ethPrivKey": "ccccxxxxx", + "ethPrivKeys": [ + "ccccxxxxx" + ], "zigzagChainId": 1, "zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com", "pairs": { diff --git a/marketmaker.js b/marketmaker.js index 9b861d6..b4d3cf6 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -11,7 +11,7 @@ dotenv.config(); const PRICE_FEEDS = {}; const OPEN_ORDERS = {}; const NONCES = {}; -let ACCOUNT_STATE = null; +const WALLETS = {}; // Load MM config let MM_CONFIG; @@ -42,20 +42,29 @@ setTimeout(processFillQueue, 1000); // Connect to zksync const CHAIN_ID = parseInt(MM_CONFIG.zigzagChainId); const ETH_NETWORK = (CHAIN_ID === 1) ? "mainnet" : "rinkeby"; -let syncWallet, ethersProvider, syncProvider, ethWallet, - fillOrdersInterval, indicateLiquidityInterval; +let ethersProvider, syncProvider, fillOrdersInterval, indicateLiquidityInterval; ethersProvider = ethers.getDefaultProvider(ETH_NETWORK); try { syncProvider = await zksync.getDefaultProvider(ETH_NETWORK); - ethWallet = new ethers.Wallet(process.env.ETH_PRIVKEY || MM_CONFIG.ethPrivKey); - syncWallet = await zksync.Wallet.fromEthSigner(ethWallet, syncProvider); - if (!(await syncWallet.isSigningKeySet())) { - console.log("setting sign key"); - const signKeyResult = await syncWallet.setSigningKey({ - feeToken: "ETH", - ethAuthType: "ECDSA", - }); - console.log(signKeyResult); + const ethPrivKeys = (process.env.ETH_PRIVKEY || MM_CONFIG.ethPrivKeys) + for(let i=0; i { + const thisAccountSellCurrency = WALLETS[accountId]['account_state'].committed.balances["ETH"]; + if (highestSellCurrency < thisAccountSellCurrency) { + highestSellCurrency = thisAccountSellCurrency; + } + }); + const balance = highestSellCurrency / 10**sellDecimals; const now = Date.now() / 1000 | 0; if (now > expires) { @@ -466,8 +481,20 @@ function indicateLiquidity (market_id) { const midPrice = getMidPrice(market_id); const expires = (Date.now() / 1000 | 0) + 10; // 10s expiry const side = mmConfig.side || 'd'; - const baseBalance = ACCOUNT_STATE.committed.balances[marketInfo.baseAsset.symbol] / 10**marketInfo.baseAsset.decimals; - const quoteBalance = ACCOUNT_STATE.committed.balances[marketInfo.quoteAsset.symbol] / 10**marketInfo.quoteAsset.decimals; + + let baseBN = 0, quoteBN = 0; + Object.keys(WALLETS).forEach(accountId => { + const thisBase = WALLETS[accountId]['account_state'].committed.balances[marketInfo.baseAsset.symbol]; + const thisQuote = WALLETS[accountId]['account_state'].committed.balances[marketInfo.quoteAsset.symbol]; + if (baseBN < thisBase) { + baseBN = thisBase; + } + if (quoteBN < thisQuote) { + quoteBN = thisQuote; + } + } + const baseBalance = baseBN / 10**marketInfo.baseAsset.decimals; + const quoteBalance = quoteBN / 10**marketInfo.quoteAsset.decimals; const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize); if (!midPrice) return false; @@ -502,5 +529,9 @@ function getMidPrice (market_id) { } async function updateAccountState() { - ACCOUNT_STATE = await syncWallet.getAccountState(); + Object.keys(WALLETS).forEach(accountId => { + (WALLETS[accountId]['syncWallet']).getAccountState().then((state) => { + WALLETS[accountId]['account_state'] = state; + }) + }); } From 0d45c21fc7145060909618a37040c1c301488da5 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 30 Jan 2022 02:25:41 +0100 Subject: [PATCH 002/160] Use individual syncWallet & ORDER_BROADCASTING --- marketmaker.js | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index b4d3cf6..d1ad964 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -35,7 +35,6 @@ console.log("ACTIVE PAIRS", activePairs); cryptowatchWsSetup(); // Initiate fill loop -let ORDER_BROADCASTING = false; const FILL_QUEUE = []; setTimeout(processFillQueue, 1000); @@ -63,7 +62,8 @@ try { WALLETS[accountId] = { 'ethWallet': ethWallet, 'syncWallet': syncWallet, - 'account_state': account_state + 'account_state': account_state, + 'ORDER_BROADCASTING': false, } } } catch (e) { @@ -112,7 +112,9 @@ function onWsOpen() { function onWsClose () { console.log("Websocket closed. Restarting"); - ORDER_BROADCASTING = false; + Object.keys(WALLETS).forEach(accountId => { + WALLETS[accountId]['ORDER_BROADCASTING'] = false; + } setTimeout(() => { clearInterval(fillOrdersInterval) clearInterval(indicateLiquidityInterval) @@ -127,7 +129,9 @@ async function handleMessage(json) { if (!(["lastprice", "liquidity2"]).includes(msg.op)) console.log(json.toString()); switch(msg.op) { case 'error': - ORDER_BROADCASTING = false; + Object.keys(WALLETS).forEach(accountId => { + WALLETS[accountId]['ORDER_BROADCASTING'] = false; + } break; case 'orders': const orders = msg.args[0]; @@ -151,7 +155,10 @@ async function handleMessage(json) { } catch (e) { console.error(e); } - ORDER_BROADCASTING = false; + const walletId = msg.args[3].fillOrder.accountId; + if(WALLETS[walletId]) { // first check if walletId from fillOrder is good + WALLETS[walletId]['ORDER_BROADCASTING'] = false; + } break default: break @@ -285,7 +292,7 @@ function validatePriceFeed(market_id) { return true; } -async function sendfillrequest(orderreceipt) { +async function sendfillrequest(orderreceipt, accountId) { const chainId = orderreceipt[0]; const orderId = orderreceipt[1]; const market_id = orderreceipt[2]; @@ -326,10 +333,10 @@ async function sendfillrequest(orderreceipt) { ratio: zksync.utils.tokenRatio(tokenRatio), validUntil: one_min_expiry } - const fillOrder = await syncWallet.getOrder(orderDetails); - - // Set global flag - ORDER_BROADCASTING = true; + const fillOrder = await WALLETS[accountId].syncWallet.getOrder(orderDetails); + + // Set wallet flag + WALLETS[accountId]['ORDER_BROADCASTING'] = true; const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] }; zigzagws.send(JSON.stringify(resp)); @@ -342,9 +349,13 @@ async function broadcastfill(chainid, orderid, swapOffer, fillOrder) { if (nonce <= userNonce) { throw new Error("badnonce"); } + const wallet = WALLETS[fillOrder.accountId]; + if(!wallet) { + throw new Error("No wallet with this accountId: "+fillOrder.accountId); + } const randint = (Math.random()*1000).toFixed(0); console.time('syncswap' + randint); - const swap = await syncWallet.syncSwap({ + const swap = await wallet['syncWallet'].syncSwap({ orders: [swapOffer, fillOrder], feeToken: "ETH", nonce: fillOrder.nonce From 02c5f86e80f13a7578f085ed6f6fcf642caa63d9 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 30 Jan 2022 02:51:06 +0100 Subject: [PATCH 003/160] Part1 of new processFillQueue logic TODO add logic to pick order. FILL_QUEUE.shift() might not result in a good order for this wallet --- marketmaker.js | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index d1ad964..79d8608 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -401,22 +401,27 @@ async function fillOpenOrders() { } async function processFillQueue() { - if (ORDER_BROADCASTING) { - setTimeout(processFillQueue, 100); - return false; - } - if (FILL_QUEUE.length === 0) { - setTimeout(processFillQueue, 100); - return false; - } - const order = FILL_QUEUE.shift(); - try { - await sendfillrequest(order); - } catch (e) { - console.error(e); - ORDER_BROADCASTING = false; - } - setTimeout(processFillQueue, 50); + Object.keys(WALLETS).forEach(accountId => { + const wallet = WALLETS[accountId]; + if (wallet['ORDER_BROADCASTING']) { + return; + } + if (FILL_QUEUE.length === 0) { + return; + } + let order + for(let i=0; i Date: Sun, 30 Jan 2022 20:56:32 +0100 Subject: [PATCH 004/160] finish processFillQueue, some refactoring --- marketmaker.js | 61 +++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 79d8608..d3f2767 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -140,7 +140,7 @@ async function handleMessage(json) { const fillable = isOrderFillable(order); console.log(fillable); if (fillable.fillable) { - FILL_QUEUE.push(order); + FILL_QUEUE.push({ order: order, wallets: fillable.wallets}); } else if (fillable.reason === "badprice") { OPEN_ORDERS[orderid] = order; @@ -150,14 +150,16 @@ async function handleMessage(json) { case "userordermatch": const chainid = msg.args[0]; const orderid = msg.args[1]; - try { - await broadcastfill(chainid, orderid, msg.args[2], msg.args[3]); - } catch (e) { - console.error(e); - } - const walletId = msg.args[3].fillOrder.accountId; - if(WALLETS[walletId]) { // first check if walletId from fillOrder is good - WALLETS[walletId]['ORDER_BROADCASTING'] = false; + const wallet = WALLETS[fillOrder.accountId]; + if(wallet) { + try { + await broadcastfill(chainid, orderid, msg.args[2], msg.args[3], wallet); + wallet['ORDER_BROADCASTING'] = false; + } catch (e) { + console.error(e); + } + } else { + console.error("No wallet with this accountId: "+fillOrder.accountId); } break default: @@ -183,14 +185,13 @@ function isOrderFillable(order) { const sellCurrency = (side === 's') ? market.quoteAsset.symbol : market.baseAsset.symbol; const sellDecimals = (side === 's') ? market.quoteAsset.decimals : market.baseAsset.decimals; const sellQuantity = (side === 's') ? quoteQuantity : baseQuantity; - let highestSellCurrency = 0; + const neededBalanceBN = sellQuantity * 10**sellDecimals; + const goodWallets = []; Object.keys(WALLETS).forEach(accountId => { - const thisAccountSellCurrency = WALLETS[accountId]['account_state'].committed.balances["ETH"]; - if (highestSellCurrency < thisAccountSellCurrency) { - highestSellCurrency = thisAccountSellCurrency; + if (WALLETS[accountId]['account_state'].committed.balances[sellCurrency] > (neededBalanceBN * 1.05)) { + goodWallets.push(accountId); } }); - const balance = highestSellCurrency / 10**sellDecimals; const now = Date.now() / 1000 | 0; if (now > expires) { @@ -208,7 +209,7 @@ function isOrderFillable(order) { return { fillable: false, reason: "badsize" }; } - if (balance < sellQuantity) { + if (goodWallets.length === 0) { return { fillable: false, reason: "badbalance" }; } @@ -226,7 +227,7 @@ function isOrderFillable(order) { return { fillable: false, reason: "badprice" }; } - return { fillable: true, reason: null }; + return { fillable: true, reason: null, wallets: goodWallets}; } function genquote(chainid, market_id, side, baseQuantity) { @@ -342,17 +343,13 @@ async function sendfillrequest(orderreceipt, accountId) { zigzagws.send(JSON.stringify(resp)); } -async function broadcastfill(chainid, orderid, swapOffer, fillOrder) { +async function broadcastfill(chainid, orderid, swapOffer, fillOrder, wallet) { // Nonce check const nonce = swapOffer.nonce; const userNonce = NONCES[swapOffer.accountId]; if (nonce <= userNonce) { throw new Error("badnonce"); } - const wallet = WALLETS[fillOrder.accountId]; - if(!wallet) { - throw new Error("No wallet with this accountId: "+fillOrder.accountId); - } const randint = (Math.random()*1000).toFixed(0); console.time('syncswap' + randint); const swap = await wallet['syncWallet'].syncSwap({ @@ -391,7 +388,7 @@ async function fillOpenOrders() { const order = OPEN_ORDERS[orderid]; const fillable = isOrderFillable(order); if (fillable.fillable) { - FILL_QUEUE.push(order); + FILL_QUEUE.push({ order: order, wallets: fillable.wallets}); delete OPEN_ORDERS[orderid]; } else if (fillable.reason !== "badprice") { @@ -411,16 +408,18 @@ async function processFillQueue() { } let order for(let i=0; i Date: Sun, 30 Jan 2022 21:52:58 +0100 Subject: [PATCH 005/160] Fixes, refactors --- marketmaker.js | 57 +++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index ee8523e..85baeb9 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -45,7 +45,7 @@ let ethersProvider, syncProvider, fillOrdersInterval, indicateLiquidityInterval; ethersProvider = ethers.getDefaultProvider(ETH_NETWORK); try { syncProvider = await zksync.getDefaultProvider(ETH_NETWORK); - const ethPrivKeys = (process.env.ETH_PRIVKEY || MM_CONFIG.ethPrivKeys) + const ethPrivKeys = (process.env.ETH_PRIVKEY || MM_CONFIG.ethPrivKeys); for(let i=0; i { const wallet = WALLETS[accountId]; if (wallet['ORDER_BROADCASTING']) { return; } - if (FILL_QUEUE.length === 0) { - return; + let index = 0; + for(;index { const thisBase = WALLETS[accountId]['account_state'].committed.balances[marketInfo.baseAsset.symbol]; const thisQuote = WALLETS[accountId]['account_state'].committed.balances[marketInfo.quoteAsset.symbol]; - if (baseBN < thisBase) { - baseBN = thisBase; - } - if (quoteBN < thisQuote) { - quoteBN = thisQuote; - } + baseBN = (baseBN < thisBase) ? thisBase : baseBN; + quoteBN = (quoteBN < thisQuote) ? thisQuote : quoteBN; } const baseBalance = baseBN / 10**marketInfo.baseAsset.decimals; const quoteBalance = quoteBN / 10**marketInfo.quoteAsset.decimals; From b0009aabdcccc9a35a843e6952dae5639650e476 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Mon, 31 Jan 2022 00:08:58 +0100 Subject: [PATCH 006/160] Add balance logging Added try/catch for updateAccountState, no need to fail the mm at an error. With multiple wallets it gets harder to track all accounts. For user comfort, log total USD balance every 3h. --- marketmaker.js | 50 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 85baeb9..eefc546 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -74,6 +74,10 @@ try { // Update account state loop setInterval(updateAccountState, 30000); +// Log mm balance over all accounts +logBalance(); +setInterval(logBalance, 3 * 60 * 60 * 1000); // 3h + // Get markets info const activePairsText = activePairs.join(','); const markets_url = `https://zigzag-markets.herokuapp.com/markets?chainid=${CHAIN_ID}&id=${activePairsText}` @@ -543,9 +547,45 @@ function getMidPrice (market_id) { } async function updateAccountState() { - Object.keys(WALLETS).forEach(accountId => { - (WALLETS[accountId]['syncWallet']).getAccountState().then((state) => { - WALLETS[accountId]['account_state'] = state; - }) - }); + try { + Object.keys(WALLETS).forEach(accountId => { + (WALLETS[accountId]['syncWallet']).getAccountState().then((state) => { + WALLETS[accountId]['account_state'] = state; + }) + }); + } catch(err) { + // pass + } +} + +async function logBalance() { + try { + await updateAccountState(); + // fetch all balances over all wallets per token + const balance = {}; + Object.keys(WALLETS).forEach(accountId => { + const committedBalaces = WALLETS[accountId]['account_state'].committed.balances; + Object.keys(committedBalaces).forEach(token => { + if(balance[token]) { + balance[token] = balance[token] + parseInt(committedBalaces[token]); + } else { + balance[token] = parseInt(committedBalaces[token]); + } + }); + }); + // get token price and total in USD + let sum = 0; + await Promise.all(Object.keys(balance).map(async token => { + const price = await syncProvider.getTokenPrice(token.toString()); + const tokenNumber = await syncProvider.tokenSet.formatToken(token, balance[token].toString()) + sum = sum + price * tokenNumber; + })); + + // log to CVS + const date = new Date().toISOString() + const content = date + ";" + sum.toFixed(2) + "\n"; + fs.writeFile('price_csv.txt', content, { flag: 'a+' }) + } catch(err) { + // pass + } } From c270a64002f8549eadfcb08e413e941ed3d4f2aa Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Mon, 31 Jan 2022 00:38:00 +0100 Subject: [PATCH 007/160] fixes --- marketmaker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index eefc546..c13631b 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -582,9 +582,9 @@ async function logBalance() { })); // log to CVS - const date = new Date().toISOString() + const date = new Date().toISOString(); const content = date + ";" + sum.toFixed(2) + "\n"; - fs.writeFile('price_csv.txt', content, { flag: 'a+' }) + fs.writeFile('price_csv.txt', content, { flag: 'a+' }, err => {}); } catch(err) { // pass } From 424da8c63716664362320f37a8e983669b724b3c Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Mon, 31 Jan 2022 18:21:44 +0100 Subject: [PATCH 008/160] Small fixes --- marketmaker.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index c13631b..e864011 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -116,7 +116,7 @@ function onWsClose () { console.log("Websocket closed. Restarting"); Object.keys(WALLETS).forEach(accountId => { WALLETS[accountId]['ORDER_BROADCASTING'] = false; - } + }); setTimeout(() => { clearInterval(fillOrdersInterval) clearInterval(indicateLiquidityInterval) @@ -133,7 +133,7 @@ async function handleMessage(json) { case 'error': Object.keys(WALLETS).forEach(accountId => { WALLETS[accountId]['ORDER_BROADCASTING'] = false; - } + }); break; case 'orders': const orders = msg.args[0]; @@ -152,13 +152,14 @@ async function handleMessage(json) { case "userordermatch": const chainid = msg.args[0]; const orderid = msg.args[1]; + const fillOrder = msg.args[3]; const wallet = WALLETS[fillOrder.accountId]; if(!wallet) { console.error("No wallet with this accountId: "+fillOrder.accountId); break } else { try { - await broadcastfill(chainid, orderid, msg.args[2], msg.args[3], wallet); + await broadcastfill(chainid, orderid, msg.args[2], fillOrder, wallet); } catch (e) { console.error(e); } @@ -405,7 +406,7 @@ async function processFillQueue() { setTimeout(processFillQueue, 100); return; } - Object.keys(WALLETS).forEach(accountId => { + await Promise.all(Object.keys(WALLETS).map(async accountId => { const wallet = WALLETS[accountId]; if (wallet['ORDER_BROADCASTING']) { return; @@ -417,16 +418,16 @@ async function processFillQueue() { } } if (index < FILL_QUEUE.length) { - const order = FILL_QUEUE.splice(index, 1); + const selectedOrder = FILL_QUEUE.splice(index, 1); try { - await sendfillrequest(order.order, accountId); + await sendfillrequest(selectedOrder[0].order, accountId); return; } catch (e) { console.error(e); wallet['ORDER_BROADCASTING'] = false; } } - }); + })); setTimeout(processFillQueue, 100); } @@ -510,7 +511,7 @@ function indicateLiquidity (market_id) { const thisQuote = WALLETS[accountId]['account_state'].committed.balances[marketInfo.quoteAsset.symbol]; baseBN = (baseBN < thisBase) ? thisBase : baseBN; quoteBN = (quoteBN < thisQuote) ? thisQuote : quoteBN; - } + }); const baseBalance = baseBN / 10**marketInfo.baseAsset.decimals; const quoteBalance = quoteBN / 10**marketInfo.quoteAsset.decimals; const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); From 492040c251a84603fcc7729e3f0d476c824c6cc1 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 1 Feb 2022 12:01:09 +0100 Subject: [PATCH 009/160] Backwards compatibility to old config Will work with 'ethPrivKey' and 'ethPrivKeys' --- marketmaker.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index e864011..ae04cbf 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -45,7 +45,15 @@ let ethersProvider, syncProvider, fillOrdersInterval, indicateLiquidityInterval; ethersProvider = ethers.getDefaultProvider(ETH_NETWORK); try { syncProvider = await zksync.getDefaultProvider(ETH_NETWORK); - const ethPrivKeys = (process.env.ETH_PRIVKEY || MM_CONFIG.ethPrivKeys); + const keys = []; + const ethPrivKey = (process.env.ETH_PRIVKEY || MM_CONFIG.ethPrivKey); + if(ethPrivKey) { keys.push(ethPrivKey); } + const ethPrivKeys = (process.env.ETH_PRIVKEYS || MM_CONFIG.ethPrivKeys); + if(ethPrivKeys && ethPrivKeys.length > 0) { + ethPrivKeys.forEach( key => { + keys.push(key); + }); + } for(let i=0; i Date: Tue, 1 Feb 2022 13:45:45 +0100 Subject: [PATCH 010/160] fix and ignore empty key --- marketmaker.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index ae04cbf..9d95c04 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -47,15 +47,17 @@ try { syncProvider = await zksync.getDefaultProvider(ETH_NETWORK); const keys = []; const ethPrivKey = (process.env.ETH_PRIVKEY || MM_CONFIG.ethPrivKey); - if(ethPrivKey) { keys.push(ethPrivKey); } + if(ethPrivKey && ethPrivKey != "") { keys.push(ethPrivKey); } const ethPrivKeys = (process.env.ETH_PRIVKEYS || MM_CONFIG.ethPrivKeys); if(ethPrivKeys && ethPrivKeys.length > 0) { - ethPrivKeys.forEach( key => { - keys.push(key); - }); + ethPrivKeys.forEach( key => { + if(key != "" && !keys.includes(key)) { + keys.push(key); + } + }); } - for(let i=0; i Date: Tue, 1 Feb 2022 13:49:52 +0100 Subject: [PATCH 011/160] update config.example --- config.json.EXAMPLE | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config.json.EXAMPLE b/config.json.EXAMPLE index 81260f1..48209e0 100644 --- a/config.json.EXAMPLE +++ b/config.json.EXAMPLE @@ -1,7 +1,9 @@ { "cryptowatchApiKey": "aaaaxxx", + "ethPrivKey": "", "ethPrivKeys": [ - "ccccxxxxx" + "", + "" ], "zigzagChainId": 1, "zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com", From 49fb0c3aef5ff099756ad16f166d5f277d0bfd40 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Thu, 3 Feb 2022 00:27:36 +0100 Subject: [PATCH 012/160] Update README --- README.md | 2 ++ marketmaker.js | 1 + 2 files changed, 3 insertions(+) diff --git a/README.md b/README.md index 2b22ca1..3e87c8e 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ Copy the `config.json.EXAMPLE` file to `config.json` to get started. Set your `eth_privkey` to be able to relay transactions. The ETH address with that private key should be loaded up with adequate funds for market making. +Currently zkSync needs around 5 seconds to process a single swap and generate the receipt. So there is a upper limit of 12 swaps per wallet per minute. To circumvent this, there is also the option to use the `eth_privkeys` array. Here you can add any number of private keys. Each should be loaded up with adequate funds for market making. The founds will be handled separately, therefor each additional wallet has the opportunity to process (at least) 12 more swaps per minute. + For now, you need a Cryptowatch API key to use the market maker. Once you obtain one, you can set the `cryptowatchApiKey` field in `config.json`. To run the marketmaker: diff --git a/marketmaker.js b/marketmaker.js index 9d95c04..8177a58 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -291,6 +291,7 @@ function validatePriceFeed(market_id) { const primaryPrice = PRICE_FEEDS[primaryPriceFeedId]; if (!primaryPrice) throw new Error("Primary price feed unavailable"); + // If there is no secondary price feed, the price auto-validates if (!secondaryPriceFeedId) return true; From fa6462d8adf0f248cbbfc3caf7927a6f3f8826ff Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Thu, 3 Feb 2022 12:25:43 +0100 Subject: [PATCH 013/160] refactor logging. remove excess logging of fillstatus fillstatus is basically the same as orderstatus. Don't log both --- marketmaker.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index b95c297..544bde8 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -114,7 +114,7 @@ function onWsClose () { async function handleMessage(json) { const msg = JSON.parse(json); - if (!(["lastprice", "liquidity2"]).includes(msg.op)) console.log(json.toString()); + if (!(["lastprice", "liquidity2", "fillstatus"]).includes(msg.op)) console.log(json.toString()); switch(msg.op) { case 'error': ORDER_BROADCASTING = false; @@ -456,19 +456,23 @@ function indicateLiquidity (market_id) { try { validatePriceFeed(market_id); } catch(e) { + console.error("Can not indicateLiquidity because: " + e); return false; } const marketInfo = MARKETS[market_id]; + if (!marketInfo) return false; + const mmConfig = MM_CONFIG.pairs[market_id]; const midPrice = getMidPrice(market_id); + if (!midPrice) return false; + const expires = (Date.now() / 1000 | 0) + 10; // 10s expiry const side = mmConfig.side || 'd'; const baseBalance = ACCOUNT_STATE.committed.balances[marketInfo.baseAsset.symbol] / 10**marketInfo.baseAsset.decimals; const quoteBalance = ACCOUNT_STATE.committed.balances[marketInfo.quoteAsset.symbol] / 10**marketInfo.quoteAsset.decimals; const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize); - if (!midPrice) return false; const splits = 10; const liquidity = []; From e4de274b031812df250e8a432ae400b14960bd6e Mon Sep 17 00:00:00 2001 From: TrooperCrypto <95502080+TrooperCrypto@users.noreply.github.com> Date: Thu, 3 Feb 2022 12:31:00 +0100 Subject: [PATCH 014/160] start fill loop after initiating wallets --- marketmaker.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 8177a58..ce2156e 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -35,9 +35,6 @@ console.log("ACTIVE PAIRS", activePairs); // Start price feeds cryptowatchWsSetup(); -// Initiate fill loop -setTimeout(processFillQueue, 1000); - // Connect to zksync const CHAIN_ID = parseInt(MM_CONFIG.zigzagChainId); const ETH_NETWORK = (CHAIN_ID === 1) ? "mainnet" : "rinkeby"; @@ -88,6 +85,9 @@ setInterval(updateAccountState, 30000); logBalance(); setInterval(logBalance, 3 * 60 * 60 * 1000); // 3h +// Initiate fill loop +setTimeout(processFillQueue, 1000); + // Get markets info const activePairsText = activePairs.join(','); const markets_url = `https://zigzag-markets.herokuapp.com/markets?chainid=${CHAIN_ID}&id=${activePairsText}` From d7f2877309d3ae186cdb7865434dae9d2670b06a Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Thu, 3 Feb 2022 12:58:23 +0100 Subject: [PATCH 015/160] Switch MARKETS over to ws Remove old fetch for markets. Use "marketinfo" op code to setup market info. Can be used to update infos. --- marketmaker.js | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 544bde8..d7588e9 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -14,6 +14,7 @@ const NONCES = {}; let ACCOUNT_STATE = null; let ORDER_BROADCASTING = false; const FILL_QUEUE = []; +const MARKETS = {}; // Load MM config let MM_CONFIG; @@ -66,23 +67,6 @@ try { await updateAccountState(); setInterval(updateAccountState, 30000); -// Get markets info -const activePairsText = activePairs.join(','); -const markets_url = `https://zigzag-markets.herokuapp.com/markets?chainid=${CHAIN_ID}&id=${activePairsText}` -const markets = await fetch(markets_url).then(r => r.json()); -if (markets.error) { - console.error(markets); - throw new Error(markets.error); -} -const MARKETS = {}; -for (let i in markets) { - const market = markets[i]; - MARKETS[market.id] = market; - if (market.alias) { - MARKETS[market.alias] = market; - } -} - let zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); zigzagws.on('open', onWsOpen); zigzagws.on('error', console.error); @@ -143,6 +127,10 @@ async function handleMessage(json) { } ORDER_BROADCASTING = false; break + case "marketinfo": + const market_info = msg.args[0]; + MARKETS[market_info.alias] = market_info; + break default: break } @@ -456,7 +444,7 @@ function indicateLiquidity (market_id) { try { validatePriceFeed(market_id); } catch(e) { - console.error("Can not indicateLiquidity because: " + e); + console.error("Can not indicateLiquidity ("+market_id+") because: " + e); return false; } From f3bfff47a092e159f110ec857fa55e145d1b0d98 Mon Sep 17 00:00:00 2001 From: vixidev99 Date: Sun, 6 Feb 2022 10:29:06 +0530 Subject: [PATCH 016/160] quick fix to merged code --- config.json.EXAMPLE | 1 - marketmaker.js | 1 - 2 files changed, 2 deletions(-) diff --git a/config.json.EXAMPLE b/config.json.EXAMPLE index 748ff73..9c76b35 100644 --- a/config.json.EXAMPLE +++ b/config.json.EXAMPLE @@ -1,6 +1,5 @@ { "cryptowatchApiKey": "aaaaxxx", - "ethPrivKey": "", "ethPrivKeys": [ "", "" diff --git a/marketmaker.js b/marketmaker.js index 89ef6ef..3f99820 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -98,7 +98,6 @@ if (markets.error) { console.error(markets); throw new Error(markets.error); } -const MARKETS = {}; for (let i in markets) { const market = markets[i]; MARKETS[market.id] = market; From a15e4f56f7e32456c962f63fd5d17b5851d67839 Mon Sep 17 00:00:00 2001 From: vixidev99 Date: Sun, 6 Feb 2022 11:05:44 +0530 Subject: [PATCH 017/160] parse JSON keys from env --- marketmaker.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 3f99820..1a8f666 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -46,7 +46,13 @@ try { const keys = []; const ethPrivKey = (process.env.ETH_PRIVKEY || MM_CONFIG.ethPrivKey); if(ethPrivKey && ethPrivKey != "") { keys.push(ethPrivKey); } - const ethPrivKeys = (process.env.ETH_PRIVKEYS || MM_CONFIG.ethPrivKeys); + let ethPrivKeys; + if (process.env.ETH_PRIVKEYS) { + ethPrivKeys = JSON.parse(process.env.ETH_PRIVKEYS); + } + else { + ethPrivKeys = MM_CONFIG.ethPrivKeys; + } if(ethPrivKeys && ethPrivKeys.length > 0) { ethPrivKeys.forEach( key => { if(key != "" && !keys.includes(key)) { From 7a75750ecb38043f3e83be298fcb6a2fefac7807 Mon Sep 17 00:00:00 2001 From: vixidev99 Date: Sun, 6 Feb 2022 11:24:29 +0530 Subject: [PATCH 018/160] show greatest wallet balance in liquidity --- marketmaker.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 1a8f666..ea89390 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -532,15 +532,19 @@ function indicateLiquidity (market_id) { const expires = (Date.now() / 1000 | 0) + 10; // 10s expiry const side = mmConfig.side || 'd'; - let baseBN = 0, quoteBN = 0; + let maxBaseBalance = 0, maxQuoteBalance = 0; Object.keys(WALLETS).forEach(accountId => { - const thisBase = WALLETS[accountId]['account_state'].committed.balances[marketInfo.baseAsset.symbol]; - const thisQuote = WALLETS[accountId]['account_state'].committed.balances[marketInfo.quoteAsset.symbol]; - baseBN = (baseBN < thisBase) ? thisBase : baseBN; - quoteBN = (quoteBN < thisQuote) ? thisQuote : quoteBN; + const walletBase = WALLETS[accountId]['account_state'].committed.balances[marketInfo.baseAsset.symbol]; + const walletQuote = WALLETS[accountId]['account_state'].committed.balances[marketInfo.quoteAsset.symbol]; + if (Number(walletBase) > maxBaseBalance) { + maxBaseBalance = walletBase; + } + if (Number(walletQuote) > maxQuoteBalance) { + maxQuoteBalance = walletQuote; + } }); - const baseBalance = baseBN / 10**marketInfo.baseAsset.decimals; - const quoteBalance = quoteBN / 10**marketInfo.quoteAsset.decimals; + const baseBalance = maxBaseBalance / 10**marketInfo.baseAsset.decimals; + const quoteBalance = maxQuoteBalance / 10**marketInfo.quoteAsset.decimals; const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize); From 170847a22705f54b4e40ae6252ec2f65b647d307 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 6 Feb 2022 13:52:34 +0100 Subject: [PATCH 019/160] Fix as account_state is string --- marketmaker.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index ea89390..238ac52 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -213,7 +213,8 @@ function isOrderFillable(order) { const neededBalanceBN = sellQuantity * 10**sellDecimals; const goodWallets = []; Object.keys(WALLETS).forEach(accountId => { - if (WALLETS[accountId]['account_state'].committed.balances[sellCurrency] > (neededBalanceBN * 1.05)) { + const walletBalance = WALLETS[accountId]['account_state'].committed.balances[sellCurrency]; + if (Number(walletBalance) > (neededBalanceBN * 1.05)) { goodWallets.push(accountId); } }); From 972b29adf49e410ef93344d9229bab091b80d7d2 Mon Sep 17 00:00:00 2001 From: vixidev99 Date: Mon, 7 Feb 2022 18:40:56 +0530 Subject: [PATCH 020/160] remove markets query since it's no longer needed. catch cryptowatch failure errors --- marketmaker.js | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index ea89390..43cb231 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -96,22 +96,6 @@ setInterval(logBalance, 3 * 60 * 60 * 1000); // 3h // Initiate fill loop setTimeout(processFillQueue, 1000); -// Get markets info -const activePairsText = activePairs.join(','); -const markets_url = `https://zigzag-markets.herokuapp.com/markets?chainid=${CHAIN_ID}&id=${activePairsText}` -const markets = await fetch(markets_url).then(r => r.json()); -if (markets.error) { - console.error(markets); - throw new Error(markets.error); -} -for (let i in markets) { - const market = markets[i]; - MARKETS[market.id] = market; - if (market.alias) { - MARKETS[market.alias] = market; - } -} - let zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); zigzagws.on('open', onWsOpen); zigzagws.on('error', console.error); @@ -468,11 +452,15 @@ async function cryptowatchWsSetup() { const cryptowatch_market_prices = await fetch("https://api.cryptowat.ch/markets/prices?apikey=" + cryptowatchApiKey).then(r => r.json()); for (let i in cryptowatch_market_ids) { const cryptowatch_market_id = cryptowatch_market_ids[i].split(":")[1]; - const cryptowatch_market = cryptowatch_markets.result.find(row => row.id == cryptowatch_market_id); - const exchange = cryptowatch_market.exchange; - const pair = cryptowatch_market.pair; - const key = `market:${exchange}:${pair}`; - PRICE_FEEDS[cryptowatch_market_ids[i]] = cryptowatch_market_prices.result[key]; + try { + const cryptowatch_market = cryptowatch_markets.result.find(row => row.id == cryptowatch_market_id); + const exchange = cryptowatch_market.exchange; + const pair = cryptowatch_market.pair; + const key = `market:${exchange}:${pair}`; + PRICE_FEEDS[cryptowatch_market_ids[i]] = cryptowatch_market_prices.result[key]; + } catch (e) { + console.error("Could not set price feed for cryptowatch:" + cryptowatch_market_id); + } } console.log(PRICE_FEEDS); From 62f0fc760504a8b39717ec11a4e46a5fffa9036e Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 9 Feb 2022 16:22:55 +0100 Subject: [PATCH 021/160] make setupPriceFeed modular for n providers --- marketmaker.js | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index c63d6c1..5452417 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -438,27 +438,44 @@ async function processFillQueue() { setTimeout(processFillQueue, 100); } -async function cryptowatchWsSetup() { - const cryptowatch_market_ids = []; +function setupPriceFeed() { + const cryptowatch = [], chainlink = []; for (let market in MM_CONFIG.pairs) { - const primaryPriceFeed = MM_CONFIG.pairs[market].priceFeedPrimary; - const secondaryPriceFeed = MM_CONFIG.pairs[market].priceFeedSecondary; - if (primaryPriceFeed) cryptowatch_market_ids.push(primaryPriceFeed); - if (secondaryPriceFeed) cryptowatch_market_ids.push(secondaryPriceFeed); - } + if(!MM_CONFIG.pairs[market].active) { continue; } + const primaryPriceFeed = MM_CONFIG.pairs[market].priceFeedPrimary; + const secondaryPriceFeed = MM_CONFIG.pairs[market].priceFeedSecondary; + [primaryPriceFeed, secondaryPriceFeed].forEach(priceFeed => { + if(!priceFeed) { return; } + const [provider, id] = priceFeed.split(':'); + switch(provider) { + case 'cryptowatch': + cryptowatch.push(id); + break; + case 'chainlink': + chainlink.push(id); + break; + default: + throw new Error("Price feed provider "+provider+" is not available.") + break; + } + }); + } + cryptowatchWsSetup(cryptowatch); +} +async function cryptowatchWsSetup(cryptowatch_market_ids) { // Set initial prices const cryptowatchApiKey = process.env.CRYPTOWATCH_API_KEY || MM_CONFIG.cryptowatchApiKey; const cryptowatch_markets = await fetch("https://api.cryptowat.ch/markets?apikey=" + cryptowatchApiKey).then(r => r.json()); const cryptowatch_market_prices = await fetch("https://api.cryptowat.ch/markets/prices?apikey=" + cryptowatchApiKey).then(r => r.json()); for (let i in cryptowatch_market_ids) { - const cryptowatch_market_id = cryptowatch_market_ids[i].split(":")[1]; + const cryptowatch_market_id = cryptowatch_market_ids[i]; try { const cryptowatch_market = cryptowatch_markets.result.find(row => row.id == cryptowatch_market_id); const exchange = cryptowatch_market.exchange; const pair = cryptowatch_market.pair; const key = `market:${exchange}:${pair}`; - PRICE_FEEDS[cryptowatch_market_ids[i]] = cryptowatch_market_prices.result[key]; + PRICE_FEEDS['cryptowatch:'+cryptowatch_market_ids[i]] = cryptowatch_market_prices.result[key]; } catch (e) { console.error("Could not set price feed for cryptowatch:" + cryptowatch_market_id); } @@ -471,7 +488,7 @@ async function cryptowatchWsSetup() { } } for (let i in cryptowatch_market_ids) { - const cryptowatch_market_id = cryptowatch_market_ids[i].split(":")[1]; + const cryptowatch_market_id = cryptowatch_market_ids[i]; // first get initial price info From bc600d6c8af94d858569dc23df32dd6abb98f97e Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 9 Feb 2022 17:23:31 +0100 Subject: [PATCH 022/160] rename --- marketmaker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 5452417..7e10604 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -34,7 +34,7 @@ for (let marketId in MM_CONFIG.pairs) { console.log("ACTIVE PAIRS", activePairs); // Start price feeds -cryptowatchWsSetup(); +setupPriceFeeds(); // Connect to zksync const CHAIN_ID = parseInt(MM_CONFIG.zigzagChainId); @@ -438,7 +438,7 @@ async function processFillQueue() { setTimeout(processFillQueue, 100); } -function setupPriceFeed() { +function setupPriceFeeds() { const cryptowatch = [], chainlink = []; for (let market in MM_CONFIG.pairs) { if(!MM_CONFIG.pairs[market].active) { continue; } From e55bd234186794999601b6be4d55e9838d200b08 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 9 Feb 2022 17:48:23 +0100 Subject: [PATCH 023/160] Update marketmaker.js --- marketmaker.js | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 7e10604..557f7cc 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -15,6 +15,10 @@ const WALLETS = {}; const FILL_QUEUE = []; const MARKETS = {}; +// coinlink interface ABI +const aggregatorV3InterfaceABI = [{ "inputs": [], "name": "decimals", "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "description", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint80", "name": "_roundId", "type": "uint80" }], "name": "getRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "latestRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "version", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }] + + // Load MM config let MM_CONFIG; if (process.env.MM_CONFIG) { @@ -449,10 +453,10 @@ function setupPriceFeeds() { const [provider, id] = priceFeed.split(':'); switch(provider) { case 'cryptowatch': - cryptowatch.push(id); + if(!cryptowatch.includes(id)) { cryptowatch.push(id); } break; case 'chainlink': - chainlink.push(id); + if(!chainlink.includes(id)) { chainlink.push(id); } break; default: throw new Error("Price feed provider "+provider+" is not available.") @@ -460,7 +464,8 @@ function setupPriceFeeds() { } }); } - cryptowatchWsSetup(cryptowatch); + if(chainlinkSetup.length) chainlinkSetup(chainlink); + if(cryptowatch.length) cryptowatchWsSetup(cryptowatch); } async function cryptowatchWsSetup(cryptowatch_market_ids) { @@ -480,7 +485,6 @@ async function cryptowatchWsSetup(cryptowatch_market_ids) { console.error("Could not set price feed for cryptowatch:" + cryptowatch_market_id); } } - console.log(PRICE_FEEDS); const subscriptionMsg = { "subscribe": { @@ -519,6 +523,32 @@ async function cryptowatchWsSetup(cryptowatch_market_ids) { } } +const chainlinkProviders = {}; +async function chainlinkSetup(chainlink_market_address) { + chainlink_market_address.forEach(async (address) => { + try { + const provider = new ethers.Contract(address, aggregatorV3InterfaceABI, ethersProvider); + const decimals = await provider.decimals(); + chainlinkProviders['chainlink:'+address] = [provider, decimals]; + + // get inital price + const response = await provider.latestRoundData(); + PRICE_FEEDS['chainlink:'+address] = parseFloat(response.answer) / 10**decimals; + } catch (e) { + throw new Error ("Error while setting up chainlink for "+address+", Error: "+e); + } + }); + setInterval(chainlinkUpdate, 5000); +} + +async function chainlinkUpdate() { + await Promise.all(Object.keys(chainlinkProviders).map(async (key) => { + const [provider, decimals] = chainlinkProviders[key]; + const response = await provider.latestRoundData(); + const price = parseFloat(response.answer) / 10**decimals; + })); +} + const CLIENT_ID = (Math.random() * 100000).toString(16); function indicateLiquidity (market_id) { try { From 439248f5e48f678635e648d4e0d75dca507b73be Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 9 Feb 2022 17:55:20 +0100 Subject: [PATCH 024/160] make infuraUrl available --- marketmaker.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 557f7cc..cacb792 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -44,7 +44,14 @@ setupPriceFeeds(); const CHAIN_ID = parseInt(MM_CONFIG.zigzagChainId); const ETH_NETWORK = (CHAIN_ID === 1) ? "mainnet" : "rinkeby"; let ethersProvider, syncProvider, fillOrdersInterval, indicateLiquidityInterval; -ethersProvider = ethers.getDefaultProvider(ETH_NETWORK); + +const providerUrl = (process.env.INFURA_URL || MM_CONFIG.infuraUrl); +if(providerUrl && ETH_NETWORK=="mainnet") { + ethersProvider = ethers.getDefaultProvider(providerUrl); +} else { + ethersProvider = ethers.getDefaultProvider(ETH_NETWORK); +} + try { syncProvider = await zksync.getDefaultProvider(ETH_NETWORK); const keys = []; From 97c800d06833726feb6492c30d519f545da1c602 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 9 Feb 2022 18:16:18 +0100 Subject: [PATCH 025/160] Added Chainlink to readme --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3e87c8e..b97392e 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,19 @@ Set your `eth_privkey` to be able to relay transactions. The ETH address with th Currently zkSync needs around 5 seconds to process a single swap and generate the receipt. So there is a upper limit of 12 swaps per wallet per minute. To circumvent this, there is also the option to use the `eth_privkeys` array. Here you can add any number of private keys. Each should be loaded up with adequate funds for market making. The founds will be handled separately, therefor each additional wallet has the opportunity to process (at least) 12 more swaps per minute. -For now, you need a Cryptowatch API key to use the market maker. Once you obtain one, you can set the `cryptowatchApiKey` field in `config.json`. +###### External price feed +You have to options to get a price feed for your market maker. It is not recommended to use different price feeds sources as primary and secondary feed, as those might have some difference and could stop the market maker. + +**Cryptowatch** +You need a Cryptowatch API key to use the market maker. Once you obtain one, you can set the `cryptowatchApiKey` field in `config.json`. And set it to your public key. + +You can use [this link](https://api.cryptowat.ch/markets) to download a JSON with all available market endpoints. Add those to you pair config as "cryptowatch:". + +**Chainlink** +With chainlink you have access to price oracles via blockchain. The requests are read calls to a smart contract. the public ethers provider might be to slow for a higher number of pairs or at times of high demand. Therefore it might be needed to have access to an Infura account. There you can get an endpoint for your market maker (like https://mainnet.infura.io/v3/...), You can add this with the `infuraUrl` field in `config.json` + +You can get the available market contracts [here.](https://docs.chain.link/docs/ethereum-addresses/)Add those to you pair config as "chainlink:
". + To run the marketmaker: @@ -130,4 +142,4 @@ cat config.json | tr -d ' ' | tr -d '\n' and set it to the value of the `MM_CONFIG` environment variable to override the config file. -You can also override the private key in the config file with the `ETH_PRIVKEY` environment variable, and the cryptowatch API key with the `CRYPTOWATCH_API_KEY` environment variable. +You can also override the private key in the config file with the `ETH_PRIVKEY` environment variable, and the cryptowatch API key with the `CRYPTOWATCH_API_KEY` environment variable, and the Infura provider url with `INFURA_URL` From a243bfcd075b29e9c14d2be443e1a9b58c673963 Mon Sep 17 00:00:00 2001 From: Trooper <95502080+TrooperCrypto@users.noreply.github.com> Date: Wed, 9 Feb 2022 18:18:24 +0100 Subject: [PATCH 026/160] rm line --- marketmaker.js | 1 - 1 file changed, 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index cacb792..2d7d509 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -18,7 +18,6 @@ const MARKETS = {}; // coinlink interface ABI const aggregatorV3InterfaceABI = [{ "inputs": [], "name": "decimals", "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "description", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint80", "name": "_roundId", "type": "uint80" }], "name": "getRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "latestRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "version", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }] - // Load MM config let MM_CONFIG; if (process.env.MM_CONFIG) { From c0e958f4a59b7cb20f7a59b5d6e3d40c7328b9ba Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 9 Feb 2022 19:22:59 +0100 Subject: [PATCH 027/160] up interval to 10s --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 2d7d509..4a1b730 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -544,7 +544,7 @@ async function chainlinkSetup(chainlink_market_address) { throw new Error ("Error while setting up chainlink for "+address+", Error: "+e); } }); - setInterval(chainlinkUpdate, 5000); + setInterval(chainlinkUpdate, 10000); } async function chainlinkUpdate() { From 730b10ba536bc79d6e4e9a082497d2aad0bbb33c Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 9 Feb 2022 19:25:38 +0100 Subject: [PATCH 028/160] Include price --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b97392e..02e0cd0 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ You need a Cryptowatch API key to use the market maker. Once you obtain one, you You can use [this link](https://api.cryptowat.ch/markets) to download a JSON with all available market endpoints. Add those to you pair config as "cryptowatch:". **Chainlink** -With chainlink you have access to price oracles via blockchain. The requests are read calls to a smart contract. the public ethers provider might be to slow for a higher number of pairs or at times of high demand. Therefore it might be needed to have access to an Infura account. There you can get an endpoint for your market maker (like https://mainnet.infura.io/v3/...), You can add this with the `infuraUrl` field in `config.json` +With chainlink you have access to price oracles via blockchain. The requests are read calls to a smart contract. the public ethers provider might be to slow for a higher number of pairs or at times of high demand. Therefore it might be needed to have access to an Infura account (100000 Requests/Day for free). There you can get an endpoint for your market maker (like https://mainnet.infura.io/v3/...), You can add this with the `infuraUrl` field in `config.json` You can get the available market contracts [here.](https://docs.chain.link/docs/ethereum-addresses/)Add those to you pair config as "chainlink:
". From 2a4a879e0adc4f0c07df235443855f4b7948c7d8 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 9 Feb 2022 19:53:40 +0100 Subject: [PATCH 029/160] Bug fixes - await first prices - move setupPriceFeeds after ethersProvider - move chainlinkProviders to globals - log prices once after first fetch --- marketmaker.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 4a1b730..d3eba53 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -14,6 +14,7 @@ const NONCES = {}; const WALLETS = {}; const FILL_QUEUE = []; const MARKETS = {}; +const chainlinkProviders = {}; // coinlink interface ABI const aggregatorV3InterfaceABI = [{ "inputs": [], "name": "decimals", "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "description", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint80", "name": "_roundId", "type": "uint80" }], "name": "getRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "latestRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "version", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }] @@ -36,14 +37,10 @@ for (let marketId in MM_CONFIG.pairs) { } console.log("ACTIVE PAIRS", activePairs); -// Start price feeds -setupPriceFeeds(); - // Connect to zksync const CHAIN_ID = parseInt(MM_CONFIG.zigzagChainId); const ETH_NETWORK = (CHAIN_ID === 1) ? "mainnet" : "rinkeby"; -let ethersProvider, syncProvider, fillOrdersInterval, indicateLiquidityInterval; - +let ethersProvider; const providerUrl = (process.env.INFURA_URL || MM_CONFIG.infuraUrl); if(providerUrl && ETH_NETWORK=="mainnet") { ethersProvider = ethers.getDefaultProvider(providerUrl); @@ -51,6 +48,10 @@ if(providerUrl && ETH_NETWORK=="mainnet") { ethersProvider = ethers.getDefaultProvider(ETH_NETWORK); } +// Start price feeds +await setupPriceFeeds(); + +let syncProvider, fillOrdersInterval, indicateLiquidityInterval; try { syncProvider = await zksync.getDefaultProvider(ETH_NETWORK); const keys = []; @@ -448,7 +449,7 @@ async function processFillQueue() { setTimeout(processFillQueue, 100); } -function setupPriceFeeds() { +async function setupPriceFeeds() { const cryptowatch = [], chainlink = []; for (let market in MM_CONFIG.pairs) { if(!MM_CONFIG.pairs[market].active) { continue; } @@ -470,8 +471,8 @@ function setupPriceFeeds() { } }); } - if(chainlinkSetup.length) chainlinkSetup(chainlink); - if(cryptowatch.length) cryptowatchWsSetup(cryptowatch); + if(chainlinkSetup.length) await chainlinkSetup(chainlink); + if(cryptowatch.length) await cryptowatchWsSetup(cryptowatch); } async function cryptowatchWsSetup(cryptowatch_market_ids) { From df16f5665a304b7497de8d03b71cdcc15b66b2cc Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 9 Feb 2022 19:53:56 +0100 Subject: [PATCH 030/160] Update marketmaker.js --- marketmaker.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index d3eba53..56a2605 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -473,6 +473,8 @@ async function setupPriceFeeds() { } if(chainlinkSetup.length) await chainlinkSetup(chainlink); if(cryptowatch.length) await cryptowatchWsSetup(cryptowatch); + + console.log(PRICE_FEEDS); } async function cryptowatchWsSetup(cryptowatch_market_ids) { @@ -530,7 +532,6 @@ async function cryptowatchWsSetup(cryptowatch_market_ids) { } } -const chainlinkProviders = {}; async function chainlinkSetup(chainlink_market_address) { chainlink_market_address.forEach(async (address) => { try { From 6220c8d1365a33b9c97e530fd2f6c0b9602c9756 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 9 Feb 2022 20:44:27 +0100 Subject: [PATCH 031/160] Update marketmaker.js --- marketmaker.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 56a2605..896d58a 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -140,7 +140,7 @@ function onWsClose () { async function handleMessage(json) { const msg = JSON.parse(json); - if (!(["lastprice", "liquidity2", "fillstatus"]).includes(msg.op)) console.log(json.toString()); + if (!(["lastprice", "liquidity2", "fillstatus", "marketinfo"]).includes(msg.op)) console.log(json.toString()); switch(msg.op) { case 'error': Object.keys(WALLETS).forEach(accountId => { @@ -181,6 +181,17 @@ async function handleMessage(json) { case "marketinfo": const market_info = msg.args[0]; MARKETS[market_info.alias] = market_info; + let oldBaseFee, oldQuoteFee; + try { + oldBaseFee = MARKETS[market_info.alias].baseFee; + oldQuoteFee = MARKETS[market_info.alias].quoteFee; + } catch (e) { + console.log(market_info) + break + } + const newBaseFee = market_info.baseFee; + const newQuoteFee = market_info.quoteFee; + console.log(`marketinfo - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); break default: break From 95e789163c44e9cc4806847111c6b688b1014534 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Thu, 10 Feb 2022 22:11:21 +0100 Subject: [PATCH 032/160] fix/ add marekt_id in log This should log the maket_id. Otherwise its pointless --- marketmaker.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 896d58a..e9ad75b 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -180,7 +180,8 @@ async function handleMessage(json) { break case "marketinfo": const market_info = msg.args[0]; - MARKETS[market_info.alias] = market_info; + const market_id = market_info.alias; + MARKETS[market_id] = market_info; let oldBaseFee, oldQuoteFee; try { oldBaseFee = MARKETS[market_info.alias].baseFee; @@ -191,7 +192,7 @@ async function handleMessage(json) { } const newBaseFee = market_info.baseFee; const newQuoteFee = market_info.quoteFee; - console.log(`marketinfo - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); + console.log(`marketinfo ${market_id} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); break default: break From dee00ae6035933b61611c88800634aee3ec1e4d7 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Thu, 10 Feb 2022 23:03:15 +0100 Subject: [PATCH 033/160] Add examples to README and add warning --- README.md | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 02e0cd0..8d106d2 100644 --- a/README.md +++ b/README.md @@ -25,17 +25,47 @@ Set your `eth_privkey` to be able to relay transactions. The ETH address with th Currently zkSync needs around 5 seconds to process a single swap and generate the receipt. So there is a upper limit of 12 swaps per wallet per minute. To circumvent this, there is also the option to use the `eth_privkeys` array. Here you can add any number of private keys. Each should be loaded up with adequate funds for market making. The founds will be handled separately, therefor each additional wallet has the opportunity to process (at least) 12 more swaps per minute. ###### External price feed -You have to options to get a price feed for your market maker. It is not recommended to use different price feeds sources as primary and secondary feed, as those might have some difference and could stop the market maker. +You have two options to get a price feed for your market maker. It is not recommended to use different price feeds sources as primary and secondary feed, as those might have some difference and could stop the market maker. + +**Warning:** Make sure your price feed is close to the price you see on zigzag. **Otherwise, your mm can lose money!** **Cryptowatch** You need a Cryptowatch API key to use the market maker. Once you obtain one, you can set the `cryptowatchApiKey` field in `config.json`. And set it to your public key. You can use [this link](https://api.cryptowat.ch/markets) to download a JSON with all available market endpoints. Add those to you pair config as "cryptowatch:". -**Chainlink** -With chainlink you have access to price oracles via blockchain. The requests are read calls to a smart contract. the public ethers provider might be to slow for a higher number of pairs or at times of high demand. Therefore it might be needed to have access to an Infura account (100000 Requests/Day for free). There you can get an endpoint for your market maker (like https://mainnet.infura.io/v3/...), You can add this with the `infuraUrl` field in `config.json` +Example: +``` +"ETH-USDC": { + "mode": "pricefeed", + "side": "d", + "initPrice": null, + "priceFeedPrimary": "cryptowatch:6631", + "priceFeedSecondary": "cryptowatch:588", + .... +} +``` -You can get the available market contracts [here.](https://docs.chain.link/docs/ethereum-addresses/)Add those to you pair config as "chainlink:
". +**Chainlink** +With chainlink you have access to price oracles via blockchain. The requests are read calls to a smart contract. The public ethers provider might be too slow for a higher number of pairs or at times of high demand. Therefore, it might be needed to have access to an Infura account (100000 Requests/Day for free). There you can get an endpoint for your market maker (like https://mainnet.infura.io/v3/...), You can add this with the `infuraUrl` field in `config.json`, like this: +``` +.... +"infuraUrl": "https://mainnet.infura.io/v3/xxxxxxxx", +"zigzagChainId": 1, +"zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com", +.... +``` +You can get the available market contracts [here.](https://docs.chain.link/docs/ethereum-addresses/)Add those to you pair config as "chainlink:
", like this: +``` +"ETH-USDC": { + "mode": "pricefeed", + "side": "d", + "initPrice": null, + "priceFeedPrimary": "chainlink:0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", + "priceFeedSecondary": null, + .... +} +``` To run the marketmaker: From 1a842292c4820dabdc4fc07238730829eae0025e Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sat, 12 Feb 2022 00:51:24 +0100 Subject: [PATCH 034/160] fix/cryptowatch restart need to give parameter to cryptowatchWsSetup --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index e9ad75b..d650c2b 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -540,7 +540,7 @@ async function cryptowatchWsSetup(cryptowatch_market_ids) { PRICE_FEEDS[market_id] = price; }; function onclose () { - setTimeout(cryptowatchWsSetup, 5000); + setTimeout(cryptowatchWsSetup, 5000, cryptowatch_market_ids); } } From 6ffd03a9152a0ca1b9389b159455275ab2264601 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sat, 12 Feb 2022 18:46:18 +0100 Subject: [PATCH 035/160] feat/ws on error --- marketmaker.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index d650c2b..24d073f 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -134,7 +134,10 @@ function onWsClose () { clearInterval(indicateLiquidityInterval) zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); zigzagws.on('open', onWsOpen); - zigzagws.on('error', onWsClose); + zigzagws.on('error', (error) => { + console.error(error); + onWsClose(); + }); }, 5000); } @@ -527,6 +530,10 @@ async function cryptowatchWsSetup(cryptowatch_market_ids) { cryptowatch_ws.on('open', onopen); cryptowatch_ws.on('message', onmessage); cryptowatch_ws.on('close', onclose); + cryptowatch_ws.on('error', (error) => { + console.error(error); + onclose(); + }); function onopen() { cryptowatch_ws.send(JSON.stringify(subscriptionMsg)); } From 44bb3f2fff6e45042f8cc863835b6ef7c431634b Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sat, 12 Feb 2022 20:39:55 +0100 Subject: [PATCH 036/160] Add restart here too --- marketmaker.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 24d073f..838b414 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -109,7 +109,10 @@ setTimeout(processFillQueue, 1000); let zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); zigzagws.on('open', onWsOpen); -zigzagws.on('error', console.error); +zigzagws.on('error', (error) => { + console.error(error); + onWsClose(); +}); function onWsOpen() { zigzagws.on('message', handleMessage); From 80f93dc029198fab35b60af9b8c91bec65d58f41 Mon Sep 17 00:00:00 2001 From: vixidev99 Date: Sun, 13 Feb 2022 11:04:34 +0530 Subject: [PATCH 037/160] dont allow bad liquidity sends to crash the MM --- marketmaker.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index d650c2b..63914da 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -617,7 +617,12 @@ function indicateLiquidity (market_id) { } } const msg = { op: "indicateliq2", args: [CHAIN_ID, market_id, liquidity, CLIENT_ID] }; - zigzagws.send(JSON.stringify(msg)); + try { + zigzagws.send(JSON.stringify(msg)); + } catch (e) { + console.error("Could not send liquidity"); + console.error(e); + } } function getMidPrice (market_id) { From 8e60a5d6479b5d616df97dbd38252601271e55c3 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 13 Feb 2022 19:28:33 +0100 Subject: [PATCH 038/160] fix --- marketmaker.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 838b414..8dc4f42 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -109,10 +109,7 @@ setTimeout(processFillQueue, 1000); let zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); zigzagws.on('open', onWsOpen); -zigzagws.on('error', (error) => { - console.error(error); - onWsClose(); -}); +zigzagws.on('error', console.error); function onWsOpen() { zigzagws.on('message', handleMessage); @@ -137,10 +134,7 @@ function onWsClose () { clearInterval(indicateLiquidityInterval) zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); zigzagws.on('open', onWsOpen); - zigzagws.on('error', (error) => { - console.error(error); - onWsClose(); - }); + zigzagws.on('error', console.error); }, 5000); } @@ -533,10 +527,8 @@ async function cryptowatchWsSetup(cryptowatch_market_ids) { cryptowatch_ws.on('open', onopen); cryptowatch_ws.on('message', onmessage); cryptowatch_ws.on('close', onclose); - cryptowatch_ws.on('error', (error) => { - console.error(error); - onclose(); - }); + cryptowatch_ws.on('error', console.error); + function onopen() { cryptowatch_ws.send(JSON.stringify(subscriptionMsg)); } From f831177e02ad0c1522ce19dc4334720331bcddc8 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 13 Feb 2022 19:52:15 +0100 Subject: [PATCH 039/160] moved close outside of onOpen --- marketmaker.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 8dc4f42..af6ec89 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -109,11 +109,11 @@ setTimeout(processFillQueue, 1000); let zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); zigzagws.on('open', onWsOpen); +zigzagws.on('close', onWsClose); zigzagws.on('error', console.error); function onWsOpen() { zigzagws.on('message', handleMessage); - zigzagws.on('close', onWsClose); fillOrdersInterval = setInterval(fillOpenOrders, 5000); for (let market in MM_CONFIG.pairs) { if (MM_CONFIG.pairs[market].active) { @@ -134,6 +134,7 @@ function onWsClose () { clearInterval(indicateLiquidityInterval) zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); zigzagws.on('open', onWsOpen); + zigzagws.on('close', onWsClose); zigzagws.on('error', console.error); }, 5000); } From 372ff83dc9a937e47494e969d5acac245c1a76b6 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Mon, 14 Feb 2022 12:51:50 +0100 Subject: [PATCH 040/160] remember order --- marketmaker.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/marketmaker.js b/marketmaker.js index 63914da..4dccfbe 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -15,6 +15,7 @@ const WALLETS = {}; const FILL_QUEUE = []; const MARKETS = {}; const chainlinkProviders = {}; +const PAST_ORDER_LIST = {}; // coinlink interface ABI const aggregatorV3InterfaceABI = [{ "inputs": [], "name": "decimals", "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "description", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint80", "name": "_roundId", "type": "uint80" }], "name": "getRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "latestRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "version", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }] @@ -373,6 +374,7 @@ async function sendfillrequest(orderreceipt, accountId) { // Set wallet flag WALLETS[accountId]['ORDER_BROADCASTING'] = true; + rememberOrder(chainId, orderId, market_id, quote.quotePrice, fillOrder); const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] }; zigzagws.send(JSON.stringify(resp)); } @@ -638,6 +640,25 @@ function getMidPrice (market_id) { return midPrice; } +function rememberOrder(chainId, orderId, market, price, fillOrder) { + const timestamp = Date.now() / 1000; + for (const [key, value] of Object.entries(PAST_ORDER_LIST)) { + if (value['expiry'] < timestamp) { + delete PAST_ORDER_LIST[key]; + } + } + + const expiry = timestamp + 900; + PAST_ORDER_LIST[orderId] = { + 'chainId': chainId, + 'orderId': orderId, + 'market': market, + 'price': price, + 'fillOrder': fillOrder, + 'expiry':expiry + }; +} + async function updateAccountState() { try { Object.keys(WALLETS).forEach(accountId => { From ed077ab3e3630475ea3b67f23af97e0b52a27668 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Mon, 14 Feb 2022 13:02:23 +0100 Subject: [PATCH 041/160] add pair timeout --- marketmaker.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 4dccfbe..b8c15d2 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -411,8 +411,20 @@ async function broadcastfill(chainid, orderid, swapOffer, fillOrder, wallet) { success = false; } console.timeEnd('receipt' + randint); - console.log("Swap broadcast result", {swap, receipt}); + + if(success) { + const order = PAST_ORDER_LIST[orderid]; + if(order) { + const market_id = order.market; + const mmConfig = MM_CONFIG.pairs[market_id]; + if(mmConfig && mmConfig.delayAfterFill) { + mmConfig.active = false; + setTimeout(activatePair, mmConfig.delayAfterFill, market_id); + } + } + } + const newstatus = success ? 'f' : 'r'; const error = success ? null : swap.error.toString(); const ordercommitmsg = {op:"orderstatusupdate", args:[[[chainid,orderid,newstatus,txhash,error]]]} @@ -640,6 +652,12 @@ function getMidPrice (market_id) { return midPrice; } +function activatePair(market_id) { + const mmConfig = MM_CONFIG.pairs[market_id]; + if(!mmConfig) return; + mmConfig.active = true; +} + function rememberOrder(chainId, orderId, market, price, fillOrder) { const timestamp = Date.now() / 1000; for (const [key, value] of Object.entries(PAST_ORDER_LIST)) { From 2edeca46d0236bc07a5e0a85e4225f8524be7acb Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Mon, 14 Feb 2022 13:05:51 +0100 Subject: [PATCH 042/160] Add delayAfterFill to readme --- README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d106d2..1bde413 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,28 @@ Orders coming in below the `minSpread` from the price feed will not be filled. T A market can be set inactive by flipping the active switch to `false`. -## Pair Setting Examples +## Pair Options + +###### delayAfterFill +The market maker will stop market making on the pair, after successfully filling an order. This can be used to wait out bigger price moves. +Example: +``` +"ETH-USDC": { + "mode": "pricefeed", + "side": "b", + "priceFeedPrimary": "cryptowatch:6631", + "priceFeedSecondary": "cryptowatch:588", + "slippageRate": 1e-5, + "maxSize": 100, + "minSize": 0.0003, + "minSpread": 0.0005, + "active": true, + "delayAfterFill": 60 +} +``` + + +## Pair Setting Examples Stable-Stable constant price: From 713a3967ddd7dbad71f058362fbab6a46467de5f Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Mon, 14 Feb 2022 13:30:27 +0100 Subject: [PATCH 043/160] refactor indicateLiquidity - indicateLiquidity without parameter - for loop over all pairs - continue if pair is not active --- marketmaker.js | 98 ++++++++++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index b8c15d2..ad0a3e2 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -116,9 +116,9 @@ function onWsOpen() { zigzagws.on('message', handleMessage); zigzagws.on('close', onWsClose); fillOrdersInterval = setInterval(fillOpenOrders, 5000); + indicateLiquidityInterval = setInterval(indicateLiquidity, 5000); for (let market in MM_CONFIG.pairs) { if (MM_CONFIG.pairs[market].active) { - indicateLiquidityInterval = setInterval(() => indicateLiquidity(market), 5000); const msg = {op:"subscribemarket", args:[CHAIN_ID, market]}; zigzagws.send(JSON.stringify(msg)); } @@ -584,59 +584,63 @@ async function chainlinkUpdate() { } const CLIENT_ID = (Math.random() * 100000).toString(16); -function indicateLiquidity (market_id) { - try { - validatePriceFeed(market_id); - } catch(e) { - console.error("Can not indicateLiquidity ("+market_id+") because: " + e); - return false; - } +function indicateLiquidity () { + for(const pair in MM_CONFIG.pairs) { + const mmConfig = MM_CONFIG.pairs[pair]; + if(!mmConfig || !mmConfig.active) continue; - const marketInfo = MARKETS[market_id]; - if (!marketInfo) return false; + try { + validatePriceFeed(market_id); + } catch(e) { + console.error("Can not indicateLiquidity ("+market_id+") because: " + e); + return continue; + } - const mmConfig = MM_CONFIG.pairs[market_id]; - const midPrice = getMidPrice(market_id); - if (!midPrice) return false; + const marketInfo = MARKETS[market_id]; + if (!marketInfo) return continue; - const expires = (Date.now() / 1000 | 0) + 10; // 10s expiry - const side = mmConfig.side || 'd'; + const midPrice = getMidPrice(market_id); + if (!midPrice) return continue; - let maxBaseBalance = 0, maxQuoteBalance = 0; - Object.keys(WALLETS).forEach(accountId => { - const walletBase = WALLETS[accountId]['account_state'].committed.balances[marketInfo.baseAsset.symbol]; - const walletQuote = WALLETS[accountId]['account_state'].committed.balances[marketInfo.quoteAsset.symbol]; - if (Number(walletBase) > maxBaseBalance) { - maxBaseBalance = walletBase; - } - if (Number(walletQuote) > maxQuoteBalance) { - maxQuoteBalance = walletQuote; - } - }); - const baseBalance = maxBaseBalance / 10**marketInfo.baseAsset.decimals; - const quoteBalance = maxQuoteBalance / 10**marketInfo.quoteAsset.decimals; - const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); - const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize); - - const splits = 10; - const liquidity = []; - for (let i=1; i <= splits; i++) { - const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i/splits)); - const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i/splits)); - if ((['b','d']).includes(side)) { - liquidity.push(["b", buyPrice, maxBuySize / splits, expires]); + const expires = (Date.now() / 1000 | 0) + 10; // 10s expiry + const side = mmConfig.side || 'd'; + + let maxBaseBalance = 0, maxQuoteBalance = 0; + Object.keys(WALLETS).forEach(accountId => { + const walletBase = WALLETS[accountId]['account_state'].committed.balances[marketInfo.baseAsset.symbol]; + const walletQuote = WALLETS[accountId]['account_state'].committed.balances[marketInfo.quoteAsset.symbol]; + if (Number(walletBase) > maxBaseBalance) { + maxBaseBalance = walletBase; + } + if (Number(walletQuote) > maxQuoteBalance) { + maxQuoteBalance = walletQuote; + } + }); + const baseBalance = maxBaseBalance / 10**marketInfo.baseAsset.decimals; + const quoteBalance = maxQuoteBalance / 10**marketInfo.quoteAsset.decimals; + const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); + const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize); + + const splits = 10; + const liquidity = []; + for (let i=1; i <= splits; i++) { + const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i/splits)); + const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i/splits)); + if ((['b','d']).includes(side)) { + liquidity.push(["b", buyPrice, maxBuySize / splits, expires]); + } + if ((['s','d']).includes(side)) { + liquidity.push(["s", sellPrice, maxSellSize / splits, expires]); + } } - if ((['s','d']).includes(side)) { - liquidity.push(["s", sellPrice, maxSellSize / splits, expires]); + const msg = { op: "indicateliq2", args: [CHAIN_ID, market_id, liquidity, CLIENT_ID] }; + try { + zigzagws.send(JSON.stringify(msg)); + } catch (e) { + console.error("Could not send liquidity"); + console.error(e); } } - const msg = { op: "indicateliq2", args: [CHAIN_ID, market_id, liquidity, CLIENT_ID] }; - try { - zigzagws.send(JSON.stringify(msg)); - } catch (e) { - console.error("Could not send liquidity"); - console.error(e); - } } function getMidPrice (market_id) { From 19652218fd488c03e0a0b3032244ff78231dd557 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Mon, 14 Feb 2022 14:06:56 +0100 Subject: [PATCH 044/160] fix --- marketmaker.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index ad0a3e2..4ab82cc 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -585,22 +585,22 @@ async function chainlinkUpdate() { const CLIENT_ID = (Math.random() * 100000).toString(16); function indicateLiquidity () { - for(const pair in MM_CONFIG.pairs) { - const mmConfig = MM_CONFIG.pairs[pair]; + for(const market_id in MM_CONFIG.pairs) { + const mmConfig = MM_CONFIG.pairs[market_id]; if(!mmConfig || !mmConfig.active) continue; try { validatePriceFeed(market_id); } catch(e) { console.error("Can not indicateLiquidity ("+market_id+") because: " + e); - return continue; + continue; } const marketInfo = MARKETS[market_id]; - if (!marketInfo) return continue; + if (!marketInfo) continue; const midPrice = getMidPrice(market_id); - if (!midPrice) return continue; + if (!midPrice) continue; const expires = (Date.now() / 1000 | 0) + 10; // 10s expiry const side = mmConfig.side || 'd'; From f07f72bd1414f8806aadc927abc7e95d266088bb Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 15 Feb 2022 12:40:32 +0100 Subject: [PATCH 045/160] Move ABI in file --- chainlinkV3InterfaceABI.abi | 121 ++++++++++++++++++++++++++++++++++++ marketmaker.js | 2 +- 2 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 chainlinkV3InterfaceABI.abi diff --git a/chainlinkV3InterfaceABI.abi b/chainlinkV3InterfaceABI.abi new file mode 100644 index 0000000..4c5f462 --- /dev/null +++ b/chainlinkV3InterfaceABI.abi @@ -0,0 +1,121 @@ +[ + { + "inputs":[ + + ], + "name":"decimals", + "outputs":[ + { + "internalType":"uint8", + "name":"", + "type":"uint8" + } + ], + "stateMutability":"view", + "type":"function" + }, + { + "inputs":[ + + ], + "name":"description", + "outputs":[ + { + "internalType":"string", + "name":"", + "type":"string" + } + ], + "stateMutability":"view", + "type":"function" + }, + { + "inputs":[ + { + "internalType":"uint80", + "name":"_roundId", + "type":"uint80" + } + ], + "name":"getRoundData", + "outputs":[ + { + "internalType":"uint80", + "name":"roundId", + "type":"uint80" + }, + { + "internalType":"int256", + "name":"answer", + "type":"int256" + }, + { + "internalType":"uint256", + "name":"startedAt", + "type":"uint256" + }, + { + "internalType":"uint256", + "name":"updatedAt", + "type":"uint256" + }, + { + "internalType":"uint80", + "name":"answeredInRound", + "type":"uint80" + } + ], + "stateMutability":"view", + "type":"function" + }, + { + "inputs":[ + + ], + "name":"latestRoundData", + "outputs":[ + { + "internalType":"uint80", + "name":"roundId", + "type":"uint80" + }, + { + "internalType":"int256", + "name":"answer", + "type":"int256" + }, + { + "internalType":"uint256", + "name":"startedAt", + "type":"uint256" + }, + { + "internalType":"uint256", + "name":"updatedAt", + "type":"uint256" + }, + { + "internalType":"uint80", + "name":"answeredInRound", + "type":"uint80" + } + ], + "stateMutability":"view", + "type":"function" + }, + { + "inputs":[ + + ], + "name":"version", + "outputs":[ + { + "internalType":"uint256", + "name":"", + "type":"uint256" + } + ], + "stateMutability":"view", + "type":"function" + } +] diff --git a/marketmaker.js b/marketmaker.js index 63914da..80d6390 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -17,7 +17,7 @@ const MARKETS = {}; const chainlinkProviders = {}; // coinlink interface ABI -const aggregatorV3InterfaceABI = [{ "inputs": [], "name": "decimals", "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "description", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint80", "name": "_roundId", "type": "uint80" }], "name": "getRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "latestRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "version", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }] +const aggregatorV3InterfaceABI = JSON.parse(fs.readFileSync('chainlinkV3InterfaceABI.abi')); // Load MM config let MM_CONFIG; From d96aaebb98ddd4271098d77d8c66188e50d9f499 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 15 Feb 2022 12:49:21 +0100 Subject: [PATCH 046/160] whitespaces --- README.md | 22 +++++++++++----------- marketmaker.js | 6 +++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8d106d2..2f527c5 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ This is the reference market maker for Zigzag zksync markets. It works on both Rinkeby and Mainnet. -This market maker uses existing price feeds to set bids and asks for a market. For now, in order to provide liquidity for a market, there must be an existing market with **greater** liquidity listed on Cryptowatch, via either Uniswap or some other centralized exchange. It is crucial that the oracle market have more liquidity than the Zigzag one so that you are not prone to oracle attacks. +This market maker uses existing price feeds to set bids and asks for a market. For now, in order to provide liquidity for a market, there must be an existing market with **greater** liquidity listed on Cryptowatch, via either Uniswap or some other centralized exchange. It is crucial that the oracle market have more liquidity than the Zigzag one so that you are not prone to oracle attacks. -Soon we will add the ability to run standalone markets and this will not be an issue. +Soon we will add the ability to run standalone markets and this will not be an issue. ## Requirements @@ -18,7 +18,7 @@ Soon we will add the ability to run standalone markets and this will not be an i ## Setup -Copy the `config.json.EXAMPLE` file to `config.json` to get started. +Copy the `config.json.EXAMPLE` file to `config.json` to get started. Set your `eth_privkey` to be able to relay transactions. The ETH address with that private key should be loaded up with adequate funds for market making. @@ -93,27 +93,27 @@ You can add, remove, and configure pair settings in the `pairs` section. A pair } ``` -There are 2 modes available with a 3rd on the way. +There are 2 modes available with a 3rd on the way. -* `pricefeed`: Follows an external price oracle and updates indicated bids and asks based on that. +* `pricefeed`: Follows an external price oracle and updates indicated bids and asks based on that. * `constant`: Sets an `initPrice` and market makes around that price. Can be combined with single-sided liquidity to simulate limit orders. -* `independent`: Under development. The price is set independent of a price feed. +* `independent`: Under development. The price is set independent of a price feed. For all modes the `slippageRate`, `maxSize`, `minSize`, `minSpread`, and `active` settings are mandatory. -For `pricefeed` mode, the `priceFeedPrimary` is mandatory. +For `pricefeed` mode, the `priceFeedPrimary` is mandatory. -For `independent` and `constant` mode, the `initPrice` is mandatory. +For `independent` and `constant` mode, the `initPrice` is mandatory. The `side` setting can be toggled for single-sided liquidity. By default, the side setting is set to `d`, which stands for double-sided liquidity. To toggle single-sided liquidity, the value can be set to `b` or `s` for buy-side only or sell-side only. -The primary price feed is the price feed used to determine the bids and asks of the market maker. The secondary price feed is used to validate the first price feed and make sure the market isn't returning bad data. If the primary and secondary price feeds vary by more than 1%, the market maker will not fill orders. +The primary price feed is the price feed used to determine the bids and asks of the market maker. The secondary price feed is used to validate the first price feed and make sure the market isn't returning bad data. If the primary and secondary price feeds vary by more than 1%, the market maker will not fill orders. -The slippage rate is the rate at which the spread increases as the base unit increases. For the example above, the spread goes up by 1e-5 for every 1 ETH in size added to an order. That's the equivalent of 0.1 bps / ETH in slippage. +The slippage rate is the rate at which the spread increases as the base unit increases. For the example above, the spread goes up by 1e-5 for every 1 ETH in size added to an order. That's the equivalent of 0.1 bps / ETH in slippage. Orders coming in below the `minSpread` from the price feed will not be filled. The spread is calculated as a decimal value. 0.01 is 1%, and 0.0002 is 2 basis points (bps). -A market can be set inactive by flipping the active switch to `false`. +A market can be set inactive by flipping the active switch to `false`. ## Pair Setting Examples diff --git a/marketmaker.js b/marketmaker.js index 80d6390..dacca3a 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -123,7 +123,7 @@ function onWsOpen() { } } } - + function onWsClose () { console.log("Websocket closed. Restarting"); Object.keys(WALLETS).forEach(accountId => { @@ -234,7 +234,7 @@ function isOrderFillable(order) { if (mmSide !== 'd' && mmSide == side) { return { fillable: false, reason: "badside" }; } - + if (baseQuantity < mmConfig.minSize) { return { fillable: false, reason: "badsize" }; } @@ -309,7 +309,7 @@ function validatePriceFeed(market_id) { // Check if primary price exists const primaryPrice = PRICE_FEEDS[primaryPriceFeedId]; if (!primaryPrice) throw new Error("Primary price feed unavailable"); - + // If there is no secondary price feed, the price auto-validates if (!secondaryPriceFeedId) return true; From f84799367190a75d08868a1f8396d4c34163b527 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 15 Feb 2022 16:40:25 +0100 Subject: [PATCH 047/160] fix and logging --- marketmaker.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index f5d1b33..a8a041f 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -421,7 +421,8 @@ async function broadcastfill(chainid, orderid, swapOffer, fillOrder, wallet) { const mmConfig = MM_CONFIG.pairs[market_id]; if(mmConfig && mmConfig.delayAfterFill) { mmConfig.active = false; - setTimeout(activatePair, mmConfig.delayAfterFill, market_id); + setTimeout(activatePair, mmConfig.delayAfterFill * 1000, market_id); + console.log(`Set ${market_id} passive for ${mmConfig.delayAfterFill} seconds.`) } } } @@ -663,6 +664,7 @@ function activatePair(market_id) { const mmConfig = MM_CONFIG.pairs[market_id]; if(!mmConfig) return; mmConfig.active = true; + console.log(`Set ${market_id} active.`) } function rememberOrder(chainId, orderId, market, price, fillOrder) { From 76137d67b0d30e4699abe82b13f983053a4782ce Mon Sep 17 00:00:00 2001 From: Trooper <95502080+TrooperCrypto@users.noreply.github.com> Date: Tue, 15 Feb 2022 17:13:43 +0100 Subject: [PATCH 048/160] Refactor/naming (#18) * market_id -> marketId * chainlinkProviders -> CHAINLINK_PROVIDERS * move indicateLiquidityInterval to where it is used * move fillOrdersInterval to where it is used * refactor case "marketinfo" * chainid -> chainId * sendfillrequest -> sendFillRequest * genquote -> genQuote * one_min_expiry -> oneMinExpiry * orderid -> orderId * randint -> randInt * txhash -> txHash * newstatus -> newStatus * ordercommitmsg -> orderCommitMsg * cryptowatch_market -> cryptowatchMarket * chainlink_market_address -> chainlinkMarketAddress * OrderId is key remove in object (back from when PAST_ORDER_LIST was an array) * tabs --- marketmaker.js | 420 ++++++++++++++++++++++++------------------------- 1 file changed, 209 insertions(+), 211 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index a8a041f..bfd98d3 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -14,11 +14,9 @@ const NONCES = {}; const WALLETS = {}; const FILL_QUEUE = []; const MARKETS = {}; -const chainlinkProviders = {}; +const CHAINLINK_PROVIDERS = {}; const PAST_ORDER_LIST = {}; -// coinlink interface ABI -const aggregatorV3InterfaceABI = JSON.parse(fs.readFileSync('chainlinkV3InterfaceABI.abi')); // Load MM config let MM_CONFIG; @@ -52,7 +50,7 @@ if(providerUrl && ETH_NETWORK=="mainnet") { // Start price feeds await setupPriceFeeds(); -let syncProvider, fillOrdersInterval, indicateLiquidityInterval; +let syncProvider; try { syncProvider = await zksync.getDefaultProvider(ETH_NETWORK); const keys = []; @@ -73,24 +71,24 @@ try { }); } for(let i=0; i { - const orderid = order[1]; + const orderId = order[1]; const fillable = isOrderFillable(order); console.log(fillable); if (fillable.fillable) { FILL_QUEUE.push({ order: order, wallets: fillable.wallets}); } else if (fillable.reason === "badprice") { - OPEN_ORDERS[orderid] = order; + OPEN_ORDERS[orderId] = order; } }); break case "userordermatch": - const chainid = msg.args[0]; - const orderid = msg.args[1]; + const chainId = msg.args[0]; + const orderId = msg.args[1]; const fillOrder = msg.args[3]; const wallet = WALLETS[fillOrder.accountId]; if(!wallet) { @@ -173,7 +171,7 @@ async function handleMessage(json) { break } else { try { - await broadcastfill(chainid, orderid, msg.args[2], fillOrder, wallet); + await broadcastfill(chainId, orderId, msg.args[2], fillOrder, wallet); } catch (e) { console.error(e); } @@ -181,20 +179,20 @@ async function handleMessage(json) { } break case "marketinfo": - const market_info = msg.args[0]; - const market_id = market_info.alias; - MARKETS[market_id] = market_info; - let oldBaseFee, oldQuoteFee; + const marketInfo = msg.args[0]; + const marketId = marketInfo.alias; + if(!marketId) break + let oldBaseFee = "N/A", oldQuoteFee = "N/A"; try { - oldBaseFee = MARKETS[market_info.alias].baseFee; - oldQuoteFee = MARKETS[market_info.alias].quoteFee; + oldBaseFee = MARKETS[marketId].baseFee; + oldQuoteFee = MARKETS[marketId].quoteFee; } catch (e) { - console.log(market_info) - break + // pass, no old marketInfo } - const newBaseFee = market_info.baseFee; - const newQuoteFee = market_info.quoteFee; - console.log(`marketinfo ${market_id} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); + MARKETS[marketId] = marketInfo; + const newBaseFee = MARKETS[marketId].baseFee; + const newQuoteFee = MARKETS[marketId].quoteFee; + console.log(`marketinfo ${} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); break default: break @@ -202,12 +200,12 @@ async function handleMessage(json) { } function isOrderFillable(order) { - const chainid = order[0]; - const market_id = order[2]; - const market = MARKETS[market_id]; - const mmConfig = MM_CONFIG.pairs[market_id]; + const chainId = order[0]; + const marketId = order[2]; + const market = MARKETS[marketId]; + const mmConfig = MM_CONFIG.pairs[marketId]; const mmSide = mmConfig.side || 'd'; - if (chainid != CHAIN_ID) return { fillable: false, reason: "badchain" } + if (chainId != CHAIN_ID) return { fillable: false, reason: "badchain" } if (!market) return { fillable: false, reason: "badmarket" } if (!mmConfig.active) return { fillable: false, reason: "inactivemarket" } @@ -222,10 +220,10 @@ function isOrderFillable(order) { const neededBalanceBN = sellQuantity * 10**sellDecimals; const goodWallets = []; Object.keys(WALLETS).forEach(accountId => { - const walletBalance = WALLETS[accountId]['account_state'].committed.balances[sellCurrency]; - if (Number(walletBalance) > (neededBalanceBN * 1.05)) { - goodWallets.push(accountId); - } + const walletBalance = WALLETS[accountId]['account_state'].committed.balances[sellCurrency]; + if (Number(walletBalance) > (neededBalanceBN * 1.05)) { + goodWallets.push(accountId); + } }); const now = Date.now() / 1000 | 0; @@ -250,7 +248,7 @@ function isOrderFillable(order) { let quote; try { - quote = genquote(chainid, market_id, side, baseQuantity); + quote = genQuote(chainId, marketId, side, baseQuantity); } catch (e) { return { fillable: false, reason: e.message } } @@ -265,21 +263,21 @@ function isOrderFillable(order) { return { fillable: true, reason: null, wallets: goodWallets}; } -function genquote(chainid, market_id, side, baseQuantity) { - const market = MARKETS[market_id]; - if (CHAIN_ID !== chainid) throw new Error("badchain"); +function genQuote(chainId, marketId, side, baseQuantity) { + const market = MARKETS[marketId]; + if (CHAIN_ID !== chainId) throw new Error("badchain"); if (!market) throw new Error("badmarket"); if (!(['b','s']).includes(side)) throw new Error("badside"); if (baseQuantity <= 0) throw new Error("badquantity"); - validatePriceFeed(market_id); + validatePriceFeed(marketId); - const mmConfig = MM_CONFIG.pairs[market_id]; + const mmConfig = MM_CONFIG.pairs[marketId]; const mmSide = mmConfig.side || 'd'; if (mmConfig.side !== 'd' && mmConfig.side === side) { throw new Error("badside"); } - const primaryPrice = getMidPrice(market_id); + const primaryPrice = getMidPrice(marketId); if (!primaryPrice) throw new Error("badprice"); const SPREAD = mmConfig.minSpread + (baseQuantity * mmConfig.slippageRate); let quoteQuantity; @@ -295,12 +293,12 @@ function genquote(chainid, market_id, side, baseQuantity) { return { quotePrice, quoteQuantity }; } -function validatePriceFeed(market_id) { - const mmConfig = MM_CONFIG.pairs[market_id]; - const mode = MM_CONFIG.pairs[market_id].mode || "pricefeed"; - const initPrice = MM_CONFIG.pairs[market_id].initPrice; - const primaryPriceFeedId = MM_CONFIG.pairs[market_id].priceFeedPrimary; - const secondaryPriceFeedId = MM_CONFIG.pairs[market_id].priceFeedSecondary; +function validatePriceFeed(marketId) { + const mmConfig = MM_CONFIG.pairs[marketId]; + const mode = MM_CONFIG.pairs[marketId].mode || "pricefeed"; + const initPrice = MM_CONFIG.pairs[marketId].initPrice; + const primaryPriceFeedId = MM_CONFIG.pairs[marketId].priceFeedPrimary; + const secondaryPriceFeedId = MM_CONFIG.pairs[marketId].priceFeedSecondary; // Constant mode checks if (mode === "constant") { @@ -329,120 +327,120 @@ function validatePriceFeed(market_id) { return true; } -async function sendfillrequest(orderreceipt, accountId) { - const chainId = orderreceipt[0]; - const orderId = orderreceipt[1]; - const market_id = orderreceipt[2]; - const market = MARKETS[market_id]; - const baseCurrency = market.baseAssetId; - const quoteCurrency = market.quoteAssetId; - const side = orderreceipt[3]; - const baseQuantity = orderreceipt[5]; - const quoteQuantity = orderreceipt[6]; - const quote = genquote(chainId, market_id, side, baseQuantity); - let tokenSell, tokenBuy, sellQuantity, buyQuantity; - if (side === "b") { - tokenSell = market.baseAssetId; - tokenBuy = market.quoteAssetId; - // Add 1 bip to to protect against rounding errors - sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals); - buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); - } else if (side === "s") { - tokenSell = market.quoteAssetId; - tokenBuy = market.baseAssetId; - // Add 1 bip to to protect against rounding errors - sellQuantity = (quote.quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals); - buyQuantity = (baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); - } - const sellQuantityParsed = syncProvider.tokenSet.parseToken( - tokenSell, - sellQuantity - ); - const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount(sellQuantityParsed); - const tokenRatio = {}; - tokenRatio[tokenBuy] = buyQuantity; - tokenRatio[tokenSell] = sellQuantity; - const one_min_expiry = (Date.now() / 1000 | 0) + 60; - const orderDetails = { - tokenSell, - tokenBuy, - amount: sellQuantityPacked, - ratio: zksync.utils.tokenRatio(tokenRatio), - validUntil: one_min_expiry - } - const fillOrder = await WALLETS[accountId].syncWallet.getOrder(orderDetails); - - // Set wallet flag - WALLETS[accountId]['ORDER_BROADCASTING'] = true; - - rememberOrder(chainId, orderId, market_id, quote.quotePrice, fillOrder); - const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] }; - zigzagws.send(JSON.stringify(resp)); +async function sendFillRequest(orderreceipt, accountId) { + const chainId = orderreceipt[0]; + const orderId = orderreceipt[1]; + const marketId = orderreceipt[2]; + const market = MARKETS[marketId]; + const baseCurrency = market.baseAssetId; + const quoteCurrency = market.quoteAssetId; + const side = orderreceipt[3]; + const baseQuantity = orderreceipt[5]; + const quoteQuantity = orderreceipt[6]; + const quote = genQuote(chainId, marketId, side, baseQuantity); + let tokenSell, tokenBuy, sellQuantity, buyQuantity; + if (side === "b") { + tokenSell = market.baseAssetId; + tokenBuy = market.quoteAssetId; + // Add 1 bip to to protect against rounding errors + sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals); + buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); + } else if (side === "s") { + tokenSell = market.quoteAssetId; + tokenBuy = market.baseAssetId; + // Add 1 bip to to protect against rounding errors + sellQuantity = (quote.quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals); + buyQuantity = (baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); + } + const sellQuantityParsed = syncProvider.tokenSet.parseToken( + tokenSell, + sellQuantity + ); + const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount(sellQuantityParsed); + const tokenRatio = {}; + tokenRatio[tokenBuy] = buyQuantity; + tokenRatio[tokenSell] = sellQuantity; + const oneMinExpiry = (Date.now() / 1000 | 0) + 60; + const orderDetails = { + tokenSell, + tokenBuy, + amount: sellQuantityPacked, + ratio: zksync.utils.tokenRatio(tokenRatio), + validUntil: oneMinExpiry + } + const fillOrder = await WALLETS[accountId].syncWallet.getOrder(orderDetails); + + // Set wallet flag + WALLETS[accountId]['ORDER_BROADCASTING'] = true; + + rememberOrder(chainId, orderId, marketId, quote.quotePrice, fillOrder); + const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] }; + zigzagws.send(JSON.stringify(resp)); } -async function broadcastfill(chainid, orderid, swapOffer, fillOrder, wallet) { - // Nonce check - const nonce = swapOffer.nonce; - const userNonce = NONCES[swapOffer.accountId]; - if (nonce <= userNonce) { - throw new Error("badnonce"); - } - const randint = (Math.random()*1000).toFixed(0); - console.time('syncswap' + randint); - const swap = await wallet['syncWallet'].syncSwap({ - orders: [swapOffer, fillOrder], - feeToken: "ETH", - nonce: fillOrder.nonce - }); - const txhash = swap.txHash.split(":")[1]; - const txhashmsg = {op:"orderstatusupdate", args:[[[chainid,orderid,'b',txhash]]]} - zigzagws.send(JSON.stringify(txhashmsg)); - console.timeEnd('syncswap' + randint); - - console.time('receipt' + randint); - let receipt, success = false; - try { - receipt = await swap.awaitReceipt(); - if (receipt.success) { - success = true; - NONCES[swapOffer.accountId] = swapOffer.nonce; - } - } catch (e) { - receipt = null; - success = false; - } - console.timeEnd('receipt' + randint); - console.log("Swap broadcast result", {swap, receipt}); - - if(success) { - const order = PAST_ORDER_LIST[orderid]; - if(order) { - const market_id = order.market; - const mmConfig = MM_CONFIG.pairs[market_id]; - if(mmConfig && mmConfig.delayAfterFill) { - mmConfig.active = false; - setTimeout(activatePair, mmConfig.delayAfterFill * 1000, market_id); - console.log(`Set ${market_id} passive for ${mmConfig.delayAfterFill} seconds.`) - } - } - } +async function broadcastfill(chainId, orderId, swapOffer, fillOrder, wallet) { + // Nonce check + const nonce = swapOffer.nonce; + const userNonce = NONCES[swapOffer.accountId]; + if (nonce <= userNonce) { + throw new Error("badnonce"); + } + const randInt = (Math.random()*1000).toFixed(0); + console.time('syncswap' + randInt); + const swap = await wallet['syncWallet'].syncSwap({ + orders: [swapOffer, fillOrder], + feeToken: "ETH", + nonce: fillOrder.nonce + }); + const txHash = swap.txHash.split(":")[1]; + const txHashMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'b',txHash]]]} + zigzagws.send(JSON.stringify(txHashMsg)); + console.timeEnd('syncswap' + randInt); + + console.time('receipt' + randInt); + let receipt, success = false; + try { + receipt = await swap.awaitReceipt(); + if (receipt.success) { + success = true; + NONCES[swapOffer.accountId] = swapOffer.nonce; + } + } catch (e) { + receipt = null; + success = false; + } + console.timeEnd('receipt' + randInt); + console.log("Swap broadcast result", {swap, receipt}); + + if(success) { + const order = PAST_ORDER_LIST[orderId]; + if(order) { + const marketId = order.market; + const mmConfig = MM_CONFIG.pairs[marketId]; + if(mmConfig && mmConfig.delayAfterFill) { + mmConfig.active = false; + setTimeout(activatePair, mmConfig.delayAfterFill * 1000, market_id); + console.log(`Set ${market_id} passive for ${mmConfig.delayAfterFill} seconds.`) + } + } + } - const newstatus = success ? 'f' : 'r'; - const error = success ? null : swap.error.toString(); - const ordercommitmsg = {op:"orderstatusupdate", args:[[[chainid,orderid,newstatus,txhash,error]]]} - zigzagws.send(JSON.stringify(ordercommitmsg)); + const newStatus = success ? 'f' : 'r'; + const error = success ? null : swap.error.toString(); + const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,newStatus,txHash,error]]]} + zigzagws.send(JSON.stringify(orderCommitMsg)); } async function fillOpenOrders() { - for (let orderid in OPEN_ORDERS) { - const order = OPEN_ORDERS[orderid]; + for (let orderId in OPEN_ORDERS) { + const order = OPEN_ORDERS[orderId]; const fillable = isOrderFillable(order); if (fillable.fillable) { FILL_QUEUE.push({ order: order, wallets: fillable.wallets}); - delete OPEN_ORDERS[orderid]; + delete OPEN_ORDERS[orderId]; } else if (fillable.reason !== "badprice") { - delete OPEN_ORDERS[orderid]; + delete OPEN_ORDERS[orderId]; } } } @@ -466,7 +464,7 @@ async function processFillQueue() { if (index < FILL_QUEUE.length) { const selectedOrder = FILL_QUEUE.splice(index, 1); try { - await sendfillrequest(selectedOrder[0].order, accountId); + await sendFillRequest(selectedOrder[0].order, accountId); return; } catch (e) { console.error(e); @@ -505,38 +503,38 @@ async function setupPriceFeeds() { console.log(PRICE_FEEDS); } -async function cryptowatchWsSetup(cryptowatch_market_ids) { +async function cryptowatchWsSetup(cryptowatchMarketIds) { // Set initial prices const cryptowatchApiKey = process.env.CRYPTOWATCH_API_KEY || MM_CONFIG.cryptowatchApiKey; - const cryptowatch_markets = await fetch("https://api.cryptowat.ch/markets?apikey=" + cryptowatchApiKey).then(r => r.json()); - const cryptowatch_market_prices = await fetch("https://api.cryptowat.ch/markets/prices?apikey=" + cryptowatchApiKey).then(r => r.json()); - for (let i in cryptowatch_market_ids) { - const cryptowatch_market_id = cryptowatch_market_ids[i]; + const cryptowatchMarkets = await fetch("https://api.cryptowat.ch/markets?apikey=" + cryptowatchApiKey).then(r => r.json()); + const cryptowatchMarketPrices = await fetch("https://api.cryptowat.ch/markets/prices?apikey=" + cryptowatchApiKey).then(r => r.json()); + for (let i in cryptowatchMarketIds) { + const cryptowatchMarketId = cryptowatchMarketIds[i]; try { - const cryptowatch_market = cryptowatch_markets.result.find(row => row.id == cryptowatch_market_id); - const exchange = cryptowatch_market.exchange; - const pair = cryptowatch_market.pair; + const cryptowatchMarket = cryptowatchMarkets.result.find(row => row.id == cryptowatchMarketId); + const exchange = cryptowatchMarket.exchange; + const pair = cryptowatchMarket.pair; const key = `market:${exchange}:${pair}`; - PRICE_FEEDS['cryptowatch:'+cryptowatch_market_ids[i]] = cryptowatch_market_prices.result[key]; + PRICE_FEEDS['cryptowatch:'+cryptowatchMarketIds[i]] = cryptowatchMarketPrices.result[key]; } catch (e) { - console.error("Could not set price feed for cryptowatch:" + cryptowatch_market_id); + console.error("Could not set price feed for cryptowatch:" + cryptowatchMarketId); } } const subscriptionMsg = { - "subscribe": { - "subscriptions": [] - } + "subscribe": { + "subscriptions": [] + } } - for (let i in cryptowatch_market_ids) { - const cryptowatch_market_id = cryptowatch_market_ids[i]; + for (let i in cryptowatchMarketIds) { + const cryptowatchMarketId = cryptowatchMarketIds[i]; // first get initial price info subscriptionMsg.subscribe.subscriptions.push({ - "streamSubscription": { - "resource": `markets:${cryptowatch_market_id}:trades` - } + "streamSubscription": { + "resource": `markets:${cryptowatchMarketId}:trades` + } }) } let cryptowatch_ws = new WebSocket("wss://stream.cryptowat.ch/connect?apikey=" + cryptowatchApiKey); @@ -544,7 +542,7 @@ async function cryptowatchWsSetup(cryptowatch_market_ids) { cryptowatch_ws.on('message', onmessage); cryptowatch_ws.on('close', onclose); cryptowatch_ws.on('error', console.error); - + function onopen() { cryptowatch_ws.send(JSON.stringify(subscriptionMsg)); } @@ -552,22 +550,23 @@ async function cryptowatchWsSetup(cryptowatch_market_ids) { const msg = JSON.parse(data); if (!msg.marketUpdate) return; - const market_id = "cryptowatch:" + msg.marketUpdate.market.marketId; + const marketId = "cryptowatch:" + msg.marketUpdate.market.marketId; let trades = msg.marketUpdate.tradesUpdate.trades; let price = trades[trades.length - 1].priceStr / 1; - PRICE_FEEDS[market_id] = price; - }; + PRICE_FEEDS[marketId] = price; + } function onclose () { - setTimeout(cryptowatchWsSetup, 5000, cryptowatch_market_ids); + setTimeout(cryptowatchWsSetup, 5000, cryptowatchMarketIds); } } -async function chainlinkSetup(chainlink_market_address) { - chainlink_market_address.forEach(async (address) => { +async function chainlinkSetup(chainlinkMarketAddress) { + chainlinkMarketAddress.forEach(async (address) => { try { + const aggregatorV3InterfaceABI = JSON.parse(fs.readFileSync('chainlinkV3InterfaceABI.abi')); const provider = new ethers.Contract(address, aggregatorV3InterfaceABI, ethersProvider); const decimals = await provider.decimals(); - chainlinkProviders['chainlink:'+address] = [provider, decimals]; + CHAINLINK_PROVIDERS['chainlink:'+address] = [provider, decimals]; // get inital price const response = await provider.latestRoundData(); @@ -580,8 +579,8 @@ async function chainlinkSetup(chainlink_market_address) { } async function chainlinkUpdate() { - await Promise.all(Object.keys(chainlinkProviders).map(async (key) => { - const [provider, decimals] = chainlinkProviders[key]; + await Promise.all(Object.keys(CHAINLINK_PROVIDERS).map(async (key) => { + const [provider, decimals] = CHAINLINK_PROVIDERS[key]; const response = await provider.latestRoundData(); const price = parseFloat(response.answer) / 10**decimals; })); @@ -589,21 +588,21 @@ async function chainlinkUpdate() { const CLIENT_ID = (Math.random() * 100000).toString(16); function indicateLiquidity () { - for(const market_id in MM_CONFIG.pairs) { - const mmConfig = MM_CONFIG.pairs[market_id]; + for(const marketId in MM_CONFIG.pairs) { + const mmConfig = MM_CONFIG.pairs[marketId]; if(!mmConfig || !mmConfig.active) continue; try { - validatePriceFeed(market_id); + validatePriceFeed(marketId); } catch(e) { - console.error("Can not indicateLiquidity ("+market_id+") because: " + e); + console.error("Can not indicateLiquidity ("+marketId+") because: " + e); continue; } - const marketInfo = MARKETS[market_id]; + const marketInfo = MARKETS[marketId]; if (!marketInfo) continue; - const midPrice = getMidPrice(market_id); + const midPrice = getMidPrice(marketId); if (!midPrice) continue; const expires = (Date.now() / 1000 | 0) + 10; // 10s expiry @@ -637,7 +636,7 @@ function indicateLiquidity () { liquidity.push(["s", sellPrice, maxSellSize / splits, expires]); } } - const msg = { op: "indicateliq2", args: [CHAIN_ID, market_id, liquidity, CLIENT_ID] }; + const msg = { op: "indicateliq2", args: [CHAIN_ID, marketId, liquidity, CLIENT_ID] }; try { zigzagws.send(JSON.stringify(msg)); } catch (e) { @@ -647,8 +646,8 @@ function indicateLiquidity () { } } -function getMidPrice (market_id) { - const mmConfig = MM_CONFIG.pairs[market_id]; +function getMidPrice (marketId) { + const mmConfig = MM_CONFIG.pairs[marketId]; const mode = mmConfig.mode || "pricefeed"; let midPrice; if (mode == "constant") { @@ -660,8 +659,8 @@ function getMidPrice (market_id) { return midPrice; } -function activatePair(market_id) { - const mmConfig = MM_CONFIG.pairs[market_id]; +function activatePair(marketId) { + const mmConfig = MM_CONFIG.pairs[marketId]; if(!mmConfig) return; mmConfig.active = true; console.log(`Set ${market_id} active.`) @@ -678,7 +677,6 @@ function rememberOrder(chainId, orderId, market, price, fillOrder) { const expiry = timestamp + 900; PAST_ORDER_LIST[orderId] = { 'chainId': chainId, - 'orderId': orderId, 'market': market, 'price': price, 'fillOrder': fillOrder, @@ -694,7 +692,7 @@ async function updateAccountState() { }) }); } catch(err) { - // pass + // pass } } @@ -707,10 +705,10 @@ async function logBalance() { const committedBalaces = WALLETS[accountId]['account_state'].committed.balances; Object.keys(committedBalaces).forEach(token => { if(balance[token]) { - balance[token] = balance[token] + parseInt(committedBalaces[token]); - } else { - balance[token] = parseInt(committedBalaces[token]); - } + balance[token] = balance[token] + parseInt(committedBalaces[token]); + } else { + balance[token] = parseInt(committedBalaces[token]); + } }); }); // get token price and total in USD @@ -726,6 +724,6 @@ async function logBalance() { const content = date + ";" + sum.toFixed(2) + "\n"; fs.writeFile('price_csv.txt', content, { flag: 'a+' }, err => {}); } catch(err) { - // pass + // pass } } From d2c34cf3d069c5d568e64880290067d352a003a2 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 15 Feb 2022 17:37:49 +0100 Subject: [PATCH 049/160] small fixes --- marketmaker.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index bfd98d3..85abb50 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -171,7 +171,7 @@ async function handleMessage(json) { break } else { try { - await broadcastfill(chainId, orderId, msg.args[2], fillOrder, wallet); + await broadcastFill(chainId, orderId, msg.args[2], fillOrder, wallet); } catch (e) { console.error(e); } @@ -192,7 +192,7 @@ async function handleMessage(json) { MARKETS[marketId] = marketInfo; const newBaseFee = MARKETS[marketId].baseFee; const newQuoteFee = MARKETS[marketId].quoteFee; - console.log(`marketinfo ${} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); + console.log(`marketinfo ${marketId} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); break default: break @@ -378,7 +378,7 @@ async function sendFillRequest(orderreceipt, accountId) { zigzagws.send(JSON.stringify(resp)); } -async function broadcastfill(chainId, orderId, swapOffer, fillOrder, wallet) { +async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { // Nonce check const nonce = swapOffer.nonce; const userNonce = NONCES[swapOffer.accountId]; @@ -420,7 +420,7 @@ async function broadcastfill(chainId, orderId, swapOffer, fillOrder, wallet) { if(mmConfig && mmConfig.delayAfterFill) { mmConfig.active = false; setTimeout(activatePair, mmConfig.delayAfterFill * 1000, market_id); - console.log(`Set ${market_id} passive for ${mmConfig.delayAfterFill} seconds.`) + console.log(`Set ${market_id} passive for ${mmConfig.delayAfterFill} seconds.`); } } } @@ -663,7 +663,7 @@ function activatePair(marketId) { const mmConfig = MM_CONFIG.pairs[marketId]; if(!mmConfig) return; mmConfig.active = true; - console.log(`Set ${market_id} active.`) + console.log(`Set ${market_id} active.`); } function rememberOrder(chainId, orderId, market, price, fillOrder) { From bf1d5d961ef46434ff5383c25c371fa2600120d7 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 15 Feb 2022 17:43:25 +0100 Subject: [PATCH 050/160] remove merge bug --- marketmaker.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 85abb50..d7a27d7 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -419,8 +419,8 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { const mmConfig = MM_CONFIG.pairs[marketId]; if(mmConfig && mmConfig.delayAfterFill) { mmConfig.active = false; - setTimeout(activatePair, mmConfig.delayAfterFill * 1000, market_id); - console.log(`Set ${market_id} passive for ${mmConfig.delayAfterFill} seconds.`); + setTimeout(activatePair, mmConfig.delayAfterFill * 1000, marketId); + console.log(`Set ${marketId} passive for ${mmConfig.delayAfterFill} seconds.`); } } } @@ -663,7 +663,7 @@ function activatePair(marketId) { const mmConfig = MM_CONFIG.pairs[marketId]; if(!mmConfig) return; mmConfig.active = true; - console.log(`Set ${market_id} active.`); + console.log(`Set ${marketId} active.`); } function rememberOrder(chainId, orderId, market, price, fillOrder) { From f48ce187afd09c710493dbeaadd017fb51f4a347 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 16 Feb 2022 10:36:18 +0100 Subject: [PATCH 051/160] cancel Liquidity --- marketmaker.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/marketmaker.js b/marketmaker.js index d7a27d7..1b9bc1b 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -420,6 +420,7 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { if(mmConfig && mmConfig.delayAfterFill) { mmConfig.active = false; setTimeout(activatePair, mmConfig.delayAfterFill * 1000, marketId); + cancelLiquidity (chainId, marketId); console.log(`Set ${marketId} passive for ${mmConfig.delayAfterFill} seconds.`); } } @@ -646,6 +647,16 @@ function indicateLiquidity () { } } +function cancelLiquidity (chainId, marketId) { + const msg = { op: "indicateliq2", args: [chainId, marketId, [], CLIENT_ID] }; + try { + zigzagws.send(JSON.stringify(msg)); + } catch (e) { + console.error("Could not send liquidity"); + console.error(e); + } +} + function getMidPrice (marketId) { const mmConfig = MM_CONFIG.pairs[marketId]; const mode = mmConfig.mode || "pricefeed"; From 6cf87018c51d6e30016e2f090df27b877d84a9ea Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 16 Feb 2022 14:37:22 +0100 Subject: [PATCH 052/160] readme in seconds --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0f1ac34..23dd208 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ A market can be set inactive by flipping the active switch to `false`. ###### delayAfterFill The market maker will stop market making on the pair, after successfully filling an order. This can be used to wait out bigger price moves. -Example: +Example, here a delay of **60 seconds** is used: ``` "ETH-USDC": { "mode": "pricefeed", @@ -131,7 +131,7 @@ Example: "minSize": 0.0003, "minSpread": 0.0005, "active": true, - "delayAfterFill": 60 + "delayAfterFill": 60 <- } ``` From bd88bdd25d52986829def48cfbc622cb8db553d0 Mon Sep 17 00:00:00 2001 From: Trooper <95502080+TrooperCrypto@users.noreply.github.com> Date: Wed, 16 Feb 2022 14:38:56 +0100 Subject: [PATCH 053/160] Update main version to latest dev version This merge includes: - Improve stability of ws: https://github.com/ZigZagExchange/market-maker/pull/15 - Delay after fill: https://github.com/ZigZagExchange/market-maker/pull/16 - move chainlink ABI in file: https://github.com/ZigZagExchange/market-maker/pull/17 - cleanup of naming & spacing: https://github.com/ZigZagExchange/market-maker/pull/18 --- README.md | 45 +++- chainlinkV3InterfaceABI.abi | 121 +++++++++ marketmaker.js | 521 ++++++++++++++++++++---------------- 3 files changed, 443 insertions(+), 244 deletions(-) create mode 100644 chainlinkV3InterfaceABI.abi diff --git a/README.md b/README.md index 8d106d2..23dd208 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ This is the reference market maker for Zigzag zksync markets. It works on both Rinkeby and Mainnet. -This market maker uses existing price feeds to set bids and asks for a market. For now, in order to provide liquidity for a market, there must be an existing market with **greater** liquidity listed on Cryptowatch, via either Uniswap or some other centralized exchange. It is crucial that the oracle market have more liquidity than the Zigzag one so that you are not prone to oracle attacks. +This market maker uses existing price feeds to set bids and asks for a market. For now, in order to provide liquidity for a market, there must be an existing market with **greater** liquidity listed on Cryptowatch, via either Uniswap or some other centralized exchange. It is crucial that the oracle market have more liquidity than the Zigzag one so that you are not prone to oracle attacks. -Soon we will add the ability to run standalone markets and this will not be an issue. +Soon we will add the ability to run standalone markets and this will not be an issue. ## Requirements @@ -18,7 +18,7 @@ Soon we will add the ability to run standalone markets and this will not be an i ## Setup -Copy the `config.json.EXAMPLE` file to `config.json` to get started. +Copy the `config.json.EXAMPLE` file to `config.json` to get started. Set your `eth_privkey` to be able to relay transactions. The ETH address with that private key should be loaded up with adequate funds for market making. @@ -93,29 +93,50 @@ You can add, remove, and configure pair settings in the `pairs` section. A pair } ``` -There are 2 modes available with a 3rd on the way. +There are 2 modes available with a 3rd on the way. -* `pricefeed`: Follows an external price oracle and updates indicated bids and asks based on that. +* `pricefeed`: Follows an external price oracle and updates indicated bids and asks based on that. * `constant`: Sets an `initPrice` and market makes around that price. Can be combined with single-sided liquidity to simulate limit orders. -* `independent`: Under development. The price is set independent of a price feed. +* `independent`: Under development. The price is set independent of a price feed. For all modes the `slippageRate`, `maxSize`, `minSize`, `minSpread`, and `active` settings are mandatory. -For `pricefeed` mode, the `priceFeedPrimary` is mandatory. +For `pricefeed` mode, the `priceFeedPrimary` is mandatory. -For `independent` and `constant` mode, the `initPrice` is mandatory. +For `independent` and `constant` mode, the `initPrice` is mandatory. The `side` setting can be toggled for single-sided liquidity. By default, the side setting is set to `d`, which stands for double-sided liquidity. To toggle single-sided liquidity, the value can be set to `b` or `s` for buy-side only or sell-side only. -The primary price feed is the price feed used to determine the bids and asks of the market maker. The secondary price feed is used to validate the first price feed and make sure the market isn't returning bad data. If the primary and secondary price feeds vary by more than 1%, the market maker will not fill orders. +The primary price feed is the price feed used to determine the bids and asks of the market maker. The secondary price feed is used to validate the first price feed and make sure the market isn't returning bad data. If the primary and secondary price feeds vary by more than 1%, the market maker will not fill orders. -The slippage rate is the rate at which the spread increases as the base unit increases. For the example above, the spread goes up by 1e-5 for every 1 ETH in size added to an order. That's the equivalent of 0.1 bps / ETH in slippage. +The slippage rate is the rate at which the spread increases as the base unit increases. For the example above, the spread goes up by 1e-5 for every 1 ETH in size added to an order. That's the equivalent of 0.1 bps / ETH in slippage. Orders coming in below the `minSpread` from the price feed will not be filled. The spread is calculated as a decimal value. 0.01 is 1%, and 0.0002 is 2 basis points (bps). -A market can be set inactive by flipping the active switch to `false`. +A market can be set inactive by flipping the active switch to `false`. -## Pair Setting Examples +## Pair Options + +###### delayAfterFill +The market maker will stop market making on the pair, after successfully filling an order. This can be used to wait out bigger price moves. +Example, here a delay of **60 seconds** is used: +``` +"ETH-USDC": { + "mode": "pricefeed", + "side": "b", + "priceFeedPrimary": "cryptowatch:6631", + "priceFeedSecondary": "cryptowatch:588", + "slippageRate": 1e-5, + "maxSize": 100, + "minSize": 0.0003, + "minSpread": 0.0005, + "active": true, + "delayAfterFill": 60 <- +} +``` + + +## Pair Setting Examples Stable-Stable constant price: diff --git a/chainlinkV3InterfaceABI.abi b/chainlinkV3InterfaceABI.abi new file mode 100644 index 0000000..4c5f462 --- /dev/null +++ b/chainlinkV3InterfaceABI.abi @@ -0,0 +1,121 @@ +[ + { + "inputs":[ + + ], + "name":"decimals", + "outputs":[ + { + "internalType":"uint8", + "name":"", + "type":"uint8" + } + ], + "stateMutability":"view", + "type":"function" + }, + { + "inputs":[ + + ], + "name":"description", + "outputs":[ + { + "internalType":"string", + "name":"", + "type":"string" + } + ], + "stateMutability":"view", + "type":"function" + }, + { + "inputs":[ + { + "internalType":"uint80", + "name":"_roundId", + "type":"uint80" + } + ], + "name":"getRoundData", + "outputs":[ + { + "internalType":"uint80", + "name":"roundId", + "type":"uint80" + }, + { + "internalType":"int256", + "name":"answer", + "type":"int256" + }, + { + "internalType":"uint256", + "name":"startedAt", + "type":"uint256" + }, + { + "internalType":"uint256", + "name":"updatedAt", + "type":"uint256" + }, + { + "internalType":"uint80", + "name":"answeredInRound", + "type":"uint80" + } + ], + "stateMutability":"view", + "type":"function" + }, + { + "inputs":[ + + ], + "name":"latestRoundData", + "outputs":[ + { + "internalType":"uint80", + "name":"roundId", + "type":"uint80" + }, + { + "internalType":"int256", + "name":"answer", + "type":"int256" + }, + { + "internalType":"uint256", + "name":"startedAt", + "type":"uint256" + }, + { + "internalType":"uint256", + "name":"updatedAt", + "type":"uint256" + }, + { + "internalType":"uint80", + "name":"answeredInRound", + "type":"uint80" + } + ], + "stateMutability":"view", + "type":"function" + }, + { + "inputs":[ + + ], + "name":"version", + "outputs":[ + { + "internalType":"uint256", + "name":"", + "type":"uint256" + } + ], + "stateMutability":"view", + "type":"function" + } +] diff --git a/marketmaker.js b/marketmaker.js index 63914da..1b9bc1b 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -14,10 +14,9 @@ const NONCES = {}; const WALLETS = {}; const FILL_QUEUE = []; const MARKETS = {}; -const chainlinkProviders = {}; +const CHAINLINK_PROVIDERS = {}; +const PAST_ORDER_LIST = {}; -// coinlink interface ABI -const aggregatorV3InterfaceABI = [{ "inputs": [], "name": "decimals", "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "description", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint80", "name": "_roundId", "type": "uint80" }], "name": "getRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "latestRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "version", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }] // Load MM config let MM_CONFIG; @@ -51,7 +50,7 @@ if(providerUrl && ETH_NETWORK=="mainnet") { // Start price feeds await setupPriceFeeds(); -let syncProvider, fillOrdersInterval, indicateLiquidityInterval; +let syncProvider; try { syncProvider = await zksync.getDefaultProvider(ETH_NETWORK); const keys = []; @@ -72,24 +71,24 @@ try { }); } for(let i=0; i indicateLiquidity(market), 5000); const msg = {op:"subscribemarket", args:[CHAIN_ID, market]}; zigzagws.send(JSON.stringify(msg)); } } } - + function onWsClose () { console.log("Websocket closed. Restarting"); Object.keys(WALLETS).forEach(accountId => { @@ -134,7 +133,8 @@ function onWsClose () { clearInterval(indicateLiquidityInterval) zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); zigzagws.on('open', onWsOpen); - zigzagws.on('error', onWsClose); + zigzagws.on('close', onWsClose); + zigzagws.on('error', console.error); }, 5000); } @@ -150,20 +150,20 @@ async function handleMessage(json) { case 'orders': const orders = msg.args[0]; orders.forEach(order => { - const orderid = order[1]; + const orderId = order[1]; const fillable = isOrderFillable(order); console.log(fillable); if (fillable.fillable) { FILL_QUEUE.push({ order: order, wallets: fillable.wallets}); } else if (fillable.reason === "badprice") { - OPEN_ORDERS[orderid] = order; + OPEN_ORDERS[orderId] = order; } }); break case "userordermatch": - const chainid = msg.args[0]; - const orderid = msg.args[1]; + const chainId = msg.args[0]; + const orderId = msg.args[1]; const fillOrder = msg.args[3]; const wallet = WALLETS[fillOrder.accountId]; if(!wallet) { @@ -171,7 +171,7 @@ async function handleMessage(json) { break } else { try { - await broadcastfill(chainid, orderid, msg.args[2], fillOrder, wallet); + await broadcastFill(chainId, orderId, msg.args[2], fillOrder, wallet); } catch (e) { console.error(e); } @@ -179,20 +179,20 @@ async function handleMessage(json) { } break case "marketinfo": - const market_info = msg.args[0]; - const market_id = market_info.alias; - MARKETS[market_id] = market_info; - let oldBaseFee, oldQuoteFee; + const marketInfo = msg.args[0]; + const marketId = marketInfo.alias; + if(!marketId) break + let oldBaseFee = "N/A", oldQuoteFee = "N/A"; try { - oldBaseFee = MARKETS[market_info.alias].baseFee; - oldQuoteFee = MARKETS[market_info.alias].quoteFee; + oldBaseFee = MARKETS[marketId].baseFee; + oldQuoteFee = MARKETS[marketId].quoteFee; } catch (e) { - console.log(market_info) - break + // pass, no old marketInfo } - const newBaseFee = market_info.baseFee; - const newQuoteFee = market_info.quoteFee; - console.log(`marketinfo ${market_id} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); + MARKETS[marketId] = marketInfo; + const newBaseFee = MARKETS[marketId].baseFee; + const newQuoteFee = MARKETS[marketId].quoteFee; + console.log(`marketinfo ${marketId} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); break default: break @@ -200,12 +200,12 @@ async function handleMessage(json) { } function isOrderFillable(order) { - const chainid = order[0]; - const market_id = order[2]; - const market = MARKETS[market_id]; - const mmConfig = MM_CONFIG.pairs[market_id]; + const chainId = order[0]; + const marketId = order[2]; + const market = MARKETS[marketId]; + const mmConfig = MM_CONFIG.pairs[marketId]; const mmSide = mmConfig.side || 'd'; - if (chainid != CHAIN_ID) return { fillable: false, reason: "badchain" } + if (chainId != CHAIN_ID) return { fillable: false, reason: "badchain" } if (!market) return { fillable: false, reason: "badmarket" } if (!mmConfig.active) return { fillable: false, reason: "inactivemarket" } @@ -220,10 +220,10 @@ function isOrderFillable(order) { const neededBalanceBN = sellQuantity * 10**sellDecimals; const goodWallets = []; Object.keys(WALLETS).forEach(accountId => { - const walletBalance = WALLETS[accountId]['account_state'].committed.balances[sellCurrency]; - if (Number(walletBalance) > (neededBalanceBN * 1.05)) { - goodWallets.push(accountId); - } + const walletBalance = WALLETS[accountId]['account_state'].committed.balances[sellCurrency]; + if (Number(walletBalance) > (neededBalanceBN * 1.05)) { + goodWallets.push(accountId); + } }); const now = Date.now() / 1000 | 0; @@ -234,7 +234,7 @@ function isOrderFillable(order) { if (mmSide !== 'd' && mmSide == side) { return { fillable: false, reason: "badside" }; } - + if (baseQuantity < mmConfig.minSize) { return { fillable: false, reason: "badsize" }; } @@ -248,7 +248,7 @@ function isOrderFillable(order) { let quote; try { - quote = genquote(chainid, market_id, side, baseQuantity); + quote = genQuote(chainId, marketId, side, baseQuantity); } catch (e) { return { fillable: false, reason: e.message } } @@ -263,21 +263,21 @@ function isOrderFillable(order) { return { fillable: true, reason: null, wallets: goodWallets}; } -function genquote(chainid, market_id, side, baseQuantity) { - const market = MARKETS[market_id]; - if (CHAIN_ID !== chainid) throw new Error("badchain"); +function genQuote(chainId, marketId, side, baseQuantity) { + const market = MARKETS[marketId]; + if (CHAIN_ID !== chainId) throw new Error("badchain"); if (!market) throw new Error("badmarket"); if (!(['b','s']).includes(side)) throw new Error("badside"); if (baseQuantity <= 0) throw new Error("badquantity"); - validatePriceFeed(market_id); + validatePriceFeed(marketId); - const mmConfig = MM_CONFIG.pairs[market_id]; + const mmConfig = MM_CONFIG.pairs[marketId]; const mmSide = mmConfig.side || 'd'; if (mmConfig.side !== 'd' && mmConfig.side === side) { throw new Error("badside"); } - const primaryPrice = getMidPrice(market_id); + const primaryPrice = getMidPrice(marketId); if (!primaryPrice) throw new Error("badprice"); const SPREAD = mmConfig.minSpread + (baseQuantity * mmConfig.slippageRate); let quoteQuantity; @@ -293,12 +293,12 @@ function genquote(chainid, market_id, side, baseQuantity) { return { quotePrice, quoteQuantity }; } -function validatePriceFeed(market_id) { - const mmConfig = MM_CONFIG.pairs[market_id]; - const mode = MM_CONFIG.pairs[market_id].mode || "pricefeed"; - const initPrice = MM_CONFIG.pairs[market_id].initPrice; - const primaryPriceFeedId = MM_CONFIG.pairs[market_id].priceFeedPrimary; - const secondaryPriceFeedId = MM_CONFIG.pairs[market_id].priceFeedSecondary; +function validatePriceFeed(marketId) { + const mmConfig = MM_CONFIG.pairs[marketId]; + const mode = MM_CONFIG.pairs[marketId].mode || "pricefeed"; + const initPrice = MM_CONFIG.pairs[marketId].initPrice; + const primaryPriceFeedId = MM_CONFIG.pairs[marketId].priceFeedPrimary; + const secondaryPriceFeedId = MM_CONFIG.pairs[marketId].priceFeedSecondary; // Constant mode checks if (mode === "constant") { @@ -309,7 +309,7 @@ function validatePriceFeed(market_id) { // Check if primary price exists const primaryPrice = PRICE_FEEDS[primaryPriceFeedId]; if (!primaryPrice) throw new Error("Primary price feed unavailable"); - + // If there is no secondary price feed, the price auto-validates if (!secondaryPriceFeedId) return true; @@ -327,106 +327,121 @@ function validatePriceFeed(market_id) { return true; } -async function sendfillrequest(orderreceipt, accountId) { - const chainId = orderreceipt[0]; - const orderId = orderreceipt[1]; - const market_id = orderreceipt[2]; - const market = MARKETS[market_id]; - const baseCurrency = market.baseAssetId; - const quoteCurrency = market.quoteAssetId; - const side = orderreceipt[3]; - const baseQuantity = orderreceipt[5]; - const quoteQuantity = orderreceipt[6]; - const quote = genquote(chainId, market_id, side, baseQuantity); - let tokenSell, tokenBuy, sellQuantity, buyQuantity; - if (side === "b") { - tokenSell = market.baseAssetId; - tokenBuy = market.quoteAssetId; - // Add 1 bip to to protect against rounding errors - sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals); - buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); - } else if (side === "s") { - tokenSell = market.quoteAssetId; - tokenBuy = market.baseAssetId; - // Add 1 bip to to protect against rounding errors - sellQuantity = (quote.quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals); - buyQuantity = (baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); - } - const sellQuantityParsed = syncProvider.tokenSet.parseToken( - tokenSell, - sellQuantity - ); - const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount(sellQuantityParsed); - const tokenRatio = {}; - tokenRatio[tokenBuy] = buyQuantity; - tokenRatio[tokenSell] = sellQuantity; - const one_min_expiry = (Date.now() / 1000 | 0) + 60; - const orderDetails = { - tokenSell, - tokenBuy, - amount: sellQuantityPacked, - ratio: zksync.utils.tokenRatio(tokenRatio), - validUntil: one_min_expiry - } - const fillOrder = await WALLETS[accountId].syncWallet.getOrder(orderDetails); - - // Set wallet flag - WALLETS[accountId]['ORDER_BROADCASTING'] = true; - - const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] }; - zigzagws.send(JSON.stringify(resp)); +async function sendFillRequest(orderreceipt, accountId) { + const chainId = orderreceipt[0]; + const orderId = orderreceipt[1]; + const marketId = orderreceipt[2]; + const market = MARKETS[marketId]; + const baseCurrency = market.baseAssetId; + const quoteCurrency = market.quoteAssetId; + const side = orderreceipt[3]; + const baseQuantity = orderreceipt[5]; + const quoteQuantity = orderreceipt[6]; + const quote = genQuote(chainId, marketId, side, baseQuantity); + let tokenSell, tokenBuy, sellQuantity, buyQuantity; + if (side === "b") { + tokenSell = market.baseAssetId; + tokenBuy = market.quoteAssetId; + // Add 1 bip to to protect against rounding errors + sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals); + buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); + } else if (side === "s") { + tokenSell = market.quoteAssetId; + tokenBuy = market.baseAssetId; + // Add 1 bip to to protect against rounding errors + sellQuantity = (quote.quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals); + buyQuantity = (baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); + } + const sellQuantityParsed = syncProvider.tokenSet.parseToken( + tokenSell, + sellQuantity + ); + const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount(sellQuantityParsed); + const tokenRatio = {}; + tokenRatio[tokenBuy] = buyQuantity; + tokenRatio[tokenSell] = sellQuantity; + const oneMinExpiry = (Date.now() / 1000 | 0) + 60; + const orderDetails = { + tokenSell, + tokenBuy, + amount: sellQuantityPacked, + ratio: zksync.utils.tokenRatio(tokenRatio), + validUntil: oneMinExpiry + } + const fillOrder = await WALLETS[accountId].syncWallet.getOrder(orderDetails); + + // Set wallet flag + WALLETS[accountId]['ORDER_BROADCASTING'] = true; + + rememberOrder(chainId, orderId, marketId, quote.quotePrice, fillOrder); + const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] }; + zigzagws.send(JSON.stringify(resp)); } -async function broadcastfill(chainid, orderid, swapOffer, fillOrder, wallet) { - // Nonce check - const nonce = swapOffer.nonce; - const userNonce = NONCES[swapOffer.accountId]; - if (nonce <= userNonce) { - throw new Error("badnonce"); - } - const randint = (Math.random()*1000).toFixed(0); - console.time('syncswap' + randint); - const swap = await wallet['syncWallet'].syncSwap({ - orders: [swapOffer, fillOrder], - feeToken: "ETH", - nonce: fillOrder.nonce - }); - const txhash = swap.txHash.split(":")[1]; - const txhashmsg = {op:"orderstatusupdate", args:[[[chainid,orderid,'b',txhash]]]} - zigzagws.send(JSON.stringify(txhashmsg)); - console.timeEnd('syncswap' + randint); - - console.time('receipt' + randint); - let receipt, success = false; - try { - receipt = await swap.awaitReceipt(); - if (receipt.success) { - success = true; - NONCES[swapOffer.accountId] = swapOffer.nonce; - } - } catch (e) { - receipt = null; - success = false; - } - console.timeEnd('receipt' + randint); +async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { + // Nonce check + const nonce = swapOffer.nonce; + const userNonce = NONCES[swapOffer.accountId]; + if (nonce <= userNonce) { + throw new Error("badnonce"); + } + const randInt = (Math.random()*1000).toFixed(0); + console.time('syncswap' + randInt); + const swap = await wallet['syncWallet'].syncSwap({ + orders: [swapOffer, fillOrder], + feeToken: "ETH", + nonce: fillOrder.nonce + }); + const txHash = swap.txHash.split(":")[1]; + const txHashMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'b',txHash]]]} + zigzagws.send(JSON.stringify(txHashMsg)); + console.timeEnd('syncswap' + randInt); + + console.time('receipt' + randInt); + let receipt, success = false; + try { + receipt = await swap.awaitReceipt(); + if (receipt.success) { + success = true; + NONCES[swapOffer.accountId] = swapOffer.nonce; + } + } catch (e) { + receipt = null; + success = false; + } + console.timeEnd('receipt' + randInt); + console.log("Swap broadcast result", {swap, receipt}); + + if(success) { + const order = PAST_ORDER_LIST[orderId]; + if(order) { + const marketId = order.market; + const mmConfig = MM_CONFIG.pairs[marketId]; + if(mmConfig && mmConfig.delayAfterFill) { + mmConfig.active = false; + setTimeout(activatePair, mmConfig.delayAfterFill * 1000, marketId); + cancelLiquidity (chainId, marketId); + console.log(`Set ${marketId} passive for ${mmConfig.delayAfterFill} seconds.`); + } + } + } - console.log("Swap broadcast result", {swap, receipt}); - const newstatus = success ? 'f' : 'r'; - const error = success ? null : swap.error.toString(); - const ordercommitmsg = {op:"orderstatusupdate", args:[[[chainid,orderid,newstatus,txhash,error]]]} - zigzagws.send(JSON.stringify(ordercommitmsg)); + const newStatus = success ? 'f' : 'r'; + const error = success ? null : swap.error.toString(); + const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,newStatus,txHash,error]]]} + zigzagws.send(JSON.stringify(orderCommitMsg)); } async function fillOpenOrders() { - for (let orderid in OPEN_ORDERS) { - const order = OPEN_ORDERS[orderid]; + for (let orderId in OPEN_ORDERS) { + const order = OPEN_ORDERS[orderId]; const fillable = isOrderFillable(order); if (fillable.fillable) { FILL_QUEUE.push({ order: order, wallets: fillable.wallets}); - delete OPEN_ORDERS[orderid]; + delete OPEN_ORDERS[orderId]; } else if (fillable.reason !== "badprice") { - delete OPEN_ORDERS[orderid]; + delete OPEN_ORDERS[orderId]; } } } @@ -450,7 +465,7 @@ async function processFillQueue() { if (index < FILL_QUEUE.length) { const selectedOrder = FILL_QUEUE.splice(index, 1); try { - await sendfillrequest(selectedOrder[0].order, accountId); + await sendFillRequest(selectedOrder[0].order, accountId); return; } catch (e) { console.error(e); @@ -489,44 +504,46 @@ async function setupPriceFeeds() { console.log(PRICE_FEEDS); } -async function cryptowatchWsSetup(cryptowatch_market_ids) { +async function cryptowatchWsSetup(cryptowatchMarketIds) { // Set initial prices const cryptowatchApiKey = process.env.CRYPTOWATCH_API_KEY || MM_CONFIG.cryptowatchApiKey; - const cryptowatch_markets = await fetch("https://api.cryptowat.ch/markets?apikey=" + cryptowatchApiKey).then(r => r.json()); - const cryptowatch_market_prices = await fetch("https://api.cryptowat.ch/markets/prices?apikey=" + cryptowatchApiKey).then(r => r.json()); - for (let i in cryptowatch_market_ids) { - const cryptowatch_market_id = cryptowatch_market_ids[i]; + const cryptowatchMarkets = await fetch("https://api.cryptowat.ch/markets?apikey=" + cryptowatchApiKey).then(r => r.json()); + const cryptowatchMarketPrices = await fetch("https://api.cryptowat.ch/markets/prices?apikey=" + cryptowatchApiKey).then(r => r.json()); + for (let i in cryptowatchMarketIds) { + const cryptowatchMarketId = cryptowatchMarketIds[i]; try { - const cryptowatch_market = cryptowatch_markets.result.find(row => row.id == cryptowatch_market_id); - const exchange = cryptowatch_market.exchange; - const pair = cryptowatch_market.pair; + const cryptowatchMarket = cryptowatchMarkets.result.find(row => row.id == cryptowatchMarketId); + const exchange = cryptowatchMarket.exchange; + const pair = cryptowatchMarket.pair; const key = `market:${exchange}:${pair}`; - PRICE_FEEDS['cryptowatch:'+cryptowatch_market_ids[i]] = cryptowatch_market_prices.result[key]; + PRICE_FEEDS['cryptowatch:'+cryptowatchMarketIds[i]] = cryptowatchMarketPrices.result[key]; } catch (e) { - console.error("Could not set price feed for cryptowatch:" + cryptowatch_market_id); + console.error("Could not set price feed for cryptowatch:" + cryptowatchMarketId); } } const subscriptionMsg = { - "subscribe": { - "subscriptions": [] - } + "subscribe": { + "subscriptions": [] + } } - for (let i in cryptowatch_market_ids) { - const cryptowatch_market_id = cryptowatch_market_ids[i]; + for (let i in cryptowatchMarketIds) { + const cryptowatchMarketId = cryptowatchMarketIds[i]; // first get initial price info subscriptionMsg.subscribe.subscriptions.push({ - "streamSubscription": { - "resource": `markets:${cryptowatch_market_id}:trades` - } + "streamSubscription": { + "resource": `markets:${cryptowatchMarketId}:trades` + } }) } let cryptowatch_ws = new WebSocket("wss://stream.cryptowat.ch/connect?apikey=" + cryptowatchApiKey); cryptowatch_ws.on('open', onopen); cryptowatch_ws.on('message', onmessage); cryptowatch_ws.on('close', onclose); + cryptowatch_ws.on('error', console.error); + function onopen() { cryptowatch_ws.send(JSON.stringify(subscriptionMsg)); } @@ -534,22 +551,23 @@ async function cryptowatchWsSetup(cryptowatch_market_ids) { const msg = JSON.parse(data); if (!msg.marketUpdate) return; - const market_id = "cryptowatch:" + msg.marketUpdate.market.marketId; + const marketId = "cryptowatch:" + msg.marketUpdate.market.marketId; let trades = msg.marketUpdate.tradesUpdate.trades; let price = trades[trades.length - 1].priceStr / 1; - PRICE_FEEDS[market_id] = price; - }; + PRICE_FEEDS[marketId] = price; + } function onclose () { - setTimeout(cryptowatchWsSetup, 5000, cryptowatch_market_ids); + setTimeout(cryptowatchWsSetup, 5000, cryptowatchMarketIds); } } -async function chainlinkSetup(chainlink_market_address) { - chainlink_market_address.forEach(async (address) => { +async function chainlinkSetup(chainlinkMarketAddress) { + chainlinkMarketAddress.forEach(async (address) => { try { + const aggregatorV3InterfaceABI = JSON.parse(fs.readFileSync('chainlinkV3InterfaceABI.abi')); const provider = new ethers.Contract(address, aggregatorV3InterfaceABI, ethersProvider); const decimals = await provider.decimals(); - chainlinkProviders['chainlink:'+address] = [provider, decimals]; + CHAINLINK_PROVIDERS['chainlink:'+address] = [provider, decimals]; // get inital price const response = await provider.latestRoundData(); @@ -562,61 +580,75 @@ async function chainlinkSetup(chainlink_market_address) { } async function chainlinkUpdate() { - await Promise.all(Object.keys(chainlinkProviders).map(async (key) => { - const [provider, decimals] = chainlinkProviders[key]; + await Promise.all(Object.keys(CHAINLINK_PROVIDERS).map(async (key) => { + const [provider, decimals] = CHAINLINK_PROVIDERS[key]; const response = await provider.latestRoundData(); const price = parseFloat(response.answer) / 10**decimals; })); } const CLIENT_ID = (Math.random() * 100000).toString(16); -function indicateLiquidity (market_id) { - try { - validatePriceFeed(market_id); - } catch(e) { - console.error("Can not indicateLiquidity ("+market_id+") because: " + e); - return false; - } +function indicateLiquidity () { + for(const marketId in MM_CONFIG.pairs) { + const mmConfig = MM_CONFIG.pairs[marketId]; + if(!mmConfig || !mmConfig.active) continue; - const marketInfo = MARKETS[market_id]; - if (!marketInfo) return false; + try { + validatePriceFeed(marketId); + } catch(e) { + console.error("Can not indicateLiquidity ("+marketId+") because: " + e); + continue; + } - const mmConfig = MM_CONFIG.pairs[market_id]; - const midPrice = getMidPrice(market_id); - if (!midPrice) return false; + const marketInfo = MARKETS[marketId]; + if (!marketInfo) continue; - const expires = (Date.now() / 1000 | 0) + 10; // 10s expiry - const side = mmConfig.side || 'd'; + const midPrice = getMidPrice(marketId); + if (!midPrice) continue; - let maxBaseBalance = 0, maxQuoteBalance = 0; - Object.keys(WALLETS).forEach(accountId => { - const walletBase = WALLETS[accountId]['account_state'].committed.balances[marketInfo.baseAsset.symbol]; - const walletQuote = WALLETS[accountId]['account_state'].committed.balances[marketInfo.quoteAsset.symbol]; - if (Number(walletBase) > maxBaseBalance) { - maxBaseBalance = walletBase; - } - if (Number(walletQuote) > maxQuoteBalance) { - maxQuoteBalance = walletQuote; - } - }); - const baseBalance = maxBaseBalance / 10**marketInfo.baseAsset.decimals; - const quoteBalance = maxQuoteBalance / 10**marketInfo.quoteAsset.decimals; - const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); - const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize); - - const splits = 10; - const liquidity = []; - for (let i=1; i <= splits; i++) { - const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i/splits)); - const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i/splits)); - if ((['b','d']).includes(side)) { - liquidity.push(["b", buyPrice, maxBuySize / splits, expires]); + const expires = (Date.now() / 1000 | 0) + 10; // 10s expiry + const side = mmConfig.side || 'd'; + + let maxBaseBalance = 0, maxQuoteBalance = 0; + Object.keys(WALLETS).forEach(accountId => { + const walletBase = WALLETS[accountId]['account_state'].committed.balances[marketInfo.baseAsset.symbol]; + const walletQuote = WALLETS[accountId]['account_state'].committed.balances[marketInfo.quoteAsset.symbol]; + if (Number(walletBase) > maxBaseBalance) { + maxBaseBalance = walletBase; + } + if (Number(walletQuote) > maxQuoteBalance) { + maxQuoteBalance = walletQuote; + } + }); + const baseBalance = maxBaseBalance / 10**marketInfo.baseAsset.decimals; + const quoteBalance = maxQuoteBalance / 10**marketInfo.quoteAsset.decimals; + const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); + const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize); + + const splits = 10; + const liquidity = []; + for (let i=1; i <= splits; i++) { + const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i/splits)); + const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i/splits)); + if ((['b','d']).includes(side)) { + liquidity.push(["b", buyPrice, maxBuySize / splits, expires]); + } + if ((['s','d']).includes(side)) { + liquidity.push(["s", sellPrice, maxSellSize / splits, expires]); + } } - if ((['s','d']).includes(side)) { - liquidity.push(["s", sellPrice, maxSellSize / splits, expires]); + const msg = { op: "indicateliq2", args: [CHAIN_ID, marketId, liquidity, CLIENT_ID] }; + try { + zigzagws.send(JSON.stringify(msg)); + } catch (e) { + console.error("Could not send liquidity"); + console.error(e); } } - const msg = { op: "indicateliq2", args: [CHAIN_ID, market_id, liquidity, CLIENT_ID] }; +} + +function cancelLiquidity (chainId, marketId) { + const msg = { op: "indicateliq2", args: [chainId, marketId, [], CLIENT_ID] }; try { zigzagws.send(JSON.stringify(msg)); } catch (e) { @@ -625,8 +657,8 @@ function indicateLiquidity (market_id) { } } -function getMidPrice (market_id) { - const mmConfig = MM_CONFIG.pairs[market_id]; +function getMidPrice (marketId) { + const mmConfig = MM_CONFIG.pairs[marketId]; const mode = mmConfig.mode || "pricefeed"; let midPrice; if (mode == "constant") { @@ -638,6 +670,31 @@ function getMidPrice (market_id) { return midPrice; } +function activatePair(marketId) { + const mmConfig = MM_CONFIG.pairs[marketId]; + if(!mmConfig) return; + mmConfig.active = true; + console.log(`Set ${marketId} active.`); +} + +function rememberOrder(chainId, orderId, market, price, fillOrder) { + const timestamp = Date.now() / 1000; + for (const [key, value] of Object.entries(PAST_ORDER_LIST)) { + if (value['expiry'] < timestamp) { + delete PAST_ORDER_LIST[key]; + } + } + + const expiry = timestamp + 900; + PAST_ORDER_LIST[orderId] = { + 'chainId': chainId, + 'market': market, + 'price': price, + 'fillOrder': fillOrder, + 'expiry':expiry + }; +} + async function updateAccountState() { try { Object.keys(WALLETS).forEach(accountId => { @@ -646,7 +703,7 @@ async function updateAccountState() { }) }); } catch(err) { - // pass + // pass } } @@ -659,10 +716,10 @@ async function logBalance() { const committedBalaces = WALLETS[accountId]['account_state'].committed.balances; Object.keys(committedBalaces).forEach(token => { if(balance[token]) { - balance[token] = balance[token] + parseInt(committedBalaces[token]); - } else { - balance[token] = parseInt(committedBalaces[token]); - } + balance[token] = balance[token] + parseInt(committedBalaces[token]); + } else { + balance[token] = parseInt(committedBalaces[token]); + } }); }); // get token price and total in USD @@ -678,6 +735,6 @@ async function logBalance() { const content = date + ";" + sum.toFixed(2) + "\n"; fs.writeFile('price_csv.txt', content, { flag: 'a+' }, err => {}); } catch(err) { - // pass + // pass } } From ce1222632571540bdfcaff910437c1ca6583a279 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 16 Feb 2022 15:18:23 +0100 Subject: [PATCH 054/160] send fill request first rememberOrder might add a small delay --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 1b9bc1b..fb46dcb 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -373,9 +373,9 @@ async function sendFillRequest(orderreceipt, accountId) { // Set wallet flag WALLETS[accountId]['ORDER_BROADCASTING'] = true; - rememberOrder(chainId, orderId, marketId, quote.quotePrice, fillOrder); const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] }; zigzagws.send(JSON.stringify(resp)); + rememberOrder(chainId, orderId, marketId, quote.quotePrice, fillOrder); } async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { From e6bd9b01c128182600afd1c9c1e63cd10234e1ad Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 15 Feb 2022 12:40:32 +0100 Subject: [PATCH 055/160] Move ABI in file --- marketmaker.js | 1 - 1 file changed, 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index fb46dcb..5d3bae5 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -17,7 +17,6 @@ const MARKETS = {}; const CHAINLINK_PROVIDERS = {}; const PAST_ORDER_LIST = {}; - // Load MM config let MM_CONFIG; if (process.env.MM_CONFIG) { From d1f78b47516619db73500589818591d0f3c6102f Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 16 Feb 2022 21:29:57 +0100 Subject: [PATCH 056/160] move fillOrderDelay in afterFill function --- marketmaker.js | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 5d3bae5..3258dfe 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -411,22 +411,16 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { console.timeEnd('receipt' + randInt); console.log("Swap broadcast result", {swap, receipt}); + let newStatus, error; if(success) { - const order = PAST_ORDER_LIST[orderId]; - if(order) { - const marketId = order.market; - const mmConfig = MM_CONFIG.pairs[marketId]; - if(mmConfig && mmConfig.delayAfterFill) { - mmConfig.active = false; - setTimeout(activatePair, mmConfig.delayAfterFill * 1000, marketId); - cancelLiquidity (chainId, marketId); - console.log(`Set ${marketId} passive for ${mmConfig.delayAfterFill} seconds.`); - } - } + afterFill(chainId, orderId); + newStatus = 'f'; + error = null; + } else { + newStatus = 'r'; + error = swap.error.toString(); } - const newStatus = success ? 'f' : 'r'; - const error = success ? null : swap.error.toString(); const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,newStatus,txHash,error]]]} zigzagws.send(JSON.stringify(orderCommitMsg)); } @@ -669,11 +663,23 @@ function getMidPrice (marketId) { return midPrice; } -function activatePair(marketId) { +async function afterFill(chainId, orderId) { + const order = PAST_ORDER_LIST[orderId]; + if(!order) { return; } + const marketId = order.market; const mmConfig = MM_CONFIG.pairs[marketId]; - if(!mmConfig) return; - mmConfig.active = true; - console.log(`Set ${marketId} active.`); + if(!mmConfig) { return; } + + if(mmConfig.delayAfterFill) { + mmConfig.active = false; + cancelLiquidity (chainId, marketId); + console.log(`Set ${marketId} passive for ${mmConfig.delayAfterFill} seconds.`); + setTimeout(function() { + mmConfig.active = true; + console.log(`Set ${marketId} active.`); + }, mmConfig.delayAfterFill * 1000); + } + } function rememberOrder(chainId, orderId, market, price, fillOrder) { From 633bd879e8927e8764bc725c4a37e1d485d7a1f4 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 16 Feb 2022 21:35:43 +0100 Subject: [PATCH 057/160] indicateLiquidity for specific pair --- marketmaker.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 3258dfe..5945876 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -581,9 +581,9 @@ async function chainlinkUpdate() { } const CLIENT_ID = (Math.random() * 100000).toString(16); -function indicateLiquidity () { - for(const marketId in MM_CONFIG.pairs) { - const mmConfig = MM_CONFIG.pairs[marketId]; +function indicateLiquidity (pairs = MM_CONFIG.pairs) { + for(const marketId in pairs) { + const mmConfig = pairs[marketId]; if(!mmConfig || !mmConfig.active) continue; try { From 81e017e05282b34cce917b5df5540e36019bea81 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 16 Feb 2022 21:41:42 +0100 Subject: [PATCH 058/160] increaseSpreadAfterFill pair option --- README.md | 22 ++++++++++++++++++++-- marketmaker.js | 12 ++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 23dd208..c850269 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,8 @@ A market can be set inactive by flipping the active switch to `false`. ## Pair Options +These pair options can be set for each pair individual. You can even use more then on option per pair (though they might cancel each other out). + ###### delayAfterFill The market maker will stop market making on the pair, after successfully filling an order. This can be used to wait out bigger price moves. Example, here a delay of **60 seconds** is used: @@ -131,11 +133,27 @@ Example, here a delay of **60 seconds** is used: "minSize": 0.0003, "minSpread": 0.0005, "active": true, - "delayAfterFill": 60 <- + "delayAfterFill": 60 <- This would pause the pair for 60 sec after a fill. } ``` - +###### increaseSpreadAfterFill +The market maker increases the spread by the set amount. After the time (**in seconds**) the spread will fall back to the old value. This can happen multiple times in case the mm fills again in the set time (e.g. 0.1 -> 0.2 -> 0.3). +Example: +``` +"ETH-USDC": { + "mode": "pricefeed", + "side": "b", + "priceFeedPrimary": "cryptowatch:6631", + "priceFeedSecondary": "cryptowatch:588", + "slippageRate": 1e-5, + "maxSize": 100, + "minSize": 0.0003, + "minSpread": 0.0005, + "active": true, + "increaseSpreadAfterFill": [0.1, 300] <- This would increase the minSpread by 0.1 per fill for 300 sec each. +} +``` ## Pair Setting Examples Stable-Stable constant price: diff --git a/marketmaker.js b/marketmaker.js index 5945876..b60a8d3 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -677,9 +677,21 @@ async function afterFill(chainId, orderId) { setTimeout(function() { mmConfig.active = true; console.log(`Set ${marketId} active.`); + indicateLiquidity([mmConfig]); }, mmConfig.delayAfterFill * 1000); } + if(mmConfig.increaseSpreadAfterFill) { + const [spread, time] = mmConfig.increaseSpreadAfterFill; + mmConfig.minSpread = mmConfig.minSpread + spread; + console.log(`Increased ${marketId} minSpread by ${spread}.`); + indicateLiquidity([mmConfig]); + setTimeout(function() { + mmConfig.minSpread = mmConfig.minSpread - spread; + console.log(`Decreased ${marketId} minSpread by ${spread}.`); + indicateLiquidity([mmConfig]); + }, time * 1000); + } } function rememberOrder(chainId, orderId, market, price, fillOrder) { From f73ff181f55417b84d00e8b8725d9d1b3c46d902 Mon Sep 17 00:00:00 2001 From: vixidev99 Date: Fri, 18 Feb 2022 15:38:43 +0530 Subject: [PATCH 059/160] quick bug fix --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index fb46dcb..e7d4f7b 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -498,7 +498,7 @@ async function setupPriceFeeds() { } }); } - if(chainlinkSetup.length) await chainlinkSetup(chainlink); + if(chainlink.length) await chainlinkSetup(chainlink); if(cryptowatch.length) await cryptowatchWsSetup(cryptowatch); console.log(PRICE_FEEDS); From cff2224d3d716d6ad8027a4b14cb91d53baf516e Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 20 Feb 2022 15:04:51 +0100 Subject: [PATCH 060/160] add increaseSizeAfterFill --- README.md | 20 ++++++++++++++++++++ marketmaker.js | 16 ++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c850269..aa5de3f 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,26 @@ Example: "increaseSpreadAfterFill": [0.1, 300] <- This would increase the minSpread by 0.1 per fill for 300 sec each. } ``` + +###### increaseSizeAfterFill +The market maker increases the size (**in base token**) by the set amount. After the time (**in seconds**) the size will fall back to the old value. This can happen multiple times in case the mm fills again in the set time (e.g. 0.1 -> 0.2 -> 0.3). +Example: +``` +"ETH-USDC": { + "mode": "pricefeed", + "side": "b", + "priceFeedPrimary": "cryptowatch:6631", + "priceFeedSecondary": "cryptowatch:588", + "slippageRate": 1e-5, + "maxSize": 100, + "minSize": 0.0003, + "minSpread": 0.0005, + "active": true, + "increaseSizeAfterFill": [0.05, 300] <- This would increase the maxSize by 0.05 ETH (base token) per fill for 300 sec each. +} +``` + + ## Pair Setting Examples Stable-Stable constant price: diff --git a/marketmaker.js b/marketmaker.js index b60a8d3..51db4b3 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -674,7 +674,7 @@ async function afterFill(chainId, orderId) { mmConfig.active = false; cancelLiquidity (chainId, marketId); console.log(`Set ${marketId} passive for ${mmConfig.delayAfterFill} seconds.`); - setTimeout(function() { + setTimeout(() => { mmConfig.active = true; console.log(`Set ${marketId} active.`); indicateLiquidity([mmConfig]); @@ -686,12 +686,24 @@ async function afterFill(chainId, orderId) { mmConfig.minSpread = mmConfig.minSpread + spread; console.log(`Increased ${marketId} minSpread by ${spread}.`); indicateLiquidity([mmConfig]); - setTimeout(function() { + setTimeout(() => { mmConfig.minSpread = mmConfig.minSpread - spread; console.log(`Decreased ${marketId} minSpread by ${spread}.`); indicateLiquidity([mmConfig]); }, time * 1000); } + + if(mmConfig.increaseSizeAfterFill) { + const [size, time] = mmConfig.increaseSizeAfterFill; + mmConfig.maxSize = mmConfig.maxSize + size; + console.log(`Increased ${marketId} maxSize by ${size}.`); + indicateLiquidity([mmConfig]); + setTimeout(() => { + mmConfig.maxSize = mmConfig.maxSize - size; + console.log(`Decreased ${marketId} minSpread by ${size}.`); + indicateLiquidity([mmConfig]); + }, time * 1000); + } } function rememberOrder(chainId, orderId, market, price, fillOrder) { From e224ebdabadb14326838631c438c60374c5b5749 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 20 Feb 2022 15:09:31 +0100 Subject: [PATCH 061/160] make change in 2 way possible SpreadAfterFill & SizeAfterFill can reduce and increase the size/spread --- README.md | 12 ++++++------ marketmaker.js | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index aa5de3f..4437149 100644 --- a/README.md +++ b/README.md @@ -137,8 +137,8 @@ Example, here a delay of **60 seconds** is used: } ``` -###### increaseSpreadAfterFill -The market maker increases the spread by the set amount. After the time (**in seconds**) the spread will fall back to the old value. This can happen multiple times in case the mm fills again in the set time (e.g. 0.1 -> 0.2 -> 0.3). +###### changeSpreadAfterFill +The market maker increases the spread by the set amount. After the time (**in seconds**) the spread will fall back to the old value. This can happen multiple times in case the mm fills again in the set time (e.g. 0.1 -> 0.2 -> 0.3). You can set a value below 0 to reduce spread after fill (like: [-0.1, 300]). Example: ``` "ETH-USDC": { @@ -151,12 +151,12 @@ Example: "minSize": 0.0003, "minSpread": 0.0005, "active": true, - "increaseSpreadAfterFill": [0.1, 300] <- This would increase the minSpread by 0.1 per fill for 300 sec each. + "changeSpreadAfterFill": [0.1, 300] <- This would increase the minSpread by 0.1 per fill for 300 sec each. } ``` -###### increaseSizeAfterFill -The market maker increases the size (**in base token**) by the set amount. After the time (**in seconds**) the size will fall back to the old value. This can happen multiple times in case the mm fills again in the set time (e.g. 0.1 -> 0.2 -> 0.3). +###### changeSizeAfterFill +The market maker increases the size (**in base token**) by the set amount. After the time (**in seconds**) the size will fall back to the old value. This can happen multiple times in case the mm fills again in the set time (e.g. 0.1 -> 0.2 -> 0.3). You can set a value below 0 to reduce size after fill (like: [-0.1, 300]). Example: ``` "ETH-USDC": { @@ -169,7 +169,7 @@ Example: "minSize": 0.0003, "minSpread": 0.0005, "active": true, - "increaseSizeAfterFill": [0.05, 300] <- This would increase the maxSize by 0.05 ETH (base token) per fill for 300 sec each. + "changeSizeAfterFill": [0.05, 300] <- This would increase the maxSize by 0.05 ETH (base token) per fill for 300 sec each. } ``` diff --git a/marketmaker.js b/marketmaker.js index 51db4b3..e72a306 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -681,26 +681,26 @@ async function afterFill(chainId, orderId) { }, mmConfig.delayAfterFill * 1000); } - if(mmConfig.increaseSpreadAfterFill) { - const [spread, time] = mmConfig.increaseSpreadAfterFill; + if(mmConfig.changeSpreadAfterFill) { + const [spread, time] = mmConfig.changeSpreadAfterFill; mmConfig.minSpread = mmConfig.minSpread + spread; - console.log(`Increased ${marketId} minSpread by ${spread}.`); + console.log(`Changed ${marketId} minSpread by ${spread}.`); indicateLiquidity([mmConfig]); setTimeout(() => { mmConfig.minSpread = mmConfig.minSpread - spread; - console.log(`Decreased ${marketId} minSpread by ${spread}.`); + console.log(`Changed ${marketId} minSpread by ${(spread * (-1))}.`); indicateLiquidity([mmConfig]); }, time * 1000); } - if(mmConfig.increaseSizeAfterFill) { - const [size, time] = mmConfig.increaseSizeAfterFill; + if(mmConfig.changeSizeAfterFill) { + const [size, time] = mmConfig.changeSizeAfterFill; mmConfig.maxSize = mmConfig.maxSize + size; - console.log(`Increased ${marketId} maxSize by ${size}.`); + console.log(`Changed ${marketId} maxSize by ${size}.`); indicateLiquidity([mmConfig]); setTimeout(() => { mmConfig.maxSize = mmConfig.maxSize - size; - console.log(`Decreased ${marketId} minSpread by ${size}.`); + console.log(`Changed ${marketId} minSpread by ${(size* (-1))}.`); indicateLiquidity([mmConfig]); }, time * 1000); } From 8bdb4052d8cb19494cce385a0dc97f8fd4d53ace Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 20 Feb 2022 15:40:26 +0100 Subject: [PATCH 062/160] rememberOrder more swap infos - save Token symbol - save quantity --- marketmaker.js | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index e72a306..f8f2a45 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -337,16 +337,20 @@ async function sendFillRequest(orderreceipt, accountId) { const baseQuantity = orderreceipt[5]; const quoteQuantity = orderreceipt[6]; const quote = genQuote(chainId, marketId, side, baseQuantity); - let tokenSell, tokenBuy, sellQuantity, buyQuantity; + let tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol; if (side === "b") { tokenSell = market.baseAssetId; tokenBuy = market.quoteAssetId; + sellSymbol = market.baseAsset.symbol; + buySymbol = market.quoteAsset.symbol; // Add 1 bip to to protect against rounding errors sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals); buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); } else if (side === "s") { tokenSell = market.quoteAssetId; tokenBuy = market.baseAssetId; + sellSymbol = market.quoteAsset.symbol; + buySymbol = market.baseAsset.symbol; // Add 1 bip to to protect against rounding errors sellQuantity = (quote.quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals); buyQuantity = (baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); @@ -374,7 +378,15 @@ async function sendFillRequest(orderreceipt, accountId) { const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] }; zigzagws.send(JSON.stringify(resp)); - rememberOrder(chainId, orderId, marketId, quote.quotePrice, fillOrder); + rememberOrder(chainId, + marketId, + orderId, + quote.quotePrice, + sellSymbol, + sellQuantity, + buySymbol, + buyQuantity + ); } async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { @@ -666,7 +678,7 @@ function getMidPrice (marketId) { async function afterFill(chainId, orderId) { const order = PAST_ORDER_LIST[orderId]; if(!order) { return; } - const marketId = order.market; + const marketId = order.marketId; const mmConfig = MM_CONFIG.pairs[marketId]; if(!mmConfig) { return; } @@ -706,7 +718,7 @@ async function afterFill(chainId, orderId) { } } -function rememberOrder(chainId, orderId, market, price, fillOrder) { +function rememberOrder(chainId, marketId, orderId, price, sellSymbol, sellQuantity, buySymbol, buyQuantity) { const timestamp = Date.now() / 1000; for (const [key, value] of Object.entries(PAST_ORDER_LIST)) { if (value['expiry'] < timestamp) { @@ -717,9 +729,12 @@ function rememberOrder(chainId, orderId, market, price, fillOrder) { const expiry = timestamp + 900; PAST_ORDER_LIST[orderId] = { 'chainId': chainId, - 'market': market, + 'marketId': marketId, 'price': price, - 'fillOrder': fillOrder, + 'sellSymbol': sellSymbol, + 'sellQuantity': sellQuantity, + 'buySymbol': buySymbol, + 'buyQuantity': buyQuantity, 'expiry':expiry }; } From c11b456af90dd0593b3bf21fc13aecc890b08baa Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 20 Feb 2022 16:11:24 +0100 Subject: [PATCH 063/160] Update account state after fill also only fetch the state zkSync every 15 min --- marketmaker.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index f8f2a45..0e78577 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -95,7 +95,7 @@ try { } // Update account state loop -setInterval(updateAccountState, 30000); +setInterval(updateAccountState, 900000); // Log mm balance over all accounts logBalance(); @@ -425,7 +425,7 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { let newStatus, error; if(success) { - afterFill(chainId, orderId); + afterFill(chainId, orderId, wallet); newStatus = 'f'; error = null; } else { @@ -675,13 +675,26 @@ function getMidPrice (marketId) { return midPrice; } -async function afterFill(chainId, orderId) { +async function afterFill(chainId, orderId, wallet) { const order = PAST_ORDER_LIST[orderId]; if(!order) { return; } const marketId = order.marketId; const mmConfig = MM_CONFIG.pairs[marketId]; if(!mmConfig) { return; } + // update account state from order + const account_state = wallet['account_state'].committed.balances; + const buyTokenParsed = syncProvider.tokenSet.parseToken ( + order.buySymbol, + order.buyQuantity + ); + const sellTokenParsed = syncProvider.tokenSet.parseToken ( + order.sellSymbol, + order.sellQuantity + ); + account_state[order.buySymbol] = account_state[order.buySymbol] + buyTokenParsed; + account_state[order.sellSymbol] = account_state[order.sellSymbol] - sellTokenParsed; + if(mmConfig.delayAfterFill) { mmConfig.active = false; cancelLiquidity (chainId, marketId); From e1146b0004949e3dc887fb9680f56e30465af922 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 20 Feb 2022 16:15:45 +0100 Subject: [PATCH 064/160] spacing setupPriceFeeds --- marketmaker.js | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 0e78577..acd7c18 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -484,22 +484,23 @@ async function processFillQueue() { async function setupPriceFeeds() { const cryptowatch = [], chainlink = []; for (let market in MM_CONFIG.pairs) { - if(!MM_CONFIG.pairs[market].active) { continue; } - const primaryPriceFeed = MM_CONFIG.pairs[market].priceFeedPrimary; - const secondaryPriceFeed = MM_CONFIG.pairs[market].priceFeedSecondary; - [primaryPriceFeed, secondaryPriceFeed].forEach(priceFeed => { - if(!priceFeed) { return; } - const [provider, id] = priceFeed.split(':'); - switch(provider) { - case 'cryptowatch': - if(!cryptowatch.includes(id)) { cryptowatch.push(id); } - break; - case 'chainlink': - if(!chainlink.includes(id)) { chainlink.push(id); } - break; - default: - throw new Error("Price feed provider "+provider+" is not available.") - break; + if(!MM_CONFIG.pairs[market].active) { continue; } + const primaryPriceFeed = MM_CONFIG.pairs[market].priceFeedPrimary; + const secondaryPriceFeed = MM_CONFIG.pairs[market].priceFeedSecondary; + [primaryPriceFeed, secondaryPriceFeed].forEach(priceFeed => { + if(!priceFeed) { return; } + const [provider, id] = priceFeed.split(':'); + switch(provider) { + case 'cryptowatch': + if(!cryptowatch.includes(id)) { cryptowatch.push(id); } + break; + case 'chainlink': + if(!chainlink.includes(id)) { chainlink.push(id); } + break; + + default: + throw new Error("Price feed provider "+provider+" is not available.") + break; } }); } From fe7228d601cc1ebce77930b504b1437cfcc5b6a2 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 20 Feb 2022 16:25:33 +0100 Subject: [PATCH 065/160] use mmConfig --- marketmaker.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index acd7c18..3722c03 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -294,10 +294,10 @@ function genQuote(chainId, marketId, side, baseQuantity) { function validatePriceFeed(marketId) { const mmConfig = MM_CONFIG.pairs[marketId]; - const mode = MM_CONFIG.pairs[marketId].mode || "pricefeed"; - const initPrice = MM_CONFIG.pairs[marketId].initPrice; - const primaryPriceFeedId = MM_CONFIG.pairs[marketId].priceFeedPrimary; - const secondaryPriceFeedId = MM_CONFIG.pairs[marketId].priceFeedSecondary; + const mode = mmConfig.mode || "pricefeed"; + const initPrice = mmConfig.initPrice; + const primaryPriceFeedId = mmConfig.priceFeedPrimary; + const secondaryPriceFeedId = mmConfig.priceFeedSecondary; // Constant mode checks if (mode === "constant") { From a7f8b085e3f25c5a3ec1024a68a16fb8a368787e Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 20 Feb 2022 17:03:29 +0100 Subject: [PATCH 066/160] enable constant mode in priceFeed can be also used as secondaryPriceFeed --- marketmaker.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 3722c03..ec4c0e4 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -490,6 +490,7 @@ async function setupPriceFeeds() { [primaryPriceFeed, secondaryPriceFeed].forEach(priceFeed => { if(!priceFeed) { return; } const [provider, id] = priceFeed.split(':'); + provider = provider.toLowerCase(); switch(provider) { case 'cryptowatch': if(!cryptowatch.includes(id)) { cryptowatch.push(id); } @@ -497,7 +498,9 @@ async function setupPriceFeeds() { case 'chainlink': if(!chainlink.includes(id)) { chainlink.push(id); } break; - + case 'constant': + PRICE_FEEDS['constant:'+id] = parseFloat(id); + break; default: throw new Error("Price feed provider "+provider+" is not available.") break; From 7797373ff89496ac634bb7d9278192c77f154e80 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 20 Feb 2022 17:12:44 +0100 Subject: [PATCH 067/160] remove getMidPrice Switch to use PRICE_FEEDS for constant mode as well. Can be set in the old way or "priceFeedPrimary": "constant:1" in pair config. --- README.md | 110 ++++++++++++++++++++++++------------------------- marketmaker.js | 40 +++++++++--------- 2 files changed, 74 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 4437149..656bfc7 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,56 @@ Set your `eth_privkey` to be able to relay transactions. The ETH address with th Currently zkSync needs around 5 seconds to process a single swap and generate the receipt. So there is a upper limit of 12 swaps per wallet per minute. To circumvent this, there is also the option to use the `eth_privkeys` array. Here you can add any number of private keys. Each should be loaded up with adequate funds for market making. The founds will be handled separately, therefor each additional wallet has the opportunity to process (at least) 12 more swaps per minute. -###### External price feed -You have two options to get a price feed for your market maker. It is not recommended to use different price feeds sources as primary and secondary feed, as those might have some difference and could stop the market maker. +To run the marketmaker: + +```bash +node marketmaker.js +``` + +## Settings + +You can add, remove, and configure pair settings in the `pairs` section. A pair setting looks like this: + +``` +"ETH-USDC": { + "mode": "pricefeed", + "side": "d", + "initPrice": null, + "priceFeedPrimary": "cryptowatch:6631", + "priceFeedSecondary": "cryptowatch:588", + "slippageRate": 1e-5, + "maxSize": 100, + "minSize": 0.0003, + "minSpread": 0.0005, + "active": true +} +``` + +A market can be set inactive by flipping the active switch to `false`. + +The `side` setting can be toggled for single-sided liquidity. By default, the side setting is set to `d`, which stands for double-sided liquidity. To toggle single-sided liquidity, the value can be set to `b` or `s` for buy-side only or sell-side only. + +The slippage rate is the rate at which the spread increases as the base unit increases. For the example above, the spread goes up by 1e-5 for every 1 ETH in size added to an order. That's the equivalent of 0.1 bps / ETH in slippage. + +Orders coming in below the `minSpread` from the price feed will not be filled. The spread is calculated as a decimal value. 0.01 is 1%, and 0.0002 is 2 basis points (bps). + + +#### Price Feed + +There are 3 modes available with a 4th on the way. + +* `cryptowatch`: Follows an external price oracle. +* `chainlink` : Follows an external price oracle. Chainlink is WEB3 and might be slower then cryptowatch. +* `constant`: Sets an fixed price and market makes around that price. Can be combined with single-sided liquidity to simulate limit orders. +* `independent`: Under development. The price is set independent of a price feed. **Warning:** Make sure your price feed is close to the price you see on zigzag. **Otherwise, your mm can lose money!** -**Cryptowatch** +For all modes the `slippageRate`, `maxSize`, `minSize`, `minSpread`, and `active` settings are mandatory. + +The primary price feed is the price feed used to determine the bids and asks of the market maker. The secondary price feed is used to validate the first price feed and make sure the market isn't returning bad data. If the primary and secondary price feeds vary by more than 3%, the market maker will not fill orders. + +###### Cryptowatch You need a Cryptowatch API key to use the market maker. Once you obtain one, you can set the `cryptowatchApiKey` field in `config.json`. And set it to your public key. You can use [this link](https://api.cryptowat.ch/markets) to download a JSON with all available market endpoints. Add those to you pair config as "cryptowatch:". @@ -37,16 +81,14 @@ You can use [this link](https://api.cryptowat.ch/markets) to download a JSON wit Example: ``` "ETH-USDC": { - "mode": "pricefeed", "side": "d", - "initPrice": null, "priceFeedPrimary": "cryptowatch:6631", "priceFeedSecondary": "cryptowatch:588", .... } ``` -**Chainlink** +###### Chainlink With chainlink you have access to price oracles via blockchain. The requests are read calls to a smart contract. The public ethers provider might be too slow for a higher number of pairs or at times of high demand. Therefore, it might be needed to have access to an Infura account (100000 Requests/Day for free). There you can get an endpoint for your market maker (like https://mainnet.infura.io/v3/...), You can add this with the `infuraUrl` field in `config.json`, like this: ``` .... @@ -58,63 +100,23 @@ With chainlink you have access to price oracles via blockchain. The requests are You can get the available market contracts [here.](https://docs.chain.link/docs/ethereum-addresses/)Add those to you pair config as "chainlink:
", like this: ``` "ETH-USDC": { - "mode": "pricefeed", "side": "d", - "initPrice": null, "priceFeedPrimary": "chainlink:0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", "priceFeedSecondary": null, .... } ``` +###### Constant +With constant mode, you can set a fixed price to market make. The bot will not change that price. Any secondary price feed will be ignored, if used as priceFeedPrimary. Also good as a `priceFeedSecondary` on stablecoins. - -To run the marketmaker: - -```bash -node marketmaker.js ``` - -## Settings - -You can add, remove, and configure pair settings in the `pairs` section. A pair setting looks like this: - -``` -"ETH-USDC": { - "mode": "pricefeed", +"DAI-USDC": { "side": "d", - "initPrice": null, - "priceFeedPrimary": "cryptowatch:6631", - "priceFeedSecondary": "cryptowatch:588", - "slippageRate": 1e-5, - "maxSize": 100, - "minSize": 0.0003, - "minSpread": 0.0005, - "active": true + "priceFeedPrimary": "constant:1", + "priceFeedSecondary": null, } ``` -There are 2 modes available with a 3rd on the way. - -* `pricefeed`: Follows an external price oracle and updates indicated bids and asks based on that. -* `constant`: Sets an `initPrice` and market makes around that price. Can be combined with single-sided liquidity to simulate limit orders. -* `independent`: Under development. The price is set independent of a price feed. - -For all modes the `slippageRate`, `maxSize`, `minSize`, `minSpread`, and `active` settings are mandatory. - -For `pricefeed` mode, the `priceFeedPrimary` is mandatory. - -For `independent` and `constant` mode, the `initPrice` is mandatory. - -The `side` setting can be toggled for single-sided liquidity. By default, the side setting is set to `d`, which stands for double-sided liquidity. To toggle single-sided liquidity, the value can be set to `b` or `s` for buy-side only or sell-side only. - -The primary price feed is the price feed used to determine the bids and asks of the market maker. The secondary price feed is used to validate the first price feed and make sure the market isn't returning bad data. If the primary and secondary price feeds vary by more than 1%, the market maker will not fill orders. - -The slippage rate is the rate at which the spread increases as the base unit increases. For the example above, the spread goes up by 1e-5 for every 1 ETH in size added to an order. That's the equivalent of 0.1 bps / ETH in slippage. - -Orders coming in below the `minSpread` from the price feed will not be filled. The spread is calculated as a decimal value. 0.01 is 1%, and 0.0002 is 2 basis points (bps). - -A market can be set inactive by flipping the active switch to `false`. - ## Pair Options These pair options can be set for each pair individual. You can even use more then on option per pair (though they might cancel each other out). @@ -180,8 +182,7 @@ Stable-Stable constant price: ``` "DAI-USDC": { - "mode": "constant", - "initPrice": 1, + "priceFeedPrimary": "constant:1", "slippageRate": 1e-9, "maxSize": 100000, "minSize": 1, @@ -210,9 +211,8 @@ Sell the rip: ``` "DYDX-USDC": { - "mode": "constant", + "priceFeedPrimary": "constant:20", "side": "s", - "initPrice": 20, "slippageRate": 1e-5, "maxSize": 1000, "minSize": 0.5, diff --git a/marketmaker.js b/marketmaker.js index ec4c0e4..a6c70c7 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -276,7 +276,7 @@ function genQuote(chainId, marketId, side, baseQuantity) { if (mmConfig.side !== 'd' && mmConfig.side === side) { throw new Error("badside"); } - const primaryPrice = getMidPrice(marketId); + const primaryPrice = PRICE_FEEDS[mmConfig.priceFeedPrimary]; if (!primaryPrice) throw new Error("badprice"); const SPREAD = mmConfig.minSpread + (baseQuantity * mmConfig.slippageRate); let quoteQuantity; @@ -294,14 +294,13 @@ function genQuote(chainId, marketId, side, baseQuantity) { function validatePriceFeed(marketId) { const mmConfig = MM_CONFIG.pairs[marketId]; - const mode = mmConfig.mode || "pricefeed"; - const initPrice = mmConfig.initPrice; const primaryPriceFeedId = mmConfig.priceFeedPrimary; const secondaryPriceFeedId = mmConfig.priceFeedSecondary; - // Constant mode checks + // Constant mode checks + const [mode, price] = primaryPriceFeedId.split(':'); if (mode === "constant") { - if (initPrice) return true; + if (price > 0) return true; else throw new Error("No initPrice available"); } @@ -321,6 +320,7 @@ function validatePriceFeed(marketId) { const percentDiff = Math.abs(primaryPrice - secondaryPrice) / primaryPrice; if (percentDiff > 0.03) { throw new Error("Circuit breaker triggered"); + console.error("Primary and secondary price feeds do not match!"); } return true; @@ -484,9 +484,19 @@ async function processFillQueue() { async function setupPriceFeeds() { const cryptowatch = [], chainlink = []; for (let market in MM_CONFIG.pairs) { - if(!MM_CONFIG.pairs[market].active) { continue; } - const primaryPriceFeed = MM_CONFIG.pairs[market].priceFeedPrimary; - const secondaryPriceFeed = MM_CONFIG.pairs[market].priceFeedSecondary; + const pairConfig = MM_CONFIG.pairs[market]; + if(!pairConfig.active) { continue; } + const primaryPriceFeed = pairConfig.priceFeedPrimary; + const secondaryPriceFeed = pairConfig.priceFeedSecondary; + + // This is needed to make the price feed backwards compatalbe with old constant mode: + // "DYDX-USDC": { + // "mode": "constant", + // "initPrice": 20, + if(pairConfig.mode == "constant") { + const initPrice = pairConfig.initPrice; + pairConfig['priceFeedPrimary'] = "constant:" + initPrice.toString(); + } [primaryPriceFeed, secondaryPriceFeed].forEach(priceFeed => { if(!priceFeed) { return; } const [provider, id] = priceFeed.split(':'); @@ -612,7 +622,7 @@ function indicateLiquidity (pairs = MM_CONFIG.pairs) { const marketInfo = MARKETS[marketId]; if (!marketInfo) continue; - const midPrice = getMidPrice(marketId); + const midPrice = PRICE_FEEDS[mmConfig.priceFeedPrimary]; if (!midPrice) continue; const expires = (Date.now() / 1000 | 0) + 10; // 10s expiry @@ -666,18 +676,6 @@ function cancelLiquidity (chainId, marketId) { } } -function getMidPrice (marketId) { - const mmConfig = MM_CONFIG.pairs[marketId]; - const mode = mmConfig.mode || "pricefeed"; - let midPrice; - if (mode == "constant") { - midPrice = mmConfig.initPrice; - } - else if (mode == "pricefeed") { - midPrice = PRICE_FEEDS[mmConfig.priceFeedPrimary]; - } - return midPrice; -} async function afterFill(chainId, orderId, wallet) { const order = PAST_ORDER_LIST[orderId]; From e6b880a81ef897724b21c4d32da67cfee91b508f Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 20 Feb 2022 22:22:22 +0100 Subject: [PATCH 068/160] fixes --- README.md | 6 +++--- marketmaker.js | 34 ++++++++++++++++++---------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 656bfc7..9c51279 100644 --- a/README.md +++ b/README.md @@ -139,8 +139,8 @@ Example, here a delay of **60 seconds** is used: } ``` -###### changeSpreadAfterFill -The market maker increases the spread by the set amount. After the time (**in seconds**) the spread will fall back to the old value. This can happen multiple times in case the mm fills again in the set time (e.g. 0.1 -> 0.2 -> 0.3). You can set a value below 0 to reduce spread after fill (like: [-0.1, 300]). +###### increaseSpreadAfterFill +The market maker increases the spread by the set amount. After the time (**in seconds**) the spread will fall back to the old value. This can happen multiple times in case the mm fills again in the set time (e.g. 0.1 -> 0.2 -> 0.3). Example: ``` "ETH-USDC": { @@ -153,7 +153,7 @@ Example: "minSize": 0.0003, "minSpread": 0.0005, "active": true, - "changeSpreadAfterFill": [0.1, 300] <- This would increase the minSpread by 0.1 per fill for 300 sec each. + "increaseSpreadAfterFill": [0.1, 300] <- This would increase the minSpread by 0.1 per fill for 300 sec each. } ``` diff --git a/marketmaker.js b/marketmaker.js index a6c70c7..5899a08 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -203,7 +203,7 @@ function isOrderFillable(order) { const marketId = order[2]; const market = MARKETS[marketId]; const mmConfig = MM_CONFIG.pairs[marketId]; - const mmSide = mmConfig.side || 'd'; + const mmSide = (mmConfig.side) ? mmConfig.side : 'd'; if (chainId != CHAIN_ID) return { fillable: false, reason: "badchain" } if (!market) return { fillable: false, reason: "badmarket" } if (!mmConfig.active) return { fillable: false, reason: "inactivemarket" } @@ -500,8 +500,7 @@ async function setupPriceFeeds() { [primaryPriceFeed, secondaryPriceFeed].forEach(priceFeed => { if(!priceFeed) { return; } const [provider, id] = priceFeed.split(':'); - provider = provider.toLowerCase(); - switch(provider) { + switch(provider.toLowerCase()) { case 'cryptowatch': if(!cryptowatch.includes(id)) { cryptowatch.push(id); } break; @@ -676,7 +675,6 @@ function cancelLiquidity (chainId, marketId) { } } - async function afterFill(chainId, orderId, wallet) { const order = PAST_ORDER_LIST[orderId]; if(!order) { return; } @@ -693,10 +691,14 @@ async function afterFill(chainId, orderId, wallet) { const sellTokenParsed = syncProvider.tokenSet.parseToken ( order.sellSymbol, order.sellQuantity - ); - account_state[order.buySymbol] = account_state[order.buySymbol] + buyTokenParsed; - account_state[order.sellSymbol] = account_state[order.sellSymbol] - sellTokenParsed; - + ); + const oldbuyTokenParsed = ethers.BigNumber.from(account_state[order.buySymbol]); + const oldsellTokenParsed = ethers.BigNumber.from(account_state[order.sellSymbol]); + account_state[order.buySymbol] = (oldbuyTokenParsed.add(buyTokenParsed)).toString(); + account_state[order.sellSymbol] = (oldsellTokenParsed.sub(sellTokenParsed)).toString(); + + const indicateMarket = {}; + indicateMarket[marketId] = mmConfig; if(mmConfig.delayAfterFill) { mmConfig.active = false; cancelLiquidity (chainId, marketId); @@ -704,19 +706,19 @@ async function afterFill(chainId, orderId, wallet) { setTimeout(() => { mmConfig.active = true; console.log(`Set ${marketId} active.`); - indicateLiquidity([mmConfig]); + indicateLiquidity(indicateMarket); }, mmConfig.delayAfterFill * 1000); } - if(mmConfig.changeSpreadAfterFill) { - const [spread, time] = mmConfig.changeSpreadAfterFill; + if(mmConfig.increaseSpreadAfterFill) { + const [spread, time] = mmConfig.increaseSpreadAfterFill; mmConfig.minSpread = mmConfig.minSpread + spread; console.log(`Changed ${marketId} minSpread by ${spread}.`); - indicateLiquidity([mmConfig]); + indicateLiquidity(indicateMarket); setTimeout(() => { mmConfig.minSpread = mmConfig.minSpread - spread; - console.log(`Changed ${marketId} minSpread by ${(spread * (-1))}.`); - indicateLiquidity([mmConfig]); + console.log(`Changed ${marketId} minSpread by -${spread}.`); + indicateLiquidity(indicateMarket); }, time * 1000); } @@ -724,11 +726,11 @@ async function afterFill(chainId, orderId, wallet) { const [size, time] = mmConfig.changeSizeAfterFill; mmConfig.maxSize = mmConfig.maxSize + size; console.log(`Changed ${marketId} maxSize by ${size}.`); - indicateLiquidity([mmConfig]); + indicateLiquidity(indicateMarket); setTimeout(() => { mmConfig.maxSize = mmConfig.maxSize - size; console.log(`Changed ${marketId} minSpread by ${(size* (-1))}.`); - indicateLiquidity([mmConfig]); + indicateLiquidity(indicateMarket); }, time * 1000); } } From 73a9aaa57d9fb30de33694354f18c8d7cefb635e Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Mon, 21 Feb 2022 19:48:25 +0100 Subject: [PATCH 069/160] update backend on nonce error send orderstatusupdate with 'r' and error about the failed nonce check. This prevents the backend from setting an timeout for the mm. --- marketmaker.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 5899a08..96252d5 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -394,7 +394,9 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { const nonce = swapOffer.nonce; const userNonce = NONCES[swapOffer.accountId]; if (nonce <= userNonce) { - throw new Error("badnonce"); + const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'r',null,"Order failed userNonce check."]]]} + zigzagws.send(JSON.stringify(orderCommitMsg)); + return; } const randInt = (Math.random()*1000).toFixed(0); console.time('syncswap' + randInt); From d947c31783bda627fe17332e82f725de4e2a16a2 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 22 Feb 2022 20:59:05 +0100 Subject: [PATCH 070/160] fix log --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 96252d5..76ee416 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -731,7 +731,7 @@ async function afterFill(chainId, orderId, wallet) { indicateLiquidity(indicateMarket); setTimeout(() => { mmConfig.maxSize = mmConfig.maxSize - size; - console.log(`Changed ${marketId} minSpread by ${(size* (-1))}.`); + console.log(`Changed ${marketId} maxSize by ${(size* (-1))}.`); indicateLiquidity(indicateMarket); }, time * 1000); } From b1d716543ab90a81285a8f827e14242be3e98620 Mon Sep 17 00:00:00 2001 From: Trooper <95502080+TrooperCrypto@users.noreply.github.com> Date: Wed, 23 Feb 2022 13:44:58 +0100 Subject: [PATCH 071/160] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9c51279..4289e3e 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ With constant mode, you can set a fixed price to market make. The bot will not c "side": "d", "priceFeedPrimary": "constant:1", "priceFeedSecondary": null, + ... } ``` From 6527a04e71844c8253364013a7198e9eda1e2523 Mon Sep 17 00:00:00 2001 From: Trooper <95502080+TrooperCrypto@users.noreply.github.com> Date: Wed, 23 Feb 2022 13:45:40 +0100 Subject: [PATCH 072/160] Update README.md --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4289e3e..6d9fc46 100644 --- a/README.md +++ b/README.md @@ -91,11 +91,12 @@ Example: ###### Chainlink With chainlink you have access to price oracles via blockchain. The requests are read calls to a smart contract. The public ethers provider might be too slow for a higher number of pairs or at times of high demand. Therefore, it might be needed to have access to an Infura account (100000 Requests/Day for free). There you can get an endpoint for your market maker (like https://mainnet.infura.io/v3/...), You can add this with the `infuraUrl` field in `config.json`, like this: ``` -.... -"infuraUrl": "https://mainnet.infura.io/v3/xxxxxxxx", -"zigzagChainId": 1, -"zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com", -.... +"ETH-USDC": { + "infuraUrl": "https://mainnet.infura.io/v3/xxxxxxxx", + "zigzagChainId": 1, + "zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com", + .... +} ``` You can get the available market contracts [here.](https://docs.chain.link/docs/ethereum-addresses/)Add those to you pair config as "chainlink:
", like this: ``` @@ -114,7 +115,7 @@ With constant mode, you can set a fixed price to market make. The bot will not c "side": "d", "priceFeedPrimary": "constant:1", "priceFeedSecondary": null, - ... + .... } ``` From ab0e496004a0978f5d4b8bca8ffbef077f2afd24 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 23 Feb 2022 13:54:41 +0100 Subject: [PATCH 073/160] small bugfix --- marketmaker.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 51b8e44..0a50ca4 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -488,9 +488,6 @@ async function setupPriceFeeds() { for (let market in MM_CONFIG.pairs) { const pairConfig = MM_CONFIG.pairs[market]; if(!pairConfig.active) { continue; } - const primaryPriceFeed = pairConfig.priceFeedPrimary; - const secondaryPriceFeed = pairConfig.priceFeedSecondary; - // This is needed to make the price feed backwards compatalbe with old constant mode: // "DYDX-USDC": { // "mode": "constant", @@ -499,6 +496,8 @@ async function setupPriceFeeds() { const initPrice = pairConfig.initPrice; pairConfig['priceFeedPrimary'] = "constant:" + initPrice.toString(); } + const primaryPriceFeed = pairConfig.priceFeedPrimary; + const secondaryPriceFeed = pairConfig.priceFeedSecondary; [primaryPriceFeed, secondaryPriceFeed].forEach(priceFeed => { if(!priceFeed) { return; } const [provider, id] = priceFeed.split(':'); From a7614f7cb01bb8dbd34998a21c4b863580c7f8bd Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Mon, 28 Feb 2022 01:05:43 +0100 Subject: [PATCH 074/160] CLIENT_ID is handled by backend now --- marketmaker.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 0a50ca4..56cb96f 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -606,7 +606,6 @@ async function chainlinkUpdate() { })); } -const CLIENT_ID = (Math.random() * 100000).toString(16); function indicateLiquidity (pairs = MM_CONFIG.pairs) { for(const marketId in pairs) { const mmConfig = pairs[marketId]; @@ -656,7 +655,7 @@ function indicateLiquidity (pairs = MM_CONFIG.pairs) { liquidity.push(["s", sellPrice, maxSellSize / splits, expires]); } } - const msg = { op: "indicateliq2", args: [CHAIN_ID, marketId, liquidity, CLIENT_ID] }; + const msg = { op: "indicateliq2", args: [CHAIN_ID, marketId, liquidity] }; try { zigzagws.send(JSON.stringify(msg)); } catch (e) { @@ -667,7 +666,7 @@ function indicateLiquidity (pairs = MM_CONFIG.pairs) { } function cancelLiquidity (chainId, marketId) { - const msg = { op: "indicateliq2", args: [chainId, marketId, [], CLIENT_ID] }; + const msg = { op: "indicateliq2", args: [chainId, marketId, []] }; try { zigzagws.send(JSON.stringify(msg)); } catch (e) { From 1da3e31e29e87d8c3bd84d5df917a42824760df1 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 1 Mar 2022 00:29:28 +0100 Subject: [PATCH 075/160] use side here --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 0a50ca4..e5be7b8 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -273,7 +273,7 @@ function genQuote(chainId, marketId, side, baseQuantity) { const mmConfig = MM_CONFIG.pairs[marketId]; const mmSide = mmConfig.side || 'd'; - if (mmConfig.side !== 'd' && mmConfig.side === side) { + if (mmSide !== 'd' && mmSide === side) { throw new Error("badside"); } const primaryPrice = PRICE_FEEDS[mmConfig.priceFeedPrimary]; From 6d22e65d13fe4d83415e9b9f0c2d34f0dc9b56ce Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 1 Mar 2022 00:33:22 +0100 Subject: [PATCH 076/160] directly sendFillRequest remove FILL_QUEUE --- marketmaker.js | 35 ++--------------------------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index e5be7b8..490cc84 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -12,7 +12,6 @@ const PRICE_FEEDS = {}; const OPEN_ORDERS = {}; const NONCES = {}; const WALLETS = {}; -const FILL_QUEUE = []; const MARKETS = {}; const CHAINLINK_PROVIDERS = {}; const PAST_ORDER_LIST = {}; @@ -153,7 +152,7 @@ async function handleMessage(json) { const fillable = isOrderFillable(order); console.log(fillable); if (fillable.fillable) { - FILL_QUEUE.push({ order: order, wallets: fillable.wallets}); + sendFillRequest(order, fillable.wallets); } else if (fillable.reason === "badprice") { OPEN_ORDERS[orderId] = order; @@ -444,7 +443,7 @@ async function fillOpenOrders() { const order = OPEN_ORDERS[orderId]; const fillable = isOrderFillable(order); if (fillable.fillable) { - FILL_QUEUE.push({ order: order, wallets: fillable.wallets}); + sendFillRequest(order, fillable.wallets); delete OPEN_ORDERS[orderId]; } else if (fillable.reason !== "badprice") { @@ -453,36 +452,6 @@ async function fillOpenOrders() { } } -async function processFillQueue() { - if (FILL_QUEUE.length === 0) { - setTimeout(processFillQueue, 100); - return; - } - await Promise.all(Object.keys(WALLETS).map(async accountId => { - const wallet = WALLETS[accountId]; - if (wallet['ORDER_BROADCASTING']) { - return; - } - let index = 0; - for(;index Date: Tue, 1 Mar 2022 00:44:26 +0100 Subject: [PATCH 077/160] fillOpenOrders more often fillOpenOrders every 400ms to match 500ms in backend --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 490cc84..0d1c13b 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -111,7 +111,7 @@ zigzagws.on('error', console.error); function onWsOpen() { zigzagws.on('message', handleMessage); - fillOrdersInterval = setInterval(fillOpenOrders, 5000); + fillOrdersInterval = setInterval(fillOpenOrders, 400); indicateLiquidityInterval = setInterval(indicateLiquidity, 5000); for (let market in MM_CONFIG.pairs) { if (MM_CONFIG.pairs[market].active) { From 7ffda56b3ee77d48e7d41bdbcce6babb575c4295 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 1 Mar 2022 00:46:04 +0100 Subject: [PATCH 078/160] add broadcasting check to fillable - Send request directly after isOrderFillable -> check if ready - only return one wallet --- marketmaker.js | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 0d1c13b..6338e44 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -152,7 +152,7 @@ async function handleMessage(json) { const fillable = isOrderFillable(order); console.log(fillable); if (fillable.fillable) { - sendFillRequest(order, fillable.wallets); + sendFillRequest(order, fillable.wallet); } else if (fillable.reason === "badprice") { OPEN_ORDERS[orderId] = order; @@ -223,6 +223,21 @@ function isOrderFillable(order) { goodWallets.push(accountId); } }); + + if (goodWallets.length === 0) { + return { fillable: false, reason: "badbalance" }; + } + + goodWallets.forEach(accountId => { + if(WALLETS[accountId]['ORDER_BROADCASTING'] == true) { + goodWallets.delete(accountId); + } + }) + + if (goodWallets.length === 0) { + return { fillable: false, reason: "sending order already " }; + } + const now = Date.now() / 1000 | 0; if (now > expires) { @@ -240,10 +255,6 @@ function isOrderFillable(order) { return { fillable: false, reason: "badsize" }; } - if (goodWallets.length === 0) { - return { fillable: false, reason: "badbalance" }; - } - let quote; try { quote = genQuote(chainId, marketId, side, baseQuantity); @@ -258,7 +269,7 @@ function isOrderFillable(order) { return { fillable: false, reason: "badprice" }; } - return { fillable: true, reason: null, wallets: goodWallets}; + return { fillable: true, reason: null, wallet: goodWallets[0]}; } function genQuote(chainId, marketId, side, baseQuantity) { From 5f8065ae97a061f7ee94b401790f8ac9faa1aaa3 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 1 Mar 2022 01:06:57 +0100 Subject: [PATCH 079/160] only set flag to false for that wallet - fillrequest returns accountId --- marketmaker.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 6338e44..dff9214 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -141,9 +141,14 @@ async function handleMessage(json) { if (!(["lastprice", "liquidity2", "fillstatus", "marketinfo"]).includes(msg.op)) console.log(json.toString()); switch(msg.op) { case 'error': - Object.keys(WALLETS).forEach(accountId => { + const accountId = msg.args[1]; + if(msg.args[0] == 'fillrequest' && accountId) { WALLETS[accountId]['ORDER_BROADCASTING'] = false; - }); + } else { + Object.keys(WALLETS).forEach(accountId => { + WALLETS[accountId]['ORDER_BROADCASTING'] = false; + }); + } break; case 'orders': const orders = msg.args[0]; From 4431b40fc2321f3be44ad90caeb594bbcc189ec5 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 1 Mar 2022 11:33:27 +0100 Subject: [PATCH 080/160] 1000ms feels good --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index dff9214..f6f58a9 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -111,7 +111,7 @@ zigzagws.on('error', console.error); function onWsOpen() { zigzagws.on('message', handleMessage); - fillOrdersInterval = setInterval(fillOpenOrders, 400); + fillOrdersInterval = setInterval(fillOpenOrders, 1000); indicateLiquidityInterval = setInterval(indicateLiquidity, 5000); for (let market in MM_CONFIG.pairs) { if (MM_CONFIG.pairs[market].active) { From e36e1f86a88cdcef84db96368e4c5d4ce1f5a803 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 1 Mar 2022 11:38:15 +0100 Subject: [PATCH 081/160] switch to filter --- marketmaker.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index f6f58a9..ccb7115 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -233,11 +233,9 @@ function isOrderFillable(order) { return { fillable: false, reason: "badbalance" }; } - goodWallets.forEach(accountId => { - if(WALLETS[accountId]['ORDER_BROADCASTING'] == true) { - goodWallets.delete(accountId); - } - }) + goodWallets = goodWallets.filter(accountId => { + WALLETS[accountId]['ORDER_BROADCASTING'] == false; + }); if (goodWallets.length === 0) { return { fillable: false, reason: "sending order already " }; From f83684ee30f8886900e9a0c6bb448fd57f201727 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 1 Mar 2022 17:32:29 +0100 Subject: [PATCH 082/160] change node version in package --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 102c7ac..8fb851a 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,6 @@ "devDependencies": {}, "description": "", "engines": { - "node": "14.x" + "node": ">=16 <17" } } From c4e613b04c9150e7aa40931626c363e049fcc349 Mon Sep 17 00:00:00 2001 From: vixidev99 Date: Wed, 2 Mar 2022 14:10:56 +0530 Subject: [PATCH 083/160] use bid-ask instead of lastprice for spreads --- marketmaker.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index e7d4f7b..02ebc13 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -534,7 +534,7 @@ async function cryptowatchWsSetup(cryptowatchMarketIds) { subscriptionMsg.subscribe.subscriptions.push({ "streamSubscription": { - "resource": `markets:${cryptowatchMarketId}:trades` + "resource": `markets:${cryptowatchMarketId}:book:spread` } }) } @@ -552,8 +552,9 @@ async function cryptowatchWsSetup(cryptowatchMarketIds) { if (!msg.marketUpdate) return; const marketId = "cryptowatch:" + msg.marketUpdate.market.marketId; - let trades = msg.marketUpdate.tradesUpdate.trades; - let price = trades[trades.length - 1].priceStr / 1; + let ask = msg.marketUpdate.orderBookSpreadUpdate.ask.priceStr; + let bid = msg.marketUpdate.orderBookSpreadUpdate.bid.priceStr; + let price = ask / 2 + bid / 2; PRICE_FEEDS[marketId] = price; } function onclose () { From 2ffe12fa9997ba14dcf7a9908385e7e0cd658b50 Mon Sep 17 00:00:00 2001 From: vixidev99 Date: Wed, 2 Mar 2022 15:18:05 +0530 Subject: [PATCH 084/160] control how many orders to display on the UI --- README.md | 18 ++++++++++++++++++ marketmaker.js | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6d9fc46..d0c8504 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,24 @@ Example: } ``` +###### numOrdersIndicated +On the UI, when indicating liquidity, by default will indicate the liquidity in 10 separate orders spaced evenly apart. To change the number of orders indicated, you can use the `numOrdersIndicated` setting. + +Example: +``` +"ETH-USDC": { + "mode": "pricefeed", + "side": "b", + "priceFeedPrimary": "cryptowatch:6631", + "priceFeedSecondary": "cryptowatch:588", + "slippageRate": 1e-5, + "maxSize": 100, + "minSize": 0.0003, + "minSpread": 0.0005, + "active": true, + "numOrdersIndicated": 5 +} +``` ## Pair Setting Examples diff --git a/marketmaker.js b/marketmaker.js index 0c6cdea..ad7b4dd 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -645,7 +645,7 @@ function indicateLiquidity (pairs = MM_CONFIG.pairs) { const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize); - const splits = 10; + const splits = mmConfig.numOrdersIndicated || 10; const liquidity = []; for (let i=1; i <= splits; i++) { const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i/splits)); From 9cc1694cffd68a874605a7f602996b89162cee3d Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Fri, 4 Mar 2022 18:07:43 +0100 Subject: [PATCH 085/160] add base and quote to remember order --- marketmaker.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/marketmaker.js b/marketmaker.js index ad7b4dd..6ef155e 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -745,11 +745,23 @@ function rememberOrder(chainId, marketId, orderId, price, sellSymbol, sellQuanti } } + const [baseSymbol, quoteSymbol] = marketId.splice('-') + let baseQuantity, quoteQuantity; + if(sellSymbol === baseSymbol) { + baseQuantity = sellQuantity; + quoteQuantity = buyQuantity; + } else { + baseQuantity = buyQuantity; + quoteQuantity = sellQuantity; + } + const expiry = timestamp + 900; PAST_ORDER_LIST[orderId] = { 'chainId': chainId, 'marketId': marketId, 'price': price, + 'baseQuantity': baseQuantity, + 'quoteQuantity': quoteQuantity, 'sellSymbol': sellSymbol, 'sellQuantity': sellQuantity, 'buySymbol': buySymbol, From 4084a3a24fc0ee9eaf82f92360e49c40d27653ee Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Fri, 4 Mar 2022 18:07:59 +0100 Subject: [PATCH 086/160] add size to pair options --- README.md | 16 ++++++++++------ marketmaker.js | 51 +++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index d0c8504..456fc42 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,9 @@ With constant mode, you can set a fixed price to market make. The bot will not c These pair options can be set for each pair individual. You can even use more then on option per pair (though they might cancel each other out). ###### delayAfterFill -The market maker will stop market making on the pair, after successfully filling an order. This can be used to wait out bigger price moves. +The market maker will stop market making on the pair, after successfully filling an order. This can be used to wait out bigger price moves. +With the second parameter, you can set the minimum trade size (**in base quantity**) to activate the option. This parameter is optional and can be omitted (like: `[60]`) + Example, here a delay of **60 seconds** is used: ``` "ETH-USDC": { @@ -137,12 +139,13 @@ Example, here a delay of **60 seconds** is used: "minSize": 0.0003, "minSpread": 0.0005, "active": true, - "delayAfterFill": 60 <- This would pause the pair for 60 sec after a fill. + "delayAfterFill": [60, 0.5] <- This would pause the pair for 60 sec after a fill. } ``` ###### increaseSpreadAfterFill -The market maker increases the spread by the set amount. After the time (**in seconds**) the spread will fall back to the old value. This can happen multiple times in case the mm fills again in the set time (e.g. 0.1 -> 0.2 -> 0.3). +The market maker increases the spread by the set amount. After the time (**in seconds**) the spread will fall back to the old value. This can happen multiple times in case the mm fills again in the set time (e.g. 0.1 -> 0.2 -> 0.3). +With the third parameter, you can set the minimum trade size (**in base quantity**) to activate the option. This parameter is optional and can be omitted. Example: ``` "ETH-USDC": { @@ -155,12 +158,13 @@ Example: "minSize": 0.0003, "minSpread": 0.0005, "active": true, - "increaseSpreadAfterFill": [0.1, 300] <- This would increase the minSpread by 0.1 per fill for 300 sec each. + "increaseSpreadAfterFill": [0.1, 300, 0.5] <- This would increase the minSpread by 0.1 per fill for 300 sec each. } ``` ###### changeSizeAfterFill -The market maker increases the size (**in base token**) by the set amount. After the time (**in seconds**) the size will fall back to the old value. This can happen multiple times in case the mm fills again in the set time (e.g. 0.1 -> 0.2 -> 0.3). You can set a value below 0 to reduce size after fill (like: [-0.1, 300]). +The market maker increases the size (**in base token**) by the set amount. After the time (**in seconds**) the size will fall back to the old value. This can happen multiple times in case the mm fills again in the set time (e.g. 0.1 -> 0.2 -> 0.3). You can set a value below 0 to reduce size after fill (like: [-0.1, 300]). +With the third parameter, you can set the minimum trade size (**in base quantity**) to activate the option. This parameter is optional and can be omitted. Example: ``` "ETH-USDC": { @@ -173,7 +177,7 @@ Example: "minSize": 0.0003, "minSpread": 0.0005, "active": true, - "changeSizeAfterFill": [0.05, 300] <- This would increase the maxSize by 0.05 ETH (base token) per fill for 300 sec each. + "changeSizeAfterFill": [0.05, 300, 0.5] <- This would increase the maxSize by 0.05 ETH (base token) per fill for 300 sec each. } ``` diff --git a/marketmaker.js b/marketmaker.js index 6ef155e..9c51cd4 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -702,17 +702,39 @@ async function afterFill(chainId, orderId, wallet) { const indicateMarket = {}; indicateMarket[marketId] = mmConfig; if(mmConfig.delayAfterFill) { - mmConfig.active = false; - cancelLiquidity (chainId, marketId); - console.log(`Set ${marketId} passive for ${mmConfig.delayAfterFill} seconds.`); - setTimeout(() => { - mmConfig.active = true; - console.log(`Set ${marketId} active.`); - indicateLiquidity(indicateMarket); - }, mmConfig.delayAfterFill * 1000); - } + let delayAfterFillMinSize + if( + !Array.isArray(mmConfig.delayAfterFill) || + !mmConfig.delayAfterFill[1] + ) { + delayAfterFillMinSize = 0; + } else { + delayAfterFillMinSize = mmConfig.delayAfterFill[1] + } - if(mmConfig.increaseSpreadAfterFill) { + if(order.baseQuantity > delayAfterFillMinSize) { + // no array -> old config + // or array and buyQuantity over minSize + mmConfig.active = false; + cancelLiquidity (chainId, marketId); + console.log(`Set ${marketId} passive for ${mmConfig.delayAfterFill} seconds.`); + setTimeout(() => { + mmConfig.active = true; + console.log(`Set ${marketId} active.`); + indicateLiquidity(indicateMarket); + }, mmConfig.delayAfterFill * 1000); + } + } + + // increaseSpreadAfterFill size might not be set + const increaseSpreadAfterFillMinSize = (mmConfig.increaseSpreadAfterFill?.[2]) + ? mmConfig.increaseSpreadAfterFill[2] + : 0 + if( + mmConfig.increaseSpreadAfterFill && + order.baseQuantity > increaseSpreadAfterFillMinSize + + ) { const [spread, time] = mmConfig.increaseSpreadAfterFill; mmConfig.minSpread = mmConfig.minSpread + spread; console.log(`Changed ${marketId} minSpread by ${spread}.`); @@ -724,7 +746,14 @@ async function afterFill(chainId, orderId, wallet) { }, time * 1000); } - if(mmConfig.changeSizeAfterFill) { + // changeSizeAfterFill size might not be set + const changeSizeAfterFillMinSize = (mmConfig.changeSizeAfterFill?.[2]) + ? mmConfig.changeSizeAfterFill[2] + : 0 + if( + mmConfig.changeSizeAfterFill && + order.baseQuantity > changeSizeAfterFillMinSize + ) { const [size, time] = mmConfig.changeSizeAfterFill; mmConfig.maxSize = mmConfig.maxSize + size; console.log(`Changed ${marketId} maxSize by ${size}.`); From e278a7073081183b6d6f10b213a379be2430bae5 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Fri, 4 Mar 2022 18:17:33 +0100 Subject: [PATCH 087/160] fix splice -> split --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 9c51cd4..38a09c1 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -774,7 +774,7 @@ function rememberOrder(chainId, marketId, orderId, price, sellSymbol, sellQuanti } } - const [baseSymbol, quoteSymbol] = marketId.splice('-') + const [baseSymbol, quoteSymbol] = marketId.split('-') let baseQuantity, quoteQuantity; if(sellSymbol === baseSymbol) { baseQuantity = sellQuantity; From c3d4ec965eecc48ad8b0b16f8721ab4736159e41 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 6 Mar 2022 19:48:36 +0100 Subject: [PATCH 088/160] add ws err msg --- marketmaker.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/marketmaker.js b/marketmaker.js index a7c3758..8bf7eb6 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -172,6 +172,8 @@ async function handleMessage(json) { try { await broadcastFill(chainId, orderId, msg.args[2], fillOrder, wallet); } catch (e) { + const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'r',null,e]]]} + zigzagws.send(JSON.stringify(orderCommitMsg)); console.error(e); } wallet['ORDER_BROADCASTING'] = false; From ef1a72389a482cece821380c7cfbf645ecc746b5 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 6 Mar 2022 20:00:35 +0100 Subject: [PATCH 089/160] sorter err msg --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 8bf7eb6..04d55c9 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -172,7 +172,7 @@ async function handleMessage(json) { try { await broadcastFill(chainId, orderId, msg.args[2], fillOrder, wallet); } catch (e) { - const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'r',null,e]]]} + const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'r',null,e.message]]]} zigzagws.send(JSON.stringify(orderCommitMsg)); console.error(e); } From f913cad97a14c401c8f1de2293353a187ec4a459 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 8 Mar 2022 13:34:04 +0100 Subject: [PATCH 090/160] move ABI in folder --- ABIs/ERC20.abi | 222 ++++ ABIs/IUniswapV3Pool.abi | 988 ++++++++++++++++++ .../chainlinkV3InterfaceABI.abi | 0 marketmaker.js | 2 +- 4 files changed, 1211 insertions(+), 1 deletion(-) create mode 100644 ABIs/ERC20.abi create mode 100644 ABIs/IUniswapV3Pool.abi rename chainlinkV3InterfaceABI.abi => ABIs/chainlinkV3InterfaceABI.abi (100%) diff --git a/ABIs/ERC20.abi b/ABIs/ERC20.abi new file mode 100644 index 0000000..668d697 --- /dev/null +++ b/ABIs/ERC20.abi @@ -0,0 +1,222 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "balance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } +] \ No newline at end of file diff --git a/ABIs/IUniswapV3Pool.abi b/ABIs/IUniswapV3Pool.abi new file mode 100644 index 0000000..49cc338 --- /dev/null +++ b/ABIs/IUniswapV3Pool.abi @@ -0,0 +1,988 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Burn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "name": "Collect", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "name": "CollectProtocol", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paid0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paid1", + "type": "uint256" + } + ], + "name": "Flash", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint16", + "name": "observationCardinalityNextOld", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "observationCardinalityNextNew", + "type": "uint16" + } + ], + "name": "IncreaseObservationCardinalityNext", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "Initialize", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol0Old", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol1Old", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol0New", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol1New", + "type": "uint8" + } + ], + "name": "SetFeeProtocol", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount1", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "Swap", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount0Requested", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Requested", + "type": "uint128" + } + ], + "name": "collect", + "outputs": [ + { + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint128", + "name": "amount0Requested", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Requested", + "type": "uint128" + } + ], + "name": "collectProtocol", + "outputs": [ + { + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "fee", + "outputs": [ + { + "internalType": "uint24", + "name": "", + "type": "uint24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeGrowthGlobal0X128", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeGrowthGlobal1X128", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "flash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "observationCardinalityNext", + "type": "uint16" + } + ], + "name": "increaseObservationCardinalityNext", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "liquidity", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxLiquidityPerTick", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "observations", + "outputs": [ + { + "internalType": "uint32", + "name": "blockTimestamp", + "type": "uint32" + }, + { + "internalType": "int56", + "name": "tickCumulative", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityCumulativeX128", + "type": "uint160" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32[]", + "name": "secondsAgos", + "type": "uint32[]" + } + ], + "name": "observe", + "outputs": [ + { + "internalType": "int56[]", + "name": "tickCumulatives", + "type": "int56[]" + }, + { + "internalType": "uint160[]", + "name": "secondsPerLiquidityCumulativeX128s", + "type": "uint160[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "positions", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside0LastX128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside1LastX128", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "tokensOwed0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "tokensOwed1", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "protocolFees", + "outputs": [ + { + "internalType": "uint128", + "name": "token0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "token1", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "feeProtocol0", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "feeProtocol1", + "type": "uint8" + } + ], + "name": "setFeeProtocol", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "slot0", + "outputs": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "internalType": "uint16", + "name": "observationIndex", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinality", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinalityNext", + "type": "uint16" + }, + { + "internalType": "uint8", + "name": "feeProtocol", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "unlocked", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + } + ], + "name": "snapshotCumulativesInside", + "outputs": [ + { + "internalType": "int56", + "name": "tickCumulativeInside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityInsideX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "secondsInside", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "zeroForOne", + "type": "bool" + }, + { + "internalType": "int256", + "name": "amountSpecified", + "type": "int256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "internalType": "int256", + "name": "amount1", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int16", + "name": "", + "type": "int16" + } + ], + "name": "tickBitmap", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tickSpacing", + "outputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "name": "ticks", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidityGross", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "liquidityNet", + "type": "int128" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside0X128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside1X128", + "type": "uint256" + }, + { + "internalType": "int56", + "name": "tickCumulativeOutside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityOutsideX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "secondsOutside", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token0", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token1", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/chainlinkV3InterfaceABI.abi b/ABIs/chainlinkV3InterfaceABI.abi similarity index 100% rename from chainlinkV3InterfaceABI.abi rename to ABIs/chainlinkV3InterfaceABI.abi diff --git a/marketmaker.js b/marketmaker.js index 04d55c9..a1894e7 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -586,7 +586,7 @@ async function cryptowatchWsSetup(cryptowatchMarketIds) { async function chainlinkSetup(chainlinkMarketAddress) { chainlinkMarketAddress.forEach(async (address) => { try { - const aggregatorV3InterfaceABI = JSON.parse(fs.readFileSync('chainlinkV3InterfaceABI.abi')); + const aggregatorV3InterfaceABI = JSON.parse(fs.readFileSync('ABIs/chainlinkV3InterfaceABI.abi')); const provider = new ethers.Contract(address, aggregatorV3InterfaceABI, ethersProvider); const decimals = await provider.decimals(); CHAINLINK_PROVIDERS['chainlink:'+address] = [provider, decimals]; From 795a15bf5babbb0d3a8671916a97d17d67238ad6 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 8 Mar 2022 13:34:38 +0100 Subject: [PATCH 091/160] add uniswapV3Setup & uniswapV3Update --- README.md | 23 ++++++++++++++++- marketmaker.js | 69 +++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 85 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 456fc42..dd2214e 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Example: ``` ###### Chainlink -With chainlink you have access to price oracles via blockchain. The requests are read calls to a smart contract. The public ethers provider might be too slow for a higher number of pairs or at times of high demand. Therefore, it might be needed to have access to an Infura account (100000 Requests/Day for free). There you can get an endpoint for your market maker (like https://mainnet.infura.io/v3/...), You can add this with the `infuraUrl` field in `config.json`, like this: +With chainlink you have access to price oracles via blockchain. The requests are read-calls to a smart contract. The public ethers provider might be too slow for a higher number of pairs or at times of high demand. Therefore, it might be needed to have access to an Infura account (100000 Requests/Day for free). You can get an endpoint for your market maker (like https://mainnet.infura.io/v3/...), You can add this with the `infuraUrl` field in `config.json`, like this: ``` "ETH-USDC": { "infuraUrl": "https://mainnet.infura.io/v3/xxxxxxxx", @@ -107,6 +107,27 @@ You can get the available market contracts [here.](https://docs.chain.link/docs/ .... } ``` + +###### UniswapV3 +With uniswapV3 you have access to price feed's via blockchain. The requests are read-calls to a smart contract. The public ethers provider might be too slow for a higher number of pairs or at times of high demand. Therefore, it might be needed to have access to an Infura account (100000 Requests/Day for free). You can get an endpoint for your market maker (like https://mainnet.infura.io/v3/...), You can add this with the `infuraUrl` field in `config.json`, like this: +``` +"ETH-USDC": { + "infuraUrl": "https://mainnet.infura.io/v3/xxxxxxxx", + "zigzagChainId": 1, + "zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com", + .... +} +``` +You can get the available market contracts [here.](https://info.uniswap.org) Select a token and then a pool matching the pair you plan to market make. Make sure base and quote tokens match (USDC-ETH don't work for ETH-USDC). After selecting a pool, you can see the adress in the browser URL. Add that to your pair config as "uniswapv3:
", like this: +``` +"ETH-USDC": { + "side": "d", + "priceFeedPrimary": "uniswapv3:0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", + "priceFeedSecondary": null, + .... +} +``` + ###### Constant With constant mode, you can set a fixed price to market make. The bot will not change that price. Any secondary price feed will be ignored, if used as priceFeedPrimary. Also good as a `priceFeedSecondary` on stablecoins. diff --git a/marketmaker.js b/marketmaker.js index a1894e7..c71560b 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -15,6 +15,7 @@ const WALLETS = {}; const FILL_QUEUE = []; const MARKETS = {}; const CHAINLINK_PROVIDERS = {}; +const UNISWAP_V3_PROVIDERS = {}; const PAST_ORDER_LIST = {}; // Load MM config @@ -486,7 +487,7 @@ async function processFillQueue() { } async function setupPriceFeeds() { - const cryptowatch = [], chainlink = []; + const cryptowatch = [], chainlink = [], uniswapV3 = []; for (let market in MM_CONFIG.pairs) { const pairConfig = MM_CONFIG.pairs[market]; if(!pairConfig.active) { continue; } @@ -510,6 +511,9 @@ async function setupPriceFeeds() { case 'chainlink': if(!chainlink.includes(id)) { chainlink.push(id); } break; + case 'uniswapv3': + if(!uniswapV3.includes(id)) { uniswapV3.push(id); } + break; case 'constant': PRICE_FEEDS['constant:'+id] = parseFloat(id); break; @@ -519,8 +523,9 @@ async function setupPriceFeeds() { } }); } - if(chainlink.length) await chainlinkSetup(chainlink); - if(cryptowatch.length) await cryptowatchWsSetup(cryptowatch); + if(chainlink.length > 0) await chainlinkSetup(chainlink); + if(cryptowatch.length > 0) await cryptowatchWsSetup(cryptowatch); + if(uniswapV3.length > 0) await uniswapV3Setup(uniswapV3); console.log(PRICE_FEEDS); } @@ -584,7 +589,7 @@ async function cryptowatchWsSetup(cryptowatchMarketIds) { } async function chainlinkSetup(chainlinkMarketAddress) { - chainlinkMarketAddress.forEach(async (address) => { + const results = chainlinkMarketAddress.map(async (address) => { try { const aggregatorV3InterfaceABI = JSON.parse(fs.readFileSync('ABIs/chainlinkV3InterfaceABI.abi')); const provider = new ethers.Contract(address, aggregatorV3InterfaceABI, ethersProvider); @@ -598,17 +603,69 @@ async function chainlinkSetup(chainlinkMarketAddress) { throw new Error ("Error while setting up chainlink for "+address+", Error: "+e); } }); - setInterval(chainlinkUpdate, 10000); + await Promise.all(results); + setInterval(chainlinkUpdate, 15000); } async function chainlinkUpdate() { await Promise.all(Object.keys(CHAINLINK_PROVIDERS).map(async (key) => { const [provider, decimals] = CHAINLINK_PROVIDERS[key]; const response = await provider.latestRoundData(); - const price = parseFloat(response.answer) / 10**decimals; + PRICE_FEEDS['chainlink:'+address] = parseFloat(response.answer) / 10**decimals; })); } +async function uniswapV3Setup(uniswapV3Address) { + const results = uniswapV3Address.map(async (address) => { + try { + const IUniswapV3PoolABI = JSON.parse(fs.readFileSync('ABIs/IUniswapV3Pool.abi')); + const ERC20ABI = JSON.parse(fs.readFileSync('ABIs/ERC20.abi')); + + const provider = new ethers.Contract(address, IUniswapV3PoolABI, ethersProvider); + + let [ + slot0, + addressToken0, + addressToken1 + ] = await Promise.all ([ + provider.slot0(), + provider.token0(), + provider.token1() + ]); + + const tokenProvier0 = new ethers.Contract(addressToken0, ERC20ABI, ethersProvider); + const tokenProvier1 = new ethers.Contract(addressToken1, ERC20ABI, ethersProvider); + + let [ + decimals0, + decimals1 + ] = await Promise.all ([ + tokenProvier0.decimals(), + tokenProvier1.decimals() + ]); + + const decimalsRatio = (10**decimals0 / 10**decimals1); + UNISWAP_V3_PROVIDERS['uniswapV3:'+address] = [provider, decimalsRatio]; + + // get inital price + const price = (slot0.sqrtPriceX96*slot0.sqrtPriceX96*decimalsRatio) / (2**192); + PRICE_FEEDS['uniswapV3:'+address] = price; + } catch (e) { + throw new Error ("Error while setting up uniswapV3 for "+address+", Error: "+e); + } + }); + await Promise.all(results); + setInterval(uniswapV3Update, 15000); +} + +async function uniswapV3Update() { + await Promise.all(Object.keys(UNISWAP_V3_PROVIDERS).map(async (key) => { + const [provider, decimalsRatio] = UNISWAP_V3_PROVIDERS[key]; + const slot0 = await provider.slot0(); + PRICE_FEEDS['chainlink:'+address] = (slot0.sqrtPriceX96*slot0.sqrtPriceX96*decimalsRatio) / (2**192); + })); +} + function indicateLiquidity (pairs = MM_CONFIG.pairs) { for(const marketId in pairs) { const mmConfig = pairs[marketId]; From 2af195c8a7c01dba238db90f2b39ade87b7c2f4a Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 8 Mar 2022 14:54:54 +0100 Subject: [PATCH 092/160] fix/price feed key --- marketmaker.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index c71560b..afd98f0 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -594,11 +594,12 @@ async function chainlinkSetup(chainlinkMarketAddress) { const aggregatorV3InterfaceABI = JSON.parse(fs.readFileSync('ABIs/chainlinkV3InterfaceABI.abi')); const provider = new ethers.Contract(address, aggregatorV3InterfaceABI, ethersProvider); const decimals = await provider.decimals(); - CHAINLINK_PROVIDERS['chainlink:'+address] = [provider, decimals]; + const key = 'chainlink:' + address; + CHAINLINK_PROVIDERS[key] = [provider, decimals]; // get inital price const response = await provider.latestRoundData(); - PRICE_FEEDS['chainlink:'+address] = parseFloat(response.answer) / 10**decimals; + PRICE_FEEDS[key] = parseFloat(response.answer) / 10**decimals; } catch (e) { throw new Error ("Error while setting up chainlink for "+address+", Error: "+e); } @@ -611,7 +612,7 @@ async function chainlinkUpdate() { await Promise.all(Object.keys(CHAINLINK_PROVIDERS).map(async (key) => { const [provider, decimals] = CHAINLINK_PROVIDERS[key]; const response = await provider.latestRoundData(); - PRICE_FEEDS['chainlink:'+address] = parseFloat(response.answer) / 10**decimals; + PRICE_FEEDS[key] = parseFloat(response.answer) / 10**decimals; })); } @@ -644,12 +645,13 @@ async function uniswapV3Setup(uniswapV3Address) { tokenProvier1.decimals() ]); + const key = 'uniswapV3:' + address; const decimalsRatio = (10**decimals0 / 10**decimals1); - UNISWAP_V3_PROVIDERS['uniswapV3:'+address] = [provider, decimalsRatio]; - + UNISWAP_V3_PROVIDERS[key] = [provider, decimalsRatio]; + // get inital price const price = (slot0.sqrtPriceX96*slot0.sqrtPriceX96*decimalsRatio) / (2**192); - PRICE_FEEDS['uniswapV3:'+address] = price; + PRICE_FEEDS[key] = price; } catch (e) { throw new Error ("Error while setting up uniswapV3 for "+address+", Error: "+e); } @@ -662,7 +664,7 @@ async function uniswapV3Update() { await Promise.all(Object.keys(UNISWAP_V3_PROVIDERS).map(async (key) => { const [provider, decimalsRatio] = UNISWAP_V3_PROVIDERS[key]; const slot0 = await provider.slot0(); - PRICE_FEEDS['chainlink:'+address] = (slot0.sqrtPriceX96*slot0.sqrtPriceX96*decimalsRatio) / (2**192); + PRICE_FEEDS[key] = (slot0.sqrtPriceX96*slot0.sqrtPriceX96*decimalsRatio) / (2**192); })); } From ebde9c334ec540ab09f5e7ee9c946013a9654ca6 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 8 Mar 2022 14:55:07 +0100 Subject: [PATCH 093/160] fix/increase delay --- marketmaker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index afd98f0..f25afee 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -605,7 +605,7 @@ async function chainlinkSetup(chainlinkMarketAddress) { } }); await Promise.all(results); - setInterval(chainlinkUpdate, 15000); + setInterval(chainlinkUpdate, 30000); } async function chainlinkUpdate() { @@ -657,7 +657,7 @@ async function uniswapV3Setup(uniswapV3Address) { } }); await Promise.all(results); - setInterval(uniswapV3Update, 15000); + setInterval(uniswapV3Update, 30000); } async function uniswapV3Update() { From e8faf8711fda2b4308183812d792a652b0c692b9 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 9 Mar 2022 16:39:48 +0100 Subject: [PATCH 094/160] rename goodWallets -> goodWalletIds wallet -> walletId --- marketmaker.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index ccb7115..854bd71 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -157,7 +157,7 @@ async function handleMessage(json) { const fillable = isOrderFillable(order); console.log(fillable); if (fillable.fillable) { - sendFillRequest(order, fillable.wallet); + sendFillRequest(order, fillable.walletId); } else if (fillable.reason === "badprice") { OPEN_ORDERS[orderId] = order; @@ -221,23 +221,23 @@ function isOrderFillable(order) { const sellDecimals = (side === 's') ? market.quoteAsset.decimals : market.baseAsset.decimals; const sellQuantity = (side === 's') ? quoteQuantity : baseQuantity; const neededBalanceBN = sellQuantity * 10**sellDecimals; - const goodWallets = []; + const goodWalletIds = []; Object.keys(WALLETS).forEach(accountId => { const walletBalance = WALLETS[accountId]['account_state'].committed.balances[sellCurrency]; if (Number(walletBalance) > (neededBalanceBN * 1.05)) { - goodWallets.push(accountId); + goodWalletIds.push(accountId); } }); - if (goodWallets.length === 0) { + if (goodWalletIds.length === 0) { return { fillable: false, reason: "badbalance" }; } - goodWallets = goodWallets.filter(accountId => { + goodWalletIds = goodWalletIds.filter(accountId => { WALLETS[accountId]['ORDER_BROADCASTING'] == false; }); - if (goodWallets.length === 0) { + if (goodWalletIds.length === 0) { return { fillable: false, reason: "sending order already " }; } @@ -272,7 +272,7 @@ function isOrderFillable(order) { return { fillable: false, reason: "badprice" }; } - return { fillable: true, reason: null, wallet: goodWallets[0]}; + return { fillable: true, reason: null, walletId: goodWalletIds[0]}; } function genQuote(chainId, marketId, side, baseQuantity) { @@ -457,7 +457,7 @@ async function fillOpenOrders() { const order = OPEN_ORDERS[orderId]; const fillable = isOrderFillable(order); if (fillable.fillable) { - sendFillRequest(order, fillable.wallets); + sendFillRequest(order, fillable.walletId); delete OPEN_ORDERS[orderId]; } else if (fillable.reason !== "badprice") { From 22606ea7175c3207a85348dfa33be0d9ff75de55 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Thu, 10 Mar 2022 22:01:26 +0100 Subject: [PATCH 095/160] remove Initiate fill loop --- marketmaker.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 854bd71..035d1cb 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -100,9 +100,6 @@ setInterval(updateAccountState, 900000); logBalance(); setInterval(logBalance, 3 * 60 * 60 * 1000); // 3h -// Initiate fill loop -setTimeout(processFillQueue, 1000); - let fillOrdersInterval, indicateLiquidityInterval; let zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); zigzagws.on('open', onWsOpen); From cb15e8f4a3a82d7bd792ba57cf5ae870359b6665 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Thu, 10 Mar 2022 22:01:35 +0100 Subject: [PATCH 096/160] goodWalletIds not const --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 035d1cb..cb05316 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -218,7 +218,7 @@ function isOrderFillable(order) { const sellDecimals = (side === 's') ? market.quoteAsset.decimals : market.baseAsset.decimals; const sellQuantity = (side === 's') ? quoteQuantity : baseQuantity; const neededBalanceBN = sellQuantity * 10**sellDecimals; - const goodWalletIds = []; + let goodWalletIds = []; Object.keys(WALLETS).forEach(accountId => { const walletBalance = WALLETS[accountId]['account_state'].committed.balances[sellCurrency]; if (Number(walletBalance) > (neededBalanceBN * 1.05)) { From 4e93e2cd09d15930d3cb887c10ede02ddeec295c Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Thu, 10 Mar 2022 22:05:10 +0100 Subject: [PATCH 097/160] fix broadcasting check --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index cb05316..d10edd0 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -231,7 +231,7 @@ function isOrderFillable(order) { } goodWalletIds = goodWalletIds.filter(accountId => { - WALLETS[accountId]['ORDER_BROADCASTING'] == false; + return !WALLETS[accountId]['ORDER_BROADCASTING']; }); if (goodWalletIds.length === 0) { From 41fa7ff8fc4970f1b7b1eb95b5bd9b2ce96b3daa Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Thu, 10 Mar 2022 22:11:05 +0100 Subject: [PATCH 098/160] update account state after balance error --- marketmaker.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/marketmaker.js b/marketmaker.js index d10edd0..ebeed74 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -444,6 +444,9 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { newStatus = 'r'; error = swap.error.toString(); } + if(error.includes('balance')) { + updateAccountState() + } const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,newStatus,txHash,error]]]} zigzagws.send(JSON.stringify(orderCommitMsg)); From 8f1c1b9f9fc71a3534c9c2e2b37b1ca4bd10bc1b Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Thu, 10 Mar 2022 22:23:05 +0100 Subject: [PATCH 099/160] revert --- marketmaker.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index ebeed74..d10edd0 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -444,9 +444,6 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { newStatus = 'r'; error = swap.error.toString(); } - if(error.includes('balance')) { - updateAccountState() - } const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,newStatus,txHash,error]]]} zigzagws.send(JSON.stringify(orderCommitMsg)); From 2ce4240242452b4490eb2bcb6bb45f90f7ed67c3 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Fri, 18 Mar 2022 11:46:23 +0100 Subject: [PATCH 100/160] add hard limit for ORDER_BROADCASTING --- marketmaker.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/marketmaker.js b/marketmaker.js index a66ffb7..556be2b 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -389,6 +389,11 @@ async function sendFillRequest(orderreceipt, accountId) { // Set wallet flag WALLETS[accountId]['ORDER_BROADCASTING'] = true; + // ORDER_BROADCASTING should not take longer as 5 sec + setTimeout(function() { + WALLETS[accountId]['ORDER_BROADCASTING'] = false; + }, 5000); + const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] }; zigzagws.send(JSON.stringify(resp)); rememberOrder(chainId, From 484a5cb194813ad042190876845d7d856e94c10b Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Fri, 18 Mar 2022 11:55:42 +0100 Subject: [PATCH 101/160] remove that with 5 sec auto cancel --- marketmaker.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 556be2b..87af50f 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -121,9 +121,6 @@ function onWsOpen() { function onWsClose () { console.log("Websocket closed. Restarting"); - Object.keys(WALLETS).forEach(accountId => { - WALLETS[accountId]['ORDER_BROADCASTING'] = false; - }); setTimeout(() => { clearInterval(fillOrdersInterval) clearInterval(indicateLiquidityInterval) @@ -142,11 +139,7 @@ async function handleMessage(json) { const accountId = msg.args[1]; if(msg.args[0] == 'fillrequest' && accountId) { WALLETS[accountId]['ORDER_BROADCASTING'] = false; - } else { - Object.keys(WALLETS).forEach(accountId => { - WALLETS[accountId]['ORDER_BROADCASTING'] = false; - }); - } + } break; case 'orders': const orders = msg.args[0]; From 45261481a0e7e6feeb5d5523e422a15bd48c73a5 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Fri, 18 Mar 2022 23:59:21 +0100 Subject: [PATCH 102/160] fix/some errors dont return args --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 87af50f..85117ee 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -136,7 +136,7 @@ async function handleMessage(json) { if (!(["lastprice", "liquidity2", "fillstatus", "marketinfo"]).includes(msg.op)) console.log(json.toString()); switch(msg.op) { case 'error': - const accountId = msg.args[1]; + const accountId = msg.args?.[1]; if(msg.args[0] == 'fillrequest' && accountId) { WALLETS[accountId]['ORDER_BROADCASTING'] = false; } From 826778759738d76c34899b30ebae86af6dedbbdc Mon Sep 17 00:00:00 2001 From: vixidev99 Date: Tue, 22 Mar 2022 10:25:04 -0700 Subject: [PATCH 103/160] offer best price on buy orders --- marketmaker.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 87af50f..80ddda8 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -343,6 +343,7 @@ async function sendFillRequest(orderreceipt, accountId) { const baseQuantity = orderreceipt[5]; const quoteQuantity = orderreceipt[6]; const quote = genQuote(chainId, marketId, side, baseQuantity); + console.log(baseQuantity, quoteQuantity, quote); let tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol; if (side === "b") { tokenSell = market.baseAssetId; @@ -350,7 +351,7 @@ async function sendFillRequest(orderreceipt, accountId) { sellSymbol = market.baseAsset.symbol; buySymbol = market.quoteAsset.symbol; // Add 1 bip to to protect against rounding errors - sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals); + sellQuantity = (quoteQuantity / quote.quotePrice * 1.0001).toFixed(market.baseAsset.decimals); buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); } else if (side === "s") { tokenSell = market.quoteAssetId; From 22f0717057ecad326013feae64be78fd5134018a Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 22 Mar 2022 21:21:00 +0100 Subject: [PATCH 104/160] feat/add uniswap update error counter --- marketmaker.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 85117ee..f9c68c5 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -17,6 +17,8 @@ const CHAINLINK_PROVIDERS = {}; const UNISWAP_V3_PROVIDERS = {}; const PAST_ORDER_LIST = {}; +let uniswap_error_counter = 0; + // Load MM config let MM_CONFIG; if (process.env.MM_CONFIG) { @@ -639,11 +641,21 @@ async function uniswapV3Setup(uniswapV3Address) { } async function uniswapV3Update() { - await Promise.all(Object.keys(UNISWAP_V3_PROVIDERS).map(async (key) => { - const [provider, decimalsRatio] = UNISWAP_V3_PROVIDERS[key]; - const slot0 = await provider.slot0(); - PRICE_FEEDS[key] = (slot0.sqrtPriceX96*slot0.sqrtPriceX96*decimalsRatio) / (2**192); - })); + try { + await Promise.all(Object.keys(UNISWAP_V3_PROVIDERS).map(async (key) => { + const [provider, decimalsRatio] = UNISWAP_V3_PROVIDERS[key]; + const slot0 = await provider.slot0(); + PRICE_FEEDS[key] = (slot0.sqrtPriceX96*slot0.sqrtPriceX96*decimalsRatio) / (2**192); + })); + // reset error counter if successful + uniswap_error_counter = 0; + } catch (err) { + uniswap_error_counter += 1; + console.log(err.message); + if(uniswap_error_counter > 4) { + throw new Error ("Can't update uniswap price!") + } + } } function indicateLiquidity (pairs = MM_CONFIG.pairs) { From bdb3b5adac47d70ec608362aaeb400a5b4dbe00c Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 22 Mar 2022 21:28:30 +0100 Subject: [PATCH 105/160] add error counter on chainlink --- marketmaker.js | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index f9c68c5..77af08e 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -18,6 +18,7 @@ const UNISWAP_V3_PROVIDERS = {}; const PAST_ORDER_LIST = {}; let uniswap_error_counter = 0; +let chainlink_error_counter = 0; // Load MM config let MM_CONFIG; @@ -589,11 +590,20 @@ async function chainlinkSetup(chainlinkMarketAddress) { } async function chainlinkUpdate() { - await Promise.all(Object.keys(CHAINLINK_PROVIDERS).map(async (key) => { - const [provider, decimals] = CHAINLINK_PROVIDERS[key]; - const response = await provider.latestRoundData(); - PRICE_FEEDS[key] = parseFloat(response.answer) / 10**decimals; - })); + try { + await Promise.all(Object.keys(CHAINLINK_PROVIDERS).map(async (key) => { + const [provider, decimals] = CHAINLINK_PROVIDERS[key]; + const response = await provider.latestRoundData(); + PRICE_FEEDS[key] = parseFloat(response.answer) / 10**decimals; + chainlink_error_counter = 0; + })); + } catch (err) { + chainlink_error_counter += 1; + console.log(`Failed to update chainlink, retry: ${err.message}`); + if(chainlink_error_counter > 4) { + throw new Error ("Failed to update chainlink since 150 seconds!") + } + } } async function uniswapV3Setup(uniswapV3Address) { @@ -651,9 +661,10 @@ async function uniswapV3Update() { uniswap_error_counter = 0; } catch (err) { uniswap_error_counter += 1; + console.log(`Failed to update uniswap, retry: ${err.message}`); console.log(err.message); if(uniswap_error_counter > 4) { - throw new Error ("Can't update uniswap price!") + throw new Error ("Failed to update uniswap since 150 seconds!") } } } From bc317a43e06773f5e68ebb14ec84a52a7b70681d Mon Sep 17 00:00:00 2001 From: Trooper <95502080+TrooperCrypto@users.noreply.github.com> Date: Wed, 23 Mar 2022 19:26:38 +0100 Subject: [PATCH 106/160] remove extra logging --- marketmaker.js | 1 - 1 file changed, 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 80ddda8..acec3d7 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -343,7 +343,6 @@ async function sendFillRequest(orderreceipt, accountId) { const baseQuantity = orderreceipt[5]; const quoteQuantity = orderreceipt[6]; const quote = genQuote(chainId, marketId, side, baseQuantity); - console.log(baseQuantity, quoteQuantity, quote); let tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol; if (side === "b") { tokenSell = market.baseAssetId; From 2ae3d91e231dfad83dbca4766f9ea8a8a265894f Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 23 Mar 2022 21:11:53 +0100 Subject: [PATCH 107/160] move reset after loop --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 77af08e..c7b2d5f 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -595,8 +595,8 @@ async function chainlinkUpdate() { const [provider, decimals] = CHAINLINK_PROVIDERS[key]; const response = await provider.latestRoundData(); PRICE_FEEDS[key] = parseFloat(response.answer) / 10**decimals; - chainlink_error_counter = 0; })); + chainlink_error_counter = 0; } catch (err) { chainlink_error_counter += 1; console.log(`Failed to update chainlink, retry: ${err.message}`); From 8b85f13933720acaea83c45deddef48d4e51f646 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Fri, 25 Mar 2022 13:09:45 +0100 Subject: [PATCH 108/160] add settings for mainnet&rinkeby --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index dd2214e..2b62583 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,14 @@ node marketmaker.js ## Settings +#### Mainnet zkSync +- "zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com" +- "zigzagChainId": 1 + +#### Rinkeby zkSync +- "zigzagWsUrl": "wss://secret-thicket-93345.herokuapp.com" +- "zigzagChainId": 1000 + You can add, remove, and configure pair settings in the `pairs` section. A pair setting looks like this: ``` From 69217927f7f6027c32668aae375a33cb54d104c9 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Fri, 25 Mar 2022 15:55:53 +0100 Subject: [PATCH 109/160] fix/dont drop order if busy --- marketmaker.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index cc7fef9..fb10c59 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -109,7 +109,7 @@ zigzagws.on('error', console.error); function onWsOpen() { zigzagws.on('message', handleMessage); - fillOrdersInterval = setInterval(fillOpenOrders, 1000); + fillOrdersInterval = setInterval(fillOpenOrders, 200); indicateLiquidityInterval = setInterval(indicateLiquidity, 5000); for (let market in MM_CONFIG.pairs) { if (MM_CONFIG.pairs[market].active) { @@ -150,7 +150,10 @@ async function handleMessage(json) { if (fillable.fillable) { sendFillRequest(order, fillable.walletId); } - else if (fillable.reason === "badprice") { + else if ([ + "sending order already", + "badprice" + ].includes(fillable.reason)) { OPEN_ORDERS[orderId] = order; } }); @@ -231,7 +234,7 @@ function isOrderFillable(order) { }); if (goodWalletIds.length === 0) { - return { fillable: false, reason: "sending order already " }; + return { fillable: false, reason: "sending order already" }; } const now = Date.now() / 1000 | 0; @@ -457,8 +460,10 @@ async function fillOpenOrders() { if (fillable.fillable) { sendFillRequest(order, fillable.walletId); delete OPEN_ORDERS[orderId]; - } - else if (fillable.reason !== "badprice") { + }else if (![ + "sending order already", + "badprice" + ].includes(fillable.reason)) { delete OPEN_ORDERS[orderId]; } } From 5beaedfca8c268ac0c208e2ad4868b8206f87389 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sat, 26 Mar 2022 15:47:26 +0100 Subject: [PATCH 110/160] update sendFillRequest --- marketmaker.js | 59 +++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index cc7fef9..8fa0f8d 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -268,7 +268,7 @@ function isOrderFillable(order) { return { fillable: true, reason: null, walletId: goodWalletIds[0]}; } -function genQuote(chainId, marketId, side, baseQuantity) { +function genQuote(chainId, marketId, side, baseQuantity, quoteQuantity = 0) { const market = MARKETS[marketId]; if (CHAIN_ID !== chainId) throw new Error("badchain"); if (!market) throw new Error("badmarket"); @@ -285,17 +285,25 @@ function genQuote(chainId, marketId, side, baseQuantity) { const primaryPrice = PRICE_FEEDS[mmConfig.priceFeedPrimary]; if (!primaryPrice) throw new Error("badprice"); const SPREAD = mmConfig.minSpread + (baseQuantity * mmConfig.slippageRate); - let quoteQuantity; - if (side === 'b') { - quoteQuantity = (baseQuantity * primaryPrice * (1 + SPREAD)) + market.quoteFee; - } - else if (side === 's') { - quoteQuantity = (baseQuantity - market.baseFee) * primaryPrice * (1 - SPREAD); + if (baseQuantity && !quoteQuantity) { + if (side === 'b') { + quoteQuantity = (baseQuantity * primaryPrice * (1 + SPREAD)) + market.quoteFee; + } else if (side === 's') { + quoteQuantity = (baseQuantity - market.baseFee) * primaryPrice * (1 - SPREAD); + } + } else if (!baseQuantity && quoteQuantity){ + if (side === 'b') { + baseQuantity = (quoteQuantity / (primaryPrice * (1 + SPREAD))) + market.baseFee; + } else if (side === 's') { + baseQuantity = (quoteQuantity - market.quoteFee) / (primaryPrice * (1 - SPREAD)); + } + } else { + throw new Error("badbase/badquote"); } const quotePrice = (quoteQuantity / baseQuantity).toPrecision(6); if (quotePrice < 0) throw new Error("Amount is inadequate to pay fee"); if (isNaN(quotePrice)) throw new Error("Internal Error. No price generated."); - return { quotePrice, quoteQuantity }; + return { quotePrice, baseQuantity, quoteQuantity }; } function validatePriceFeed(marketId) { @@ -325,8 +333,8 @@ function validatePriceFeed(marketId) { // If the secondary price feed varies from the primary price feed by more than 1%, assume something is broken const percentDiff = Math.abs(primaryPrice - secondaryPrice) / primaryPrice; if (percentDiff > 0.03) { - throw new Error("Circuit breaker triggered"); - console.error("Primary and secondary price feeds do not match!"); + console.error("Primary and secondary price feeds do not match!"); + throw new Error("Circuit breaker triggered"); } return true; @@ -342,24 +350,25 @@ async function sendFillRequest(orderreceipt, accountId) { const side = orderreceipt[3]; const baseQuantity = orderreceipt[5]; const quoteQuantity = orderreceipt[6]; - const quote = genQuote(chainId, marketId, side, baseQuantity); let tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol; if (side === "b") { - tokenSell = market.baseAssetId; - tokenBuy = market.quoteAssetId; - sellSymbol = market.baseAsset.symbol; - buySymbol = market.quoteAsset.symbol; - // Add 1 bip to to protect against rounding errors - sellQuantity = (quoteQuantity / quote.quotePrice * 1.0001).toFixed(market.baseAsset.decimals); - buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); + const quote = genQuote(chainId, marketId, side, baseQuantity); + tokenSell = market.baseAssetId; + tokenBuy = market.quoteAssetId; + sellSymbol = market.baseAsset.symbol; + buySymbol = market.quoteAsset.symbol; + // Add 1 bip to to protect against rounding errors + sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals); // set by user + buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); } else if (side === "s") { - tokenSell = market.quoteAssetId; - tokenBuy = market.baseAssetId; - sellSymbol = market.quoteAsset.symbol; - buySymbol = market.baseAsset.symbol; - // Add 1 bip to to protect against rounding errors - sellQuantity = (quote.quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals); - buyQuantity = (baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); + const quote = genQuote(chainId, marketId, side, 0, quoteQuantity); + tokenSell = market.quoteAssetId; + tokenBuy = market.baseAssetId; + sellSymbol = market.quoteAsset.symbol; + buySymbol = market.baseAsset.symbol; + // Add 1 bip to to protect against rounding errors + sellQuantity = (quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals); // set by user + buyQuantity = (quote.baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); } const sellQuantityParsed = syncProvider.tokenSet.parseToken( tokenSell, From d16457dc337790a37af7467b29dca797a8b26440 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sat, 26 Mar 2022 15:58:05 +0100 Subject: [PATCH 111/160] remove rounding for user amount --- marketmaker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 8fa0f8d..3ea095f 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -358,7 +358,7 @@ async function sendFillRequest(orderreceipt, accountId) { sellSymbol = market.baseAsset.symbol; buySymbol = market.quoteAsset.symbol; // Add 1 bip to to protect against rounding errors - sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals); // set by user + sellQuantity = baseQuantity.toFixed(market.baseAsset.decimals); // set by user buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); } else if (side === "s") { const quote = genQuote(chainId, marketId, side, 0, quoteQuantity); @@ -367,7 +367,7 @@ async function sendFillRequest(orderreceipt, accountId) { sellSymbol = market.quoteAsset.symbol; buySymbol = market.baseAsset.symbol; // Add 1 bip to to protect against rounding errors - sellQuantity = (quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals); // set by user + sellQuantity = quoteQuantity.toFixed(market.quoteAsset.decimals); // set by user buyQuantity = (quote.baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); } const sellQuantityParsed = syncProvider.tokenSet.parseToken( From 2438883afcd9ebe72e055221d5a0e6980547ec11 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sat, 26 Mar 2022 16:01:57 +0100 Subject: [PATCH 112/160] remove mm side rounding --- marketmaker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 950c2dd..5df2e57 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -362,7 +362,7 @@ async function sendFillRequest(orderreceipt, accountId) { buySymbol = market.quoteAsset.symbol; // Add 1 bip to to protect against rounding errors sellQuantity = baseQuantity.toFixed(market.baseAsset.decimals); // set by user - buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); + buyQuantity = quote.quoteQuantity.toFixed(market.quoteAsset.decimals); } else if (side === "s") { const quote = genQuote(chainId, marketId, side, 0, quoteQuantity); tokenSell = market.quoteAssetId; @@ -371,7 +371,7 @@ async function sendFillRequest(orderreceipt, accountId) { buySymbol = market.baseAsset.symbol; // Add 1 bip to to protect against rounding errors sellQuantity = quoteQuantity.toFixed(market.quoteAsset.decimals); // set by user - buyQuantity = (quote.baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); + buyQuantity = quote.baseQuantity.toFixed(market.baseAsset.decimals); } const sellQuantityParsed = syncProvider.tokenSet.parseToken( tokenSell, From eb12458fe69d857e579c37c68ca993454b95cfe0 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sat, 26 Mar 2022 17:46:04 +0100 Subject: [PATCH 113/160] define quote outside if --- marketmaker.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 5df2e57..10b65dd 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -353,9 +353,9 @@ async function sendFillRequest(orderreceipt, accountId) { const side = orderreceipt[3]; const baseQuantity = orderreceipt[5]; const quoteQuantity = orderreceipt[6]; - let tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol; + let quote, tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol; if (side === "b") { - const quote = genQuote(chainId, marketId, side, baseQuantity); + quote = genQuote(chainId, marketId, side, baseQuantity); tokenSell = market.baseAssetId; tokenBuy = market.quoteAssetId; sellSymbol = market.baseAsset.symbol; @@ -364,7 +364,7 @@ async function sendFillRequest(orderreceipt, accountId) { sellQuantity = baseQuantity.toFixed(market.baseAsset.decimals); // set by user buyQuantity = quote.quoteQuantity.toFixed(market.quoteAsset.decimals); } else if (side === "s") { - const quote = genQuote(chainId, marketId, side, 0, quoteQuantity); + quote = genQuote(chainId, marketId, side, 0, quoteQuantity); tokenSell = market.quoteAssetId; tokenBuy = market.baseAssetId; sellSymbol = market.quoteAsset.symbol; From 819299ad04a758b41a30ee807ddb7f7bf1e77425 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 27 Mar 2022 12:44:41 +0200 Subject: [PATCH 114/160] fix/genQuote tests --- marketmaker.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index dd0e6e7..3b04e3c 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -279,7 +279,9 @@ function genQuote(chainId, marketId, side, baseQuantity, quoteQuantity = 0) { if (CHAIN_ID !== chainId) throw new Error("badchain"); if (!market) throw new Error("badmarket"); if (!(['b','s']).includes(side)) throw new Error("badside"); - if (baseQuantity <= 0) throw new Error("badquantity"); + if (baseQuantity < 0) throw new Error("badbasequantity"); + if (quoteQuantity < 0) throw new Error("badquotequantity"); + if (baseQuantity === 0 & quoteQuantity === 0) throw new Error("badquantity"); validatePriceFeed(marketId); @@ -304,7 +306,7 @@ function genQuote(chainId, marketId, side, baseQuantity, quoteQuantity = 0) { baseQuantity = (quoteQuantity - market.quoteFee) / (primaryPrice * (1 - SPREAD)); } } else { - throw new Error("badbase/badquote"); + throw new Error("badbase/quotequantity"); } const quotePrice = (quoteQuantity / baseQuantity).toPrecision(6); if (quotePrice < 0) throw new Error("Amount is inadequate to pay fee"); From 4a96390c7320b80c66bb6093c5a8ca38b7557d87 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 27 Mar 2022 15:39:37 +0200 Subject: [PATCH 115/160] roll back --- marketmaker.js | 179 +++++++++++++++++++++++-------------------------- 1 file changed, 84 insertions(+), 95 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 3b04e3c..28f6f63 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -274,44 +274,34 @@ function isOrderFillable(order) { return { fillable: true, reason: null, walletId: goodWalletIds[0]}; } -function genQuote(chainId, marketId, side, baseQuantity, quoteQuantity = 0) { - const market = MARKETS[marketId]; - if (CHAIN_ID !== chainId) throw new Error("badchain"); - if (!market) throw new Error("badmarket"); - if (!(['b','s']).includes(side)) throw new Error("badside"); - if (baseQuantity < 0) throw new Error("badbasequantity"); - if (quoteQuantity < 0) throw new Error("badquotequantity"); - if (baseQuantity === 0 & quoteQuantity === 0) throw new Error("badquantity"); - - validatePriceFeed(marketId); - - const mmConfig = MM_CONFIG.pairs[marketId]; - const mmSide = mmConfig.side || 'd'; - if (mmSide !== 'd' && mmSide === side) { - throw new Error("badside"); - } - const primaryPrice = PRICE_FEEDS[mmConfig.priceFeedPrimary]; - if (!primaryPrice) throw new Error("badprice"); - const SPREAD = mmConfig.minSpread + (baseQuantity * mmConfig.slippageRate); - if (baseQuantity && !quoteQuantity) { - if (side === 'b') { - quoteQuantity = (baseQuantity * primaryPrice * (1 + SPREAD)) + market.quoteFee; - } else if (side === 's') { - quoteQuantity = (baseQuantity - market.baseFee) * primaryPrice * (1 - SPREAD); - } - } else if (!baseQuantity && quoteQuantity){ - if (side === 'b') { - baseQuantity = (quoteQuantity / (primaryPrice * (1 + SPREAD))) + market.baseFee; - } else if (side === 's') { - baseQuantity = (quoteQuantity - market.quoteFee) / (primaryPrice * (1 - SPREAD)); - } - } else { - throw new Error("badbase/quotequantity"); - } - const quotePrice = (quoteQuantity / baseQuantity).toPrecision(6); - if (quotePrice < 0) throw new Error("Amount is inadequate to pay fee"); - if (isNaN(quotePrice)) throw new Error("Internal Error. No price generated."); - return { quotePrice, baseQuantity, quoteQuantity }; +function genQuote(chainId, marketId, side, baseQuantity) { + const market = MARKETS[marketId]; + if (CHAIN_ID !== chainId) throw new Error("badchain"); + if (!market) throw new Error("badmarket"); + if (!(['b','s']).includes(side)) throw new Error("badside"); + if (baseQuantity <= 0) throw new Error("badquantity"); + + validatePriceFeed(marketId); + + const mmConfig = MM_CONFIG.pairs[marketId]; + const mmSide = mmConfig.side || 'd'; + if (mmSide !== 'd' && mmSide === side) { + throw new Error("badside"); + } + const primaryPrice = PRICE_FEEDS[mmConfig.priceFeedPrimary]; + if (!primaryPrice) throw new Error("badprice"); + const SPREAD = mmConfig.minSpread + (baseQuantity * mmConfig.slippageRate); + let quoteQuantity; + if (side === 'b') { + quoteQuantity = (baseQuantity * primaryPrice * (1 + SPREAD)) + market.quoteFee; + } + else if (side === 's') { + quoteQuantity = (baseQuantity - market.baseFee) * primaryPrice * (1 - SPREAD); + } + const quotePrice = (quoteQuantity / baseQuantity).toPrecision(6); + if (quotePrice < 0) throw new Error("Amount is inadequate to pay fee"); + if (isNaN(quotePrice)) throw new Error("Internal Error. No price generated."); + return { quotePrice, quoteQuantity }; } function validatePriceFeed(marketId) { @@ -349,72 +339,71 @@ function validatePriceFeed(marketId) { } async function sendFillRequest(orderreceipt, accountId) { - const chainId = orderreceipt[0]; - const orderId = orderreceipt[1]; - const marketId = orderreceipt[2]; - const market = MARKETS[marketId]; - const baseCurrency = market.baseAssetId; - const quoteCurrency = market.quoteAssetId; - const side = orderreceipt[3]; - const baseQuantity = orderreceipt[5]; - const quoteQuantity = orderreceipt[6]; - let quote, tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol; - if (side === "b") { - quote = genQuote(chainId, marketId, side, baseQuantity); + const chainId = orderreceipt[0]; + const orderId = orderreceipt[1]; + const marketId = orderreceipt[2]; + const market = MARKETS[marketId]; + const baseCurrency = market.baseAssetId; + const quoteCurrency = market.quoteAssetId; + const side = orderreceipt[3]; + const baseQuantity = orderreceipt[5]; + const quoteQuantity = orderreceipt[6]; + const quote = genQuote(chainId, marketId, side, baseQuantity); + let tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol; + if (side === "b") { tokenSell = market.baseAssetId; tokenBuy = market.quoteAssetId; sellSymbol = market.baseAsset.symbol; buySymbol = market.quoteAsset.symbol; // Add 1 bip to to protect against rounding errors - sellQuantity = baseQuantity.toFixed(market.baseAsset.decimals); // set by user - buyQuantity = quote.quoteQuantity.toFixed(market.quoteAsset.decimals); - } else if (side === "s") { - quote = genQuote(chainId, marketId, side, 0, quoteQuantity); + sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals); + buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); + } else if (side === "s") { tokenSell = market.quoteAssetId; tokenBuy = market.baseAssetId; sellSymbol = market.quoteAsset.symbol; buySymbol = market.baseAsset.symbol; - // Add 1 bip to to protect against rounding errors - sellQuantity = quoteQuantity.toFixed(market.quoteAsset.decimals); // set by user - buyQuantity = quote.baseQuantity.toFixed(market.baseAsset.decimals); - } - const sellQuantityParsed = syncProvider.tokenSet.parseToken( - tokenSell, - sellQuantity - ); - const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount(sellQuantityParsed); - const tokenRatio = {}; - tokenRatio[tokenBuy] = buyQuantity; - tokenRatio[tokenSell] = sellQuantity; - const oneMinExpiry = (Date.now() / 1000 | 0) + 60; - const orderDetails = { - tokenSell, - tokenBuy, - amount: sellQuantityPacked, - ratio: zksync.utils.tokenRatio(tokenRatio), - validUntil: oneMinExpiry - } - const fillOrder = await WALLETS[accountId].syncWallet.getOrder(orderDetails); - - // Set wallet flag - WALLETS[accountId]['ORDER_BROADCASTING'] = true; - - // ORDER_BROADCASTING should not take longer as 5 sec - setTimeout(function() { - WALLETS[accountId]['ORDER_BROADCASTING'] = false; - }, 5000); - - const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] }; - zigzagws.send(JSON.stringify(resp)); - rememberOrder(chainId, - marketId, - orderId, - quote.quotePrice, - sellSymbol, - sellQuantity, - buySymbol, - buyQuantity - ); + // Add 1 bip to to protect against rounding errors + sellQuantity = (quote.quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals); + buyQuantity = (baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); + } + const sellQuantityParsed = syncProvider.tokenSet.parseToken( + tokenSell, + sellQuantity + ); + const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount(sellQuantityParsed); + const tokenRatio = {}; + tokenRatio[tokenBuy] = buyQuantity; + tokenRatio[tokenSell] = sellQuantity; + const oneMinExpiry = (Date.now() / 1000 | 0) + 60; + const orderDetails = { + tokenSell, + tokenBuy, + amount: sellQuantityPacked, + ratio: zksync.utils.tokenRatio(tokenRatio), + validUntil: oneMinExpiry + } + const fillOrder = await WALLETS[accountId].syncWallet.getOrder(orderDetails); + + // Set wallet flag + WALLETS[accountId]['ORDER_BROADCASTING'] = true; + + // ORDER_BROADCASTING should not take longer as 5 sec + setTimeout(function() { + WALLETS[accountId]['ORDER_BROADCASTING'] = false; + }, 5000); + + const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] }; + zigzagws.send(JSON.stringify(resp)); + rememberOrder(chainId, + marketId, + orderId, + quote.quotePrice, + sellSymbol, + sellQuantity, + buySymbol, + buyQuantity + ); } async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { From 623182e9c8ed779673cd178b9bf715795842f82b Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 27 Mar 2022 16:51:08 +0200 Subject: [PATCH 116/160] move quote outside loop --- marketmaker.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index a3c23b2..dd0e6e7 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -356,9 +356,9 @@ async function sendFillRequest(orderreceipt, accountId) { const side = orderreceipt[3]; const baseQuantity = orderreceipt[5]; const quoteQuantity = orderreceipt[6]; - let tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol; + let quote, tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol; if (side === "b") { - const quote = genQuote(chainId, marketId, side, baseQuantity); + quote = genQuote(chainId, marketId, side, baseQuantity); tokenSell = market.baseAssetId; tokenBuy = market.quoteAssetId; sellSymbol = market.baseAsset.symbol; @@ -367,7 +367,7 @@ async function sendFillRequest(orderreceipt, accountId) { sellQuantity = baseQuantity.toFixed(market.baseAsset.decimals); // set by user buyQuantity = quote.quoteQuantity.toFixed(market.quoteAsset.decimals); } else if (side === "s") { - const quote = genQuote(chainId, marketId, side, 0, quoteQuantity); + quote = genQuote(chainId, marketId, side, 0, quoteQuantity); tokenSell = market.quoteAssetId; tokenBuy = market.baseAssetId; sellSymbol = market.quoteAsset.symbol; From 16519f55e14198ad5175fdab201757b711e625a0 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 27 Mar 2022 16:51:25 +0200 Subject: [PATCH 117/160] validate price with same logic --- marketmaker.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index dd0e6e7..f0364d5 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -259,18 +259,21 @@ function isOrderFillable(order) { let quote; try { + if (side === 's') { quote = genQuote(chainId, marketId, side, baseQuantity); + if (price > quote.quotePrice) { + return { fillable: false, reason: "badprice" }; + } + } else { + quote = genQuote(chainId, marketId, side, 0, quoteQuantity); + if (price < quote.quotePrice) { + return { fillable: false, reason: "badprice" }; + } + } } catch (e) { return { fillable: false, reason: e.message } } - if (side == 's' && price > quote.quotePrice) { - return { fillable: false, reason: "badprice" }; - } - else if (side == 'b' && price < quote.quotePrice) { - return { fillable: false, reason: "badprice" }; - } - return { fillable: true, reason: null, walletId: goodWalletIds[0]}; } From ee6385d82c90465a97879c56160bc9111d3f2fce Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 27 Mar 2022 16:51:50 +0200 Subject: [PATCH 118/160] dont change fee with spread --- marketmaker.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index f0364d5..5bd51f9 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -282,7 +282,9 @@ function genQuote(chainId, marketId, side, baseQuantity, quoteQuantity = 0) { if (CHAIN_ID !== chainId) throw new Error("badchain"); if (!market) throw new Error("badmarket"); if (!(['b','s']).includes(side)) throw new Error("badside"); - if (baseQuantity <= 0) throw new Error("badquantity"); + if (baseQuantity < 0) throw new Error("badbasequantity"); + if (quoteQuantity < 0) throw new Error("badquotequantity"); + if (baseQuantity === 0 & quoteQuantity === 0) throw new Error("badquantity"); validatePriceFeed(marketId); @@ -298,13 +300,13 @@ function genQuote(chainId, marketId, side, baseQuantity, quoteQuantity = 0) { if (side === 'b') { quoteQuantity = (baseQuantity * primaryPrice * (1 + SPREAD)) + market.quoteFee; } else if (side === 's') { - quoteQuantity = (baseQuantity - market.baseFee) * primaryPrice * (1 - SPREAD); + quoteQuantity = (baseQuantity * primaryPrice * (1 - SPREAD)) - market.quoteFee; } - } else if (!baseQuantity && quoteQuantity){ + } else if (!baseQuantity && quoteQuantity) { if (side === 'b') { - baseQuantity = (quoteQuantity / (primaryPrice * (1 + SPREAD))) + market.baseFee; + baseQuantity = (quoteQuantity / (primaryPrice * (1 - SPREAD))) + market.baseFee; } else if (side === 's') { - baseQuantity = (quoteQuantity - market.quoteFee) / (primaryPrice * (1 - SPREAD)); + baseQuantity = (quoteQuantity / (primaryPrice * (1 + SPREAD))) - market.baseFee; } } else { throw new Error("badbase/badquote"); From 6457329be84bf79d0adc8bfc3b6ecf1b5b3ea120 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 27 Mar 2022 17:08:37 +0200 Subject: [PATCH 119/160] fix ratio --- marketmaker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 5bd51f9..57d27ae 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -304,9 +304,9 @@ function genQuote(chainId, marketId, side, baseQuantity, quoteQuantity = 0) { } } else if (!baseQuantity && quoteQuantity) { if (side === 'b') { - baseQuantity = (quoteQuantity / (primaryPrice * (1 - SPREAD))) + market.baseFee; - } else if (side === 's') { baseQuantity = (quoteQuantity / (primaryPrice * (1 + SPREAD))) - market.baseFee; + } else if (side === 's') { + baseQuantity = (quoteQuantity / (primaryPrice * (1 - SPREAD))) + market.baseFee; } } else { throw new Error("badbase/badquote"); From 88e3ee17dd323cbdbb14561a2c827bfe6f53998b Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 30 Mar 2022 13:42:53 +0200 Subject: [PATCH 120/160] feat/match at better price --- marketmaker.js | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 57d27ae..9851d10 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -152,8 +152,7 @@ async function handleMessage(json) { console.log(fillable); if (fillable.fillable) { sendFillRequest(order, fillable.walletId); - } - else if ([ + } else if ([ "sending order already", "badprice" ].includes(fillable.reason)) { @@ -304,9 +303,9 @@ function genQuote(chainId, marketId, side, baseQuantity, quoteQuantity = 0) { } } else if (!baseQuantity && quoteQuantity) { if (side === 'b') { - baseQuantity = (quoteQuantity / (primaryPrice * (1 + SPREAD))) - market.baseFee; + baseQuantity = (quoteQuantity / (primaryPrice * (1 + SPREAD))) + market.baseFee; } else if (side === 's') { - baseQuantity = (quoteQuantity / (primaryPrice * (1 - SPREAD))) + market.baseFee; + baseQuantity = (quoteQuantity / (primaryPrice * (1 - SPREAD))) - market.baseFee; } } else { throw new Error("badbase/badquote"); @@ -361,30 +360,34 @@ async function sendFillRequest(orderreceipt, accountId) { const side = orderreceipt[3]; const baseQuantity = orderreceipt[5]; const quoteQuantity = orderreceipt[6]; - let quote, tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol; + let quote, tokenSell, tokenBuy, sellQuantity, sellQuantityParsed, buyQuantity, buySymbol, sellSymbol; if (side === "b") { - quote = genQuote(chainId, marketId, side, baseQuantity); + quote = genQuote(chainId, marketId, side, 0, quoteQuantity); tokenSell = market.baseAssetId; tokenBuy = market.quoteAssetId; sellSymbol = market.baseAsset.symbol; - buySymbol = market.quoteAsset.symbol; - // Add 1 bip to to protect against rounding errors - sellQuantity = baseQuantity.toFixed(market.baseAsset.decimals); // set by user - buyQuantity = quote.quoteQuantity.toFixed(market.quoteAsset.decimals); + buySymbol = market.quoteAsset.symbol; + buyQuantity = quoteQuantity.toFixed(market.quoteAsset.decimals); // set by user + sellQuantity = quote.baseQuantity.toFixed(market.baseAsset.decimals); + // Add 0.1 bip to the amount to protect against rounding errors + sellQuantityParsed = syncProvider.tokenSet.parseToken( + tokenSell, + (quote.baseQuantity*0.99999).toFixed(market.baseAsset.decimals) + ); } else if (side === "s") { - quote = genQuote(chainId, marketId, side, 0, quoteQuantity); + quote = genQuote(chainId, marketId, side, baseQuantity); tokenSell = market.quoteAssetId; tokenBuy = market.baseAssetId; sellSymbol = market.quoteAsset.symbol; buySymbol = market.baseAsset.symbol; - // Add 1 bip to to protect against rounding errors - sellQuantity = quoteQuantity.toFixed(market.quoteAsset.decimals); // set by user - buyQuantity = quote.baseQuantity.toFixed(market.baseAsset.decimals); - } - const sellQuantityParsed = syncProvider.tokenSet.parseToken( + buyQuantity = baseQuantity.toFixed(market.baseAsset.decimals); // set by user + sellQuantity = quote.quoteQuantity.toFixed(market.quoteAsset.decimals); + // Add 0.1 bip to the amount to protect against rounding errors + sellQuantityParsed = syncProvider.tokenSet.parseToken( tokenSell, - sellQuantity - ); + (quote.quoteQuantity*0.99999).toFixed(market.quoteAsset.decimals) + ); + } const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount(sellQuantityParsed); const tokenRatio = {}; tokenRatio[tokenBuy] = buyQuantity; From df69208b0fb1d98831f0cfba461c41a035dbaf90 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 30 Mar 2022 21:56:16 +0200 Subject: [PATCH 121/160] update package-lock,json --- package-lock.json | 1611 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1598 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2898c3d..bc8d7f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,1585 @@ { "name": "zigzag-market-maker", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "zigzag-market-maker", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "dotenv": "^10.0.0", + "ethers": "^5.5.1", + "node-fetch": "^3.1.0", + "ws": "^8.2.3", + "zksync": "^0.11.6" + }, + "devDependencies": {}, + "engines": { + "node": ">=16 <17" + } + }, + "node_modules/@ethersproject/abi": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.5.0.tgz", + "integrity": "sha512-loW7I4AohP5KycATvc0MgujU6JyCHPqHdeoo9z3Nr9xEiNioxa65ccdm1+fsoJhkuhdRtfcL8cfyGamz2AxZ5w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/address": "^5.5.0", + "@ethersproject/bignumber": "^5.5.0", + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/constants": "^5.5.0", + "@ethersproject/hash": "^5.5.0", + "@ethersproject/keccak256": "^5.5.0", + "@ethersproject/logger": "^5.5.0", + "@ethersproject/properties": "^5.5.0", + "@ethersproject/strings": "^5.5.0" + } + }, + "node_modules/@ethersproject/abstract-provider": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.5.1.tgz", + "integrity": "sha512-m+MA/ful6eKbxpr99xUYeRvLkfnlqzrF8SZ46d/xFB1A7ZVknYc/sXJG0RcufF52Qn2jeFj1hhcoQ7IXjNKUqg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.5.0", + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/logger": "^5.5.0", + "@ethersproject/networks": "^5.5.0", + "@ethersproject/properties": "^5.5.0", + "@ethersproject/transactions": "^5.5.0", + "@ethersproject/web": "^5.5.0" + } + }, + "node_modules/@ethersproject/abstract-signer": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.5.0.tgz", + "integrity": "sha512-lj//7r250MXVLKI7sVarXAbZXbv9P50lgmJQGr2/is82EwEb8r7HrxsmMqAjTsztMYy7ohrIhGMIml+Gx4D3mA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-provider": "^5.5.0", + "@ethersproject/bignumber": "^5.5.0", + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/logger": "^5.5.0", + "@ethersproject/properties": "^5.5.0" + } + }, + "node_modules/@ethersproject/address": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.5.0.tgz", + "integrity": "sha512-l4Nj0eWlTUh6ro5IbPTgbpT4wRbdH5l8CQf7icF7sb/SI3Nhd9Y9HzhonTSTi6CefI0necIw7LJqQPopPLZyWw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.5.0", + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/keccak256": "^5.5.0", + "@ethersproject/logger": "^5.5.0", + "@ethersproject/rlp": "^5.5.0" + } + }, + "node_modules/@ethersproject/base64": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.5.0.tgz", + "integrity": "sha512-tdayUKhU1ljrlHzEWbStXazDpsx4eg1dBXUSI6+mHlYklOXoXF6lZvw8tnD6oVaWfnMxAgRSKROg3cVKtCcppA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.5.0" + } + }, + "node_modules/@ethersproject/basex": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.5.0.tgz", + "integrity": "sha512-ZIodwhHpVJ0Y3hUCfUucmxKsWQA5TMnavp5j/UOuDdzZWzJlRmuOjcTMIGgHCYuZmHt36BfiSyQPSRskPxbfaQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/properties": "^5.5.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.5.0.tgz", + "integrity": "sha512-6Xytlwvy6Rn3U3gKEc1vP7nR92frHkv6wtVr95LFR3jREXiCPzdWxKQ1cx4JGQBXxcguAwjA8murlYN2TSiEbg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/logger": "^5.5.0", + "bn.js": "^4.11.9" + } + }, + "node_modules/@ethersproject/bytes": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.5.0.tgz", + "integrity": "sha512-ABvc7BHWhZU9PNM/tANm/Qx4ostPGadAuQzWTr3doklZOhDlmcBqclrQe/ZXUIj3K8wC28oYeuRa+A37tX9kog==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/logger": "^5.5.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.5.0.tgz", + "integrity": "sha512-2MsRRVChkvMWR+GyMGY4N1sAX9Mt3J9KykCsgUFd/1mwS0UH1qw+Bv9k1UJb3X3YJYFco9H20pjSlOIfCG5HYQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.5.0" + } + }, + "node_modules/@ethersproject/contracts": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.5.0.tgz", + "integrity": "sha512-2viY7NzyvJkh+Ug17v7g3/IJC8HqZBDcOjYARZLdzRxrfGlRgmYgl6xPRKVbEzy1dWKw/iv7chDcS83pg6cLxg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abi": "^5.5.0", + "@ethersproject/abstract-provider": "^5.5.0", + "@ethersproject/abstract-signer": "^5.5.0", + "@ethersproject/address": "^5.5.0", + "@ethersproject/bignumber": "^5.5.0", + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/constants": "^5.5.0", + "@ethersproject/logger": "^5.5.0", + "@ethersproject/properties": "^5.5.0", + "@ethersproject/transactions": "^5.5.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.5.0.tgz", + "integrity": "sha512-dnGVpK1WtBjmnp3mUT0PlU2MpapnwWI0PibldQEq1408tQBAbZpPidkWoVVuNMOl/lISO3+4hXZWCL3YV7qzfg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-signer": "^5.5.0", + "@ethersproject/address": "^5.5.0", + "@ethersproject/bignumber": "^5.5.0", + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/keccak256": "^5.5.0", + "@ethersproject/logger": "^5.5.0", + "@ethersproject/properties": "^5.5.0", + "@ethersproject/strings": "^5.5.0" + } + }, + "node_modules/@ethersproject/hdnode": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.5.0.tgz", + "integrity": "sha512-mcSOo9zeUg1L0CoJH7zmxwUG5ggQHU1UrRf8jyTYy6HxdZV+r0PBoL1bxr+JHIPXRzS6u/UW4mEn43y0tmyF8Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-signer": "^5.5.0", + "@ethersproject/basex": "^5.5.0", + "@ethersproject/bignumber": "^5.5.0", + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/logger": "^5.5.0", + "@ethersproject/pbkdf2": "^5.5.0", + "@ethersproject/properties": "^5.5.0", + "@ethersproject/sha2": "^5.5.0", + "@ethersproject/signing-key": "^5.5.0", + "@ethersproject/strings": "^5.5.0", + "@ethersproject/transactions": "^5.5.0", + "@ethersproject/wordlists": "^5.5.0" + } + }, + "node_modules/@ethersproject/json-wallets": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.5.0.tgz", + "integrity": "sha512-9lA21XQnCdcS72xlBn1jfQdj2A1VUxZzOzi9UkNdnokNKke/9Ya2xA9aIK1SC3PQyBDLt4C+dfps7ULpkvKikQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-signer": "^5.5.0", + "@ethersproject/address": "^5.5.0", + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/hdnode": "^5.5.0", + "@ethersproject/keccak256": "^5.5.0", + "@ethersproject/logger": "^5.5.0", + "@ethersproject/pbkdf2": "^5.5.0", + "@ethersproject/properties": "^5.5.0", + "@ethersproject/random": "^5.5.0", + "@ethersproject/strings": "^5.5.0", + "@ethersproject/transactions": "^5.5.0", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + } + }, + "node_modules/@ethersproject/keccak256": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.5.0.tgz", + "integrity": "sha512-5VoFCTjo2rYbBe1l2f4mccaRFN/4VQEYFwwn04aJV2h7qf4ZvI2wFxUE1XOX+snbwCLRzIeikOqtAoPwMza9kg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.5.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.5.0.tgz", + "integrity": "sha512-rIY/6WPm7T8n3qS2vuHTUBPdXHl+rGxWxW5okDfo9J4Z0+gRRZT0msvUdIJkE4/HS29GUMziwGaaKO2bWONBrg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ] + }, + "node_modules/@ethersproject/networks": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.5.0.tgz", + "integrity": "sha512-KWfP3xOnJeF89Uf/FCJdV1a2aDJe5XTN2N52p4fcQ34QhDqQFkgQKZ39VGtiqUgHcLI8DfT0l9azC3KFTunqtA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/logger": "^5.5.0" + } + }, + "node_modules/@ethersproject/pbkdf2": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.5.0.tgz", + "integrity": "sha512-SaDvQFvXPnz1QGpzr6/HToLifftSXGoXrbpZ6BvoZhmx4bNLHrxDe8MZisuecyOziP1aVEwzC2Hasj+86TgWVg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/sha2": "^5.5.0" + } + }, + "node_modules/@ethersproject/properties": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.5.0.tgz", + "integrity": "sha512-l3zRQg3JkD8EL3CPjNK5g7kMx4qSwiR60/uk5IVjd3oq1MZR5qUg40CNOoEJoX5wc3DyY5bt9EbMk86C7x0DNA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/logger": "^5.5.0" + } + }, + "node_modules/@ethersproject/providers": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.5.0.tgz", + "integrity": "sha512-xqMbDnS/FPy+J/9mBLKddzyLLAQFjrVff5g00efqxPzcAwXiR+SiCGVy6eJ5iAIirBOATjx7QLhDNPGV+AEQsw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-provider": "^5.5.0", + "@ethersproject/abstract-signer": "^5.5.0", + "@ethersproject/address": "^5.5.0", + "@ethersproject/basex": "^5.5.0", + "@ethersproject/bignumber": "^5.5.0", + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/constants": "^5.5.0", + "@ethersproject/hash": "^5.5.0", + "@ethersproject/logger": "^5.5.0", + "@ethersproject/networks": "^5.5.0", + "@ethersproject/properties": "^5.5.0", + "@ethersproject/random": "^5.5.0", + "@ethersproject/rlp": "^5.5.0", + "@ethersproject/sha2": "^5.5.0", + "@ethersproject/strings": "^5.5.0", + "@ethersproject/transactions": "^5.5.0", + "@ethersproject/web": "^5.5.0", + "bech32": "1.1.4", + "ws": "7.4.6" + } + }, + "node_modules/@ethersproject/providers/node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@ethersproject/random": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.5.0.tgz", + "integrity": "sha512-egGYZwZ/YIFKMHcoBUo8t3a8Hb/TKYX8BCBoLjudVCZh892welR3jOxgOmb48xznc9bTcMm7Tpwc1gHC1PFNFQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/logger": "^5.5.0" + } + }, + "node_modules/@ethersproject/rlp": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.5.0.tgz", + "integrity": "sha512-hLv8XaQ8PTI9g2RHoQGf/WSxBfTB/NudRacbzdxmst5VHAqd1sMibWG7SENzT5Dj3yZ3kJYx+WiRYEcQTAkcYA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/logger": "^5.5.0" + } + }, + "node_modules/@ethersproject/sha2": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.5.0.tgz", + "integrity": "sha512-B5UBoglbCiHamRVPLA110J+2uqsifpZaTmid2/7W5rbtYVz6gus6/hSDieIU/6gaKIDcOj12WnOdiymEUHIAOA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/logger": "^5.5.0", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/signing-key": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.5.0.tgz", + "integrity": "sha512-5VmseH7qjtNmDdZBswavhotYbWB0bOwKIlOTSlX14rKn5c11QmJwGt4GHeo7NrL/Ycl7uo9AHvEqs5xZgFBTng==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/logger": "^5.5.0", + "@ethersproject/properties": "^5.5.0", + "bn.js": "^4.11.9", + "elliptic": "6.5.4", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/solidity": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.5.0.tgz", + "integrity": "sha512-9NgZs9LhGMj6aCtHXhtmFQ4AN4sth5HuFXVvAQtzmm0jpSCNOTGtrHZJAeYTh7MBjRR8brylWZxBZR9zDStXbw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.5.0", + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/keccak256": "^5.5.0", + "@ethersproject/logger": "^5.5.0", + "@ethersproject/sha2": "^5.5.0", + "@ethersproject/strings": "^5.5.0" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.5.0.tgz", + "integrity": "sha512-9fy3TtF5LrX/wTrBaT8FGE6TDJyVjOvXynXJz5MT5azq+E6D92zuKNx7i29sWW2FjVOaWjAsiZ1ZWznuduTIIQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/constants": "^5.5.0", + "@ethersproject/logger": "^5.5.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.5.0.tgz", + "integrity": "sha512-9RZYSKX26KfzEd/1eqvv8pLauCKzDTub0Ko4LfIgaERvRuwyaNV78mJs7cpIgZaDl6RJui4o49lHwwCM0526zA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/address": "^5.5.0", + "@ethersproject/bignumber": "^5.5.0", + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/constants": "^5.5.0", + "@ethersproject/keccak256": "^5.5.0", + "@ethersproject/logger": "^5.5.0", + "@ethersproject/properties": "^5.5.0", + "@ethersproject/rlp": "^5.5.0", + "@ethersproject/signing-key": "^5.5.0" + } + }, + "node_modules/@ethersproject/units": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.5.0.tgz", + "integrity": "sha512-7+DpjiZk4v6wrikj+TCyWWa9dXLNU73tSTa7n0TSJDxkYbV3Yf1eRh9ToMLlZtuctNYu9RDNNy2USq3AdqSbag==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.5.0", + "@ethersproject/constants": "^5.5.0", + "@ethersproject/logger": "^5.5.0" + } + }, + "node_modules/@ethersproject/wallet": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.5.0.tgz", + "integrity": "sha512-Mlu13hIctSYaZmUOo7r2PhNSd8eaMPVXe1wxrz4w4FCE4tDYBywDH+bAR1Xz2ADyXGwqYMwstzTrtUVIsKDO0Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-provider": "^5.5.0", + "@ethersproject/abstract-signer": "^5.5.0", + "@ethersproject/address": "^5.5.0", + "@ethersproject/bignumber": "^5.5.0", + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/hash": "^5.5.0", + "@ethersproject/hdnode": "^5.5.0", + "@ethersproject/json-wallets": "^5.5.0", + "@ethersproject/keccak256": "^5.5.0", + "@ethersproject/logger": "^5.5.0", + "@ethersproject/properties": "^5.5.0", + "@ethersproject/random": "^5.5.0", + "@ethersproject/signing-key": "^5.5.0", + "@ethersproject/transactions": "^5.5.0", + "@ethersproject/wordlists": "^5.5.0" + } + }, + "node_modules/@ethersproject/web": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.5.0.tgz", + "integrity": "sha512-BEgY0eL5oH4mAo37TNYVrFeHsIXLRxggCRG/ksRIxI2X5uj5IsjGmcNiRN/VirQOlBxcUhCgHhaDLG4m6XAVoA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/base64": "^5.5.0", + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/logger": "^5.5.0", + "@ethersproject/properties": "^5.5.0", + "@ethersproject/strings": "^5.5.0" + } + }, + "node_modules/@ethersproject/wordlists": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.5.0.tgz", + "integrity": "sha512-bL0UTReWDiaQJJYOC9sh/XcRu/9i2jMrzf8VLRmPKx58ckSlOJiohODkECCO50dtLZHcGU6MLXQ4OOrgBwP77Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.5.0", + "@ethersproject/hash": "^5.5.0", + "@ethersproject/logger": "^5.5.0", + "@ethersproject/properties": "^5.5.0", + "@ethersproject/strings": "^5.5.0" + } + }, + "node_modules/aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" + }, + "node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + }, + "node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "node_modules/bufferutil": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", + "integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chnl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/chnl/-/chnl-1.2.0.tgz", + "integrity": "sha512-g5gJb59edwCliFbX2j7G6sBfY4sX9YLy211yctONI2GRaiX0f2zIbKWmBm+sPqFNEpM7Ljzm7IJX/xrjiEbPrw==" + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "engines": { + "node": ">=10" + } + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/es-abstract": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", + "is-string": "^1.0.7", + "is-weakref": "^1.0.1", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "dependencies": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/ethers": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.5.1.tgz", + "integrity": "sha512-RodEvUFZI+EmFcE6bwkuJqpCYHazdzeR1nMzg+YWQSmQEsNtfl1KHGfp/FWZYl48bI/g7cgBeP2IlPthjiVngw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abi": "5.5.0", + "@ethersproject/abstract-provider": "5.5.1", + "@ethersproject/abstract-signer": "5.5.0", + "@ethersproject/address": "5.5.0", + "@ethersproject/base64": "5.5.0", + "@ethersproject/basex": "5.5.0", + "@ethersproject/bignumber": "5.5.0", + "@ethersproject/bytes": "5.5.0", + "@ethersproject/constants": "5.5.0", + "@ethersproject/contracts": "5.5.0", + "@ethersproject/hash": "5.5.0", + "@ethersproject/hdnode": "5.5.0", + "@ethersproject/json-wallets": "5.5.0", + "@ethersproject/keccak256": "5.5.0", + "@ethersproject/logger": "5.5.0", + "@ethersproject/networks": "5.5.0", + "@ethersproject/pbkdf2": "5.5.0", + "@ethersproject/properties": "5.5.0", + "@ethersproject/providers": "5.5.0", + "@ethersproject/random": "5.5.0", + "@ethersproject/rlp": "5.5.0", + "@ethersproject/sha2": "5.5.0", + "@ethersproject/signing-key": "5.5.0", + "@ethersproject/solidity": "5.5.0", + "@ethersproject/strings": "5.5.0", + "@ethersproject/transactions": "5.5.0", + "@ethersproject/units": "5.5.0", + "@ethersproject/wallet": "5.5.0", + "@ethersproject/web": "5.5.0", + "@ethersproject/wordlists": "5.5.0" + } + }, + "node_modules/ext": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", + "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", + "dependencies": { + "type": "^2.5.0" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", + "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==" + }, + "node_modules/fetch-blob": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz", + "integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/follow-redirects": { + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "node_modules/is-weakref": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", + "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", + "dependencies": { + "call-bind": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.3.tgz", + "integrity": "sha512-AXP18u4pidSZ1xYXRDPY/8jdv3RAozIt/WLNR/MBGZAz+xjtlr90RvCnsvHQRiXyWliZF/CpytExp32UU67/SA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-gyp-build": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", + "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/object-inspect": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/promise-controller": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/promise-controller/-/promise-controller-1.0.0.tgz", + "integrity": "sha512-goA0zA9L91tuQbUmiMinSYqlyUtEgg4fxJcjYnLYOQnrktb4o4UqciXDNXiRUPiDBPACmsr1k8jDW4r7UDq9Qw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/promise.prototype.finally": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/promise.prototype.finally/-/promise.prototype.finally-3.1.3.tgz", + "integrity": "sha512-EXRF3fC9/0gz4qkt/f5EP5iW4kj9oFpBICNpCNOb/52+8nlHIX07FPLbi/q4qYBQ1xZqivMzTpNQSnArVASolQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dependencies": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/utf-8-validate": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz", + "integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", + "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/websocket": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", + "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", + "dependencies": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/websocket-as-promised": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/websocket-as-promised/-/websocket-as-promised-1.1.0.tgz", + "integrity": "sha512-agq8bPsPFKBWinKQkoXwY7LoBYe+2fQ7Gnuxx964+BTIiyAdL130FnB60bXuVQdUCdaS17R/MyRaaO4WIqtl4Q==", + "dependencies": { + "chnl": "^1.2.0", + "promise-controller": "^1.0.0", + "promise.prototype.finally": "^3.1.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=", + "engines": { + "node": ">=0.10.32" + } + }, + "node_modules/zksync": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/zksync/-/zksync-0.11.6.tgz", + "integrity": "sha512-p54+KjmKhd5MUe0rWZDcqM3k9UT2BqpYMp2fOpl5m+Hp9GLn+PMAiKYaWtzgjnYvZ7ceOdKq8CEHKKEbpLyZ0A==", + "dependencies": { + "axios": "^0.21.2", + "websocket": "^1.0.30", + "websocket-as-promised": "^1.1.0", + "zksync-crypto": "^0.6.1" + }, + "peerDependencies": { + "@ethersproject/logger": "^5.4.0", + "ethers": "^5.4.4" + } + }, + "node_modules/zksync-crypto": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/zksync-crypto/-/zksync-crypto-0.6.2.tgz", + "integrity": "sha512-Ry8c9kixDu3rlGCZDOg1UhzKDYe93iOZ7/Z6N1bkkrVKhmZJTIc7h+1kOjOyyhaSaHwBt8quMeBAaZKjtUeB2Q==" + } + }, "dependencies": { "@ethersproject/abi": { "version": "5.5.0", @@ -240,7 +1817,8 @@ "ws": { "version": "7.4.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "requires": {} } } }, @@ -598,17 +2176,18 @@ } }, "fetch-blob": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.3.tgz", - "integrity": "sha512-ax1Y5I9w+9+JiM+wdHkhBoxew+zG4AJ2SvAD1v1szpddUIiPERVGBxrMcB2ZqW0Y3PP8bOWYv2zqQq1Jp2kqUQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz", + "integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==", "requires": { + "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "follow-redirects": { - "version": "1.14.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz", - "integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==" + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" }, "formdata-polyfill": { "version": "4.0.10", @@ -813,13 +2392,18 @@ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" + }, "node-fetch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.1.0.tgz", - "integrity": "sha512-QU0WbIfMUjd5+MUzQOYhenAazakV7Irh1SGkWCsRzBwvm4fAhzEUaHMJ6QLP7gWT6WO9/oH2zhKMMGMuIrDyKw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.3.tgz", + "integrity": "sha512-AXP18u4pidSZ1xYXRDPY/8jdv3RAozIt/WLNR/MBGZAz+xjtlr90RvCnsvHQRiXyWliZF/CpytExp32UU67/SA==", "requires": { "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.2", + "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, @@ -972,7 +2556,8 @@ "ws": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==" + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "requires": {} }, "yaeti": { "version": "0.0.6", From f32dedbcb74109a608dbeb0ea6cec20389c1b2d8 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Fri, 1 Apr 2022 22:12:12 +0200 Subject: [PATCH 122/160] use wei ratio --- marketmaker.js | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 9851d10..e99c63a 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -303,9 +303,9 @@ function genQuote(chainId, marketId, side, baseQuantity, quoteQuantity = 0) { } } else if (!baseQuantity && quoteQuantity) { if (side === 'b') { - baseQuantity = (quoteQuantity / (primaryPrice * (1 + SPREAD))) + market.baseFee; + baseQuantity = (quoteQuantity / (primaryPrice * (1 + SPREAD))) - market.baseFee; } else if (side === 's') { - baseQuantity = (quoteQuantity / (primaryPrice * (1 - SPREAD))) - market.baseFee; + baseQuantity = (quoteQuantity / (primaryPrice * (1 - SPREAD))) + market.baseFee; } } else { throw new Error("badbase/badquote"); @@ -360,44 +360,52 @@ async function sendFillRequest(orderreceipt, accountId) { const side = orderreceipt[3]; const baseQuantity = orderreceipt[5]; const quoteQuantity = orderreceipt[6]; - let quote, tokenSell, tokenBuy, sellQuantity, sellQuantityParsed, buyQuantity, buySymbol, sellSymbol; + let quote, tokenSell, tokenBuy, buySymbol, sellSymbol, sellQuantity, buyQuantity; if (side === "b") { quote = genQuote(chainId, marketId, side, 0, quoteQuantity); + tokenSell = market.baseAssetId; tokenBuy = market.quoteAssetId; + sellSymbol = market.baseAsset.symbol; - buySymbol = market.quoteAsset.symbol; + buySymbol = market.quoteAsset.symbol; + buyQuantity = quoteQuantity.toFixed(market.quoteAsset.decimals); // set by user sellQuantity = quote.baseQuantity.toFixed(market.baseAsset.decimals); - // Add 0.1 bip to the amount to protect against rounding errors - sellQuantityParsed = syncProvider.tokenSet.parseToken( - tokenSell, - (quote.baseQuantity*0.99999).toFixed(market.baseAsset.decimals) - ); } else if (side === "s") { quote = genQuote(chainId, marketId, side, baseQuantity); + tokenSell = market.quoteAssetId; tokenBuy = market.baseAssetId; + sellSymbol = market.quoteAsset.symbol; buySymbol = market.baseAsset.symbol; + buyQuantity = baseQuantity.toFixed(market.baseAsset.decimals); // set by user sellQuantity = quote.quoteQuantity.toFixed(market.quoteAsset.decimals); - // Add 0.1 bip to the amount to protect against rounding errors - sellQuantityParsed = syncProvider.tokenSet.parseToken( - tokenSell, - (quote.quoteQuantity*0.99999).toFixed(market.quoteAsset.decimals) - ); - } - const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount(sellQuantityParsed); + } + + const buyQuantityParsed = syncProvider.tokenSet.parseToken( + tokenBuy, + buyQuantity + ); + const sellQuantityParsed = syncProvider.tokenSet.parseToken( + tokenSell, + sellQuantity + ); + // Add 0.1 bip to the amount to protect against rounding errors + const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount( + sellQuantityParsed * 0.99999 + ); const tokenRatio = {}; - tokenRatio[tokenBuy] = buyQuantity; - tokenRatio[tokenSell] = sellQuantity; + tokenRatio[tokenBuy] = buyQuantityParsed; + tokenRatio[tokenSell] = sellQuantityPacked; const oneMinExpiry = (Date.now() / 1000 | 0) + 60; const orderDetails = { tokenSell, tokenBuy, amount: sellQuantityPacked, - ratio: zksync.utils.tokenRatio(tokenRatio), + ratio: zksync.utils.weiRatio(tokenRatio), validUntil: oneMinExpiry } const fillOrder = await WALLETS[accountId].syncWallet.getOrder(orderDetails); From da8ffa668ab20f3307476cc14f3b9225a75b7350 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Fri, 1 Apr 2022 22:21:21 +0200 Subject: [PATCH 123/160] fix --- marketmaker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index e99c63a..101cdef 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -392,10 +392,10 @@ async function sendFillRequest(orderreceipt, accountId) { const sellQuantityParsed = syncProvider.tokenSet.parseToken( tokenSell, sellQuantity - ); + ) * 0.99999; // Add 0.1 bip to the amount to protect against rounding errors const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount( - sellQuantityParsed * 0.99999 + sellQuantityParsed ); const tokenRatio = {}; tokenRatio[tokenBuy] = buyQuantityParsed; From 81e34ec468578a3aec950be973fb386d632790bf Mon Sep 17 00:00:00 2001 From: Trooper <95502080+TrooperCrypto@users.noreply.github.com> Date: Sat, 2 Apr 2022 21:24:35 +0200 Subject: [PATCH 124/160] fix --- marketmaker.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 824fd55..ebb94ec 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -259,20 +259,16 @@ function isOrderFillable(order) { let quote; try { - if (side === 's') { - quote = genQuote(chainId, marketId, side, baseQuantity); - if (price > quote.quotePrice) { - return { fillable: false, reason: "badprice" }; - } - } else { - quote = genQuote(chainId, marketId, side, 0, quoteQuantity); - if (price < quote.quotePrice) { - return { fillable: false, reason: "badprice" }; - } - } + quote = genQuote(chainId, marketId, side, baseQuantity); } catch (e) { return { fillable: false, reason: e.message } } + if (side == 's' && price > quote.quotePrice) { + return { fillable: false, reason: "badprice" }; + } + else if (side == 'b' && price < quote.quotePrice) { + return { fillable: false, reason: "badprice" }; + } return { fillable: true, reason: null, walletId: goodWalletIds[0]}; } From 9a7620dd2c1e60584288186806165e4b7cccf456 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sat, 2 Apr 2022 21:42:55 +0200 Subject: [PATCH 125/160] enable different fee tokens --- marketmaker.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 28f6f63..2433d0a 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -16,6 +16,7 @@ const MARKETS = {}; const CHAINLINK_PROVIDERS = {}; const UNISWAP_V3_PROVIDERS = {}; const PAST_ORDER_LIST = {}; +const FEE_TOKENS = []; let uniswap_error_counter = 0; let chainlink_error_counter = 0; @@ -195,6 +196,18 @@ async function handleMessage(json) { const newBaseFee = MARKETS[marketId].baseFee; const newQuoteFee = MARKETS[marketId].quoteFee; console.log(`marketinfo ${marketId} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); + if( + marketInfo.baseAsset.enabledForFees && + !FEE_TOKENS.includes(marketInfo.baseAsset.id) + ) { + FEE_TOKENS.push(marketInfo.baseAsset.id); + } + if( + marketInfo.quoteAsset.enabledForFees && + !FEE_TOKENS.includes(marketInfo.quoteAsset.id) + ) { + FEE_TOKENS.push(marketInfo.quoteAsset.id); + } break default: break @@ -415,11 +428,15 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { zigzagws.send(JSON.stringify(orderCommitMsg)); return; } + // select token to match user's fee token + const feeToken = (FEE_TOKENS.includes(swapOffer.tokenSell)) + ? swapOffer.tokenSell + : 0 const randInt = (Math.random()*1000).toFixed(0); console.time('syncswap' + randInt); const swap = await wallet['syncWallet'].syncSwap({ orders: [swapOffer, fillOrder], - feeToken: "ETH", + feeToken: feeToken, nonce: fillOrder.nonce }); const txHash = swap.txHash.split(":")[1]; From abab7f24ddc806eb6048643f8b98ee82c5e9edc7 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sat, 2 Apr 2022 21:53:01 +0200 Subject: [PATCH 126/160] add option for default fee token --- README.md | 17 +++++++++++++++++ marketmaker.js | 25 ++++++++++++++++++------- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2b62583..c50474c 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,23 @@ node marketmaker.js ## Settings +#### Fee Token + +With the defualt setting the bot will pay the zkSync fee wiht the same token as the user (buy currency for the bot). You can chose to override that by a fixed fee token. Check if your tokens is avalible to pay fees on zkSync [here](https://zkscan.io/explorer/tokens). + +``` +{ + "cryptowatchApiKey": "aaaaxxx", + "ethPrivKeys": [ + "", + "" + ], + "zigzagChainId": 1, + "zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com", + "feeToken": "ETH", <- add this line if you eg. want to pay the fees in Ethereum + "pairs": { +``` + #### Mainnet zkSync - "zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com" - "zigzagChainId": 1 diff --git a/marketmaker.js b/marketmaker.js index 2433d0a..4aeddcb 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -16,7 +16,8 @@ const MARKETS = {}; const CHAINLINK_PROVIDERS = {}; const UNISWAP_V3_PROVIDERS = {}; const PAST_ORDER_LIST = {}; -const FEE_TOKENS = []; +const FEE_TOKEN = null; +const FEE_TOKEN_LIST = []; let uniswap_error_counter = 0; let chainlink_error_counter = 0; @@ -30,6 +31,9 @@ else { const mmConfigFile = fs.readFileSync("config.json", "utf8"); MM_CONFIG = JSON.parse(mmConfigFile); } +if (MM_CONFIG.feeToken) { + FEE_TOKEN = MM_CONFIG.feeToken; +} let activePairs = []; for (let marketId in MM_CONFIG.pairs) { const pair = MM_CONFIG.pairs[marketId]; @@ -196,17 +200,18 @@ async function handleMessage(json) { const newBaseFee = MARKETS[marketId].baseFee; const newQuoteFee = MARKETS[marketId].quoteFee; console.log(`marketinfo ${marketId} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); + if (FEE_TOKEN) break if( marketInfo.baseAsset.enabledForFees && - !FEE_TOKENS.includes(marketInfo.baseAsset.id) + !FEE_TOKEN_LIST.includes(marketInfo.baseAsset.id) ) { - FEE_TOKENS.push(marketInfo.baseAsset.id); + FEE_TOKEN_LIST.push(marketInfo.baseAsset.id); } if( marketInfo.quoteAsset.enabledForFees && - !FEE_TOKENS.includes(marketInfo.quoteAsset.id) + !FEE_TOKEN_LIST.includes(marketInfo.quoteAsset.id) ) { - FEE_TOKENS.push(marketInfo.quoteAsset.id); + FEE_TOKEN_LIST.push(marketInfo.quoteAsset.id); } break default: @@ -429,9 +434,15 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { return; } // select token to match user's fee token - const feeToken = (FEE_TOKENS.includes(swapOffer.tokenSell)) + let feeToken; + if (FEE_TOKEN) { + feeToken = FEE_TOKEN + } else { + feeToken = (FEE_TOKEN_LIST.includes(swapOffer.tokenSell)) ? swapOffer.tokenSell - : 0 + : 'ETH' + } + const randInt = (Math.random()*1000).toFixed(0); console.time('syncswap' + randInt); const swap = await wallet['syncWallet'].syncSwap({ From d89260b8f69bcca584613050f7063c1885bd05a6 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 20 Apr 2022 09:10:19 +0200 Subject: [PATCH 127/160] PRICE_FEED's in lowercase --- marketmaker.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index ebb94ec..749dffa 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -486,6 +486,14 @@ async function setupPriceFeeds() { } const primaryPriceFeed = pairConfig.priceFeedPrimary; const secondaryPriceFeed = pairConfig.priceFeedSecondary; + + // parse keys to lower case to match later PRICE_FEED keys + if (primaryPriceFeed) { + MM_CONFIG.pairs[market].priceFeedPrimary = primaryPriceFeed.toLowerCase(); + } + if (secondaryPriceFeed) { + MM_CONFIG.pairs[market].priceFeedSecondary = secondaryPriceFeed.toLowerCase(); + } [primaryPriceFeed, secondaryPriceFeed].forEach(priceFeed => { if(!priceFeed) { return; } const [provider, id] = priceFeed.split(':'); @@ -639,7 +647,7 @@ async function uniswapV3Setup(uniswapV3Address) { tokenProvier1.decimals() ]); - const key = 'uniswapV3:' + address; + const key = 'uniswapv3:' + address; const decimalsRatio = (10**decimals0 / 10**decimals1); UNISWAP_V3_PROVIDERS[key] = [provider, decimalsRatio]; From f5da3c004e908e99cb4f8f1f20e8ff4090d901ef Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 4 May 2022 00:34:32 +0200 Subject: [PATCH 128/160] fix/check if account has balance --- marketmaker.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 824fd55..4f70f3e 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -762,11 +762,13 @@ async function afterFill(chainId, orderId, wallet) { const sellTokenParsed = syncProvider.tokenSet.parseToken ( order.sellSymbol, order.sellQuantity - ); - const oldbuyTokenParsed = ethers.BigNumber.from(account_state[order.buySymbol]); - const oldsellTokenParsed = ethers.BigNumber.from(account_state[order.sellSymbol]); - account_state[order.buySymbol] = (oldbuyTokenParsed.add(buyTokenParsed)).toString(); - account_state[order.sellSymbol] = (oldsellTokenParsed.sub(sellTokenParsed)).toString(); + ); + const oldBuyBalance = account_state[order.buySymbol] ? account_state[order.buySymbol] : '0'; + const oldSellBalance = account_state[order.sellSymbol] ? account_state[order.sellSymbol] : '0'; + const oldBuyTokenParsed = ethers.BigNumber.from(oldBuyBalance); + const oldSellTokenParsed = ethers.BigNumber.from(oldSellBalance); + account_state[order.buySymbol] = (oldBuyTokenParsed.add(buyTokenParsed)).toString(); + account_state[order.sellSymbol] = (oldSellTokenParsed.sub(sellTokenParsed)).toString(); const indicateMarket = {}; indicateMarket[marketId] = mmConfig; From b69d18a8447c001b8f57306bbe375b02361f47f1 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sat, 7 May 2022 18:50:17 +0200 Subject: [PATCH 129/160] fix const to let --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 3aaba3a..5f649b3 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -16,8 +16,8 @@ const MARKETS = {}; const CHAINLINK_PROVIDERS = {}; const UNISWAP_V3_PROVIDERS = {}; const PAST_ORDER_LIST = {}; -const FEE_TOKEN = null; const FEE_TOKEN_LIST = []; +let FEE_TOKEN = null; let uniswap_error_counter = 0; let chainlink_error_counter = 0; From 0ac10e0feca34411f4ee892362c5723e2d9cb5f1 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 8 May 2022 15:14:52 +0200 Subject: [PATCH 130/160] fix readme --- README.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c50474c..060e167 100644 --- a/README.md +++ b/README.md @@ -116,12 +116,14 @@ Example: ###### Chainlink With chainlink you have access to price oracles via blockchain. The requests are read-calls to a smart contract. The public ethers provider might be too slow for a higher number of pairs or at times of high demand. Therefore, it might be needed to have access to an Infura account (100000 Requests/Day for free). You can get an endpoint for your market maker (like https://mainnet.infura.io/v3/...), You can add this with the `infuraUrl` field in `config.json`, like this: ``` -"ETH-USDC": { - "infuraUrl": "https://mainnet.infura.io/v3/xxxxxxxx", - "zigzagChainId": 1, - "zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com", - .... -} + +"infuraUrl": "https://mainnet.infura.io/v3/xxxxxxxx", +"pairs": { + "ETH-USDC": { + "zigzagChainId": 1, + "zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com", + .... + } ``` You can get the available market contracts [here.](https://docs.chain.link/docs/ethereum-addresses/)Add those to you pair config as "chainlink:
", like this: ``` @@ -136,12 +138,13 @@ You can get the available market contracts [here.](https://docs.chain.link/docs/ ###### UniswapV3 With uniswapV3 you have access to price feed's via blockchain. The requests are read-calls to a smart contract. The public ethers provider might be too slow for a higher number of pairs or at times of high demand. Therefore, it might be needed to have access to an Infura account (100000 Requests/Day for free). You can get an endpoint for your market maker (like https://mainnet.infura.io/v3/...), You can add this with the `infuraUrl` field in `config.json`, like this: ``` -"ETH-USDC": { - "infuraUrl": "https://mainnet.infura.io/v3/xxxxxxxx", - "zigzagChainId": 1, - "zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com", - .... -} +"infuraUrl": "https://mainnet.infura.io/v3/xxxxxxxx", +"pairs": { + "ETH-USDC": { + "zigzagChainId": 1, + "zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com", + .... + } ``` You can get the available market contracts [here.](https://info.uniswap.org) Select a token and then a pool matching the pair you plan to market make. Make sure base and quote tokens match (USDC-ETH don't work for ETH-USDC). After selecting a pool, you can see the adress in the browser URL. Add that to your pair config as "uniswapv3:
", like this: ``` From 4ce02ca099ca861df646750e63f545714b2bd1e2 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 8 May 2022 15:25:46 +0200 Subject: [PATCH 131/160] indicateLiquidity min size $10 --- marketmaker.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 5f649b3..77f14b0 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -746,16 +746,25 @@ function indicateLiquidity (pairs = MM_CONFIG.pairs) { const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize); - const splits = mmConfig.numOrdersIndicated || 10; + // default splits + let buySplits = mmConfig.numOrdersIndicated || 10; + let sellSplits = mmConfig.numOrdersIndicated || 10; + + // check if balance passes the min liquidity size - 10 USD + const usdBaseBalance = baseBalance * marketInfo.baseAsset.usdPrice; + const usdQuoteBalance = quoteBalance * marketInfo.quoteAsset.usdPrice; + if (usdBaseBalance < (10 * buySplits)) buySplits = Math.floor(usdBaseBalance / 10) + if (usdQuoteBalance < (10 * sellSplits)) sellSplits = Math.floor(usdQuoteBalance / 10) + const liquidity = []; for (let i=1; i <= splits; i++) { - const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i/splits)); - const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i/splits)); + const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i/buySplits)); + const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i/sellSplits)); if ((['b','d']).includes(side)) { - liquidity.push(["b", buyPrice, maxBuySize / splits, expires]); + liquidity.push(["b", buyPrice, maxBuySize / buySplits, expires]); } if ((['s','d']).includes(side)) { - liquidity.push(["s", sellPrice, maxSellSize / splits, expires]); + liquidity.push(["s", sellPrice, maxSellSize / sellSplits, expires]); } } const msg = { op: "indicateliq2", args: [CHAIN_ID, marketId, liquidity] }; From 675f4db5a4fa8d49a22349e780573cf7f28b5f5e Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Tue, 10 May 2022 21:13:04 +0200 Subject: [PATCH 132/160] fix split loops --- marketmaker.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 77f14b0..cb0ac8d 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -757,16 +757,19 @@ function indicateLiquidity (pairs = MM_CONFIG.pairs) { if (usdQuoteBalance < (10 * sellSplits)) sellSplits = Math.floor(usdQuoteBalance / 10) const liquidity = []; - for (let i=1; i <= splits; i++) { + for (let i=1; i <= buySplits; i++) { const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i/buySplits)); - const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i/sellSplits)); if ((['b','d']).includes(side)) { liquidity.push(["b", buyPrice, maxBuySize / buySplits, expires]); } - if ((['s','d']).includes(side)) { - liquidity.push(["s", sellPrice, maxSellSize / sellSplits, expires]); - } } + for (let i=1; i <= sellSplits; i++) { + const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i/sellSplits)); + if ((['s','d']).includes(side)) { + liquidity.push(["s", sellPrice, maxSellSize / sellSplits, expires]); + } + } + const msg = { op: "indicateliq2", args: [CHAIN_ID, marketId, liquidity] }; try { zigzagws.send(JSON.stringify(msg)); From 866c1674c968c892eb1a420bba292edad5db0820 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 11 May 2022 08:29:56 +0200 Subject: [PATCH 133/160] move balance check to use quote result --- marketmaker.js | 50 ++++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index cb0ac8d..455be63 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -233,30 +233,7 @@ function isOrderFillable(order) { const expires = order[7]; const side = order[3]; const price = order[4]; - const sellCurrency = (side === 's') ? market.quoteAsset.symbol : market.baseAsset.symbol; - const sellDecimals = (side === 's') ? market.quoteAsset.decimals : market.baseAsset.decimals; - const sellQuantity = (side === 's') ? quoteQuantity : baseQuantity; - const neededBalanceBN = sellQuantity * 10**sellDecimals; - let goodWalletIds = []; - Object.keys(WALLETS).forEach(accountId => { - const walletBalance = WALLETS[accountId]['account_state'].committed.balances[sellCurrency]; - if (Number(walletBalance) > (neededBalanceBN * 1.05)) { - goodWalletIds.push(accountId); - } - }); - - if (goodWalletIds.length === 0) { - return { fillable: false, reason: "badbalance" }; - } - - goodWalletIds = goodWalletIds.filter(accountId => { - return !WALLETS[accountId]['ORDER_BROADCASTING']; - }); - - if (goodWalletIds.length === 0) { - return { fillable: false, reason: "sending order already" }; - } - + const now = Date.now() / 1000 | 0; if (now > expires) { @@ -286,6 +263,31 @@ function isOrderFillable(order) { else if (side == 'b' && price < quote.quotePrice) { return { fillable: false, reason: "badprice" }; } + + const sellCurrency = (side === 's') ? market.quoteAsset.symbol : market.baseAsset.symbol; + const sellDecimals = (side === 's') ? market.quoteAsset.decimals : market.baseAsset.decimals; + const sellQuantity = (side === 's') ? quote.quoteQuantity : baseQuantity; + const neededBalanceBN = sellQuantity * 10**sellDecimals; + let goodWalletIds = []; + Object.keys(WALLETS).forEach(accountId => { + const walletBalance = WALLETS[accountId]['account_state'].committed.balances[sellCurrency]; + if (Number(walletBalance) > (neededBalanceBN * 1.05)) { + goodWalletIds.push(accountId); + } + }); + + if (goodWalletIds.length === 0) { + return { fillable: false, reason: "badbalance" }; + } + + goodWalletIds = goodWalletIds.filter(accountId => { + return !WALLETS[accountId]['ORDER_BROADCASTING']; + }); + + if (goodWalletIds.length === 0) { + return { fillable: false, reason: "sending order already" }; + } + return { fillable: true, reason: null, walletId: goodWalletIds[0]}; } From 4e5a8f9b05400dba3ef006fe05876d0956d30f2a Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Fri, 13 May 2022 22:15:41 +0200 Subject: [PATCH 134/160] fix/quoteQuantity should be a Number --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 455be63..dbc2d5e 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -315,7 +315,7 @@ function genQuote(chainId, marketId, side, baseQuantity) { else if (side === 's') { quoteQuantity = (baseQuantity - market.baseFee) * primaryPrice * (1 - SPREAD); } - const quotePrice = (quoteQuantity / baseQuantity).toPrecision(6); + const quotePrice = Number((quoteQuantity / baseQuantity).toPrecision(6)); if (quotePrice < 0) throw new Error("Amount is inadequate to pay fee"); if (isNaN(quotePrice)) throw new Error("Internal Error. No price generated."); return { quotePrice, quoteQuantity }; From f0f30915e99b3367495b4616b14f6ed8c13e2092 Mon Sep 17 00:00:00 2001 From: Connor Knabe Date: Sat, 14 May 2022 17:28:10 -0500 Subject: [PATCH 135/160] Fixing typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 060e167..308b411 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ node marketmaker.js #### Fee Token -With the defualt setting the bot will pay the zkSync fee wiht the same token as the user (buy currency for the bot). You can chose to override that by a fixed fee token. Check if your tokens is avalible to pay fees on zkSync [here](https://zkscan.io/explorer/tokens). +With the defualt setting the bot will pay the zkSync fee wiht the same token as the user (buy currency for the bot). You can chose to override that by a fixed fee token. Check if your tokens is available to pay fees on zkSync [here](https://zkscan.io/explorer/tokens). ``` { From 487a251cd6d2da47f3905f6b1f23b11f53d065c7 Mon Sep 17 00:00:00 2001 From: Christian Koopmann Date: Tue, 17 May 2022 15:40:31 +0800 Subject: [PATCH 136/160] Add uniswapV3 to the price feed list --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 060e167..2379c83 100644 --- a/README.md +++ b/README.md @@ -85,11 +85,12 @@ Orders coming in below the `minSpread` from the price feed will not be filled. T #### Price Feed -There are 3 modes available with a 4th on the way. +There are 4 modes available with a 5th on the way. * `cryptowatch`: Follows an external price oracle. * `chainlink` : Follows an external price oracle. Chainlink is WEB3 and might be slower then cryptowatch. * `constant`: Sets an fixed price and market makes around that price. Can be combined with single-sided liquidity to simulate limit orders. +* `uniswapV3`: Reads prices on-chain from a specified uniswapV3 pool * `independent`: Under development. The price is set independent of a price feed. **Warning:** Make sure your price feed is close to the price you see on zigzag. **Otherwise, your mm can lose money!** From 56dba8abd14e27bdf762ad9ce8856389b01c382b Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sat, 21 May 2022 18:11:44 +0200 Subject: [PATCH 137/160] fix sell and buy order splits --- marketmaker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 455be63..9e522ee 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -755,8 +755,8 @@ function indicateLiquidity (pairs = MM_CONFIG.pairs) { // check if balance passes the min liquidity size - 10 USD const usdBaseBalance = baseBalance * marketInfo.baseAsset.usdPrice; const usdQuoteBalance = quoteBalance * marketInfo.quoteAsset.usdPrice; - if (usdBaseBalance < (10 * buySplits)) buySplits = Math.floor(usdBaseBalance / 10) - if (usdQuoteBalance < (10 * sellSplits)) sellSplits = Math.floor(usdQuoteBalance / 10) + if (usdQuoteBalance < (10 * buySplits)) buySplits = Math.floor(usdQuoteBalance / 10) + if (usdBaseBalance < (10 * sellSplits)) sellSplits = Math.floor(usdBaseBalance / 10) const liquidity = []; for (let i=1; i <= buySplits; i++) { From 3e244c63c4de6ccfa8ac508b0e609914d78d1c6c Mon Sep 17 00:00:00 2001 From: Trooper <95502080+TrooperCrypto@users.noreply.github.com> Date: Fri, 27 May 2022 17:19:08 +0200 Subject: [PATCH 138/160] update package.json dont use newer then 0.11.6 zkSync skd. V0.12 has breaking changes --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8fb851a..19da3cb 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "ethers": "^5.5.1", "node-fetch": "^3.1.0", "ws": "^8.2.3", - "zksync": "^0.11.6" + "zksync": "0.11.6" }, "type": "module", "devDependencies": {}, From 611c1bb32cc1995b3ba5c4628cdf69ae4ec47068 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 29 May 2022 21:57:53 +0200 Subject: [PATCH 139/160] less frequent indicateLiquidity --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 9e522ee..07abde8 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -118,7 +118,7 @@ zigzagws.on('error', console.error); function onWsOpen() { zigzagws.on('message', handleMessage); fillOrdersInterval = setInterval(fillOpenOrders, 200); - indicateLiquidityInterval = setInterval(indicateLiquidity, 5000); + indicateLiquidityInterval = setInterval(indicateLiquidity, 12500); for (let market in MM_CONFIG.pairs) { if (MM_CONFIG.pairs[market].active) { const msg = {op:"subscribemarket", args:[CHAIN_ID, market]}; From 48fbc3176c088ebee8672b5f09dae5e120bc4b70 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 29 May 2022 21:58:17 +0200 Subject: [PATCH 140/160] no need for splits under 1k --- marketmaker.js | 44 ++++---------------------------------------- 1 file changed, 4 insertions(+), 40 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 07abde8..ca6bf54 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -105,10 +105,6 @@ try { // Update account state loop setInterval(updateAccountState, 900000); -// Log mm balance over all accounts -logBalance(); -setInterval(logBalance, 3 * 60 * 60 * 1000); // 3h - let fillOrdersInterval, indicateLiquidityInterval; let zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); zigzagws.on('open', onWsOpen); @@ -748,13 +744,12 @@ function indicateLiquidity (pairs = MM_CONFIG.pairs) { const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize); - // default splits - let buySplits = mmConfig.numOrdersIndicated || 10; - let sellSplits = mmConfig.numOrdersIndicated || 10; - - // check if balance passes the min liquidity size - 10 USD + // dont do splits if under 1000 USD const usdBaseBalance = baseBalance * marketInfo.baseAsset.usdPrice; const usdQuoteBalance = quoteBalance * marketInfo.quoteAsset.usdPrice; + let buySplits = (usdQuoteBalance < 1000) ? 1 : (mmConfig.numOrdersIndicated || 4); + let sellSplits = (usdBaseBalance < 1000) ? 1 : (mmConfig.numOrdersIndicated || 4); + if (usdQuoteBalance < (10 * buySplits)) buySplits = Math.floor(usdQuoteBalance / 10) if (usdBaseBalance < (10 * sellSplits)) sellSplits = Math.floor(usdBaseBalance / 10) @@ -928,34 +923,3 @@ async function updateAccountState() { } } -async function logBalance() { - try { - await updateAccountState(); - // fetch all balances over all wallets per token - const balance = {}; - Object.keys(WALLETS).forEach(accountId => { - const committedBalaces = WALLETS[accountId]['account_state'].committed.balances; - Object.keys(committedBalaces).forEach(token => { - if(balance[token]) { - balance[token] = balance[token] + parseInt(committedBalaces[token]); - } else { - balance[token] = parseInt(committedBalaces[token]); - } - }); - }); - // get token price and total in USD - let sum = 0; - await Promise.all(Object.keys(balance).map(async token => { - const price = await syncProvider.getTokenPrice(token.toString()); - const tokenNumber = await syncProvider.tokenSet.formatToken(token, balance[token].toString()) - sum = sum + price * tokenNumber; - })); - - // log to CVS - const date = new Date().toISOString(); - const content = date + ";" + sum.toFixed(2) + "\n"; - fs.writeFile('price_csv.txt', content, { flag: 'a+' }, err => {}); - } catch(err) { - // pass - } -} From 7cffef3e861a6363fe46b3e6fdcc2e049ebe3025 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Mon, 6 Jun 2022 03:57:27 +0200 Subject: [PATCH 141/160] README - Security advice --- README.md | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 2c6ac27..9443c1a 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,18 @@ This market maker uses existing price feeds to set bids and asks for a market. F Soon we will add the ability to run standalone markets and this will not be an issue. + +## Security advice + +__Do not share your private key__ + +Running the bot on a VPS has many advantages. But you need to make sure your system is safe. If someone gains access to your system, all files can be compromised. There a quite a few good guides about how to keep your VPS safe: + +- An Introduction to Securing your Linux VPS - [Digitalocean](https://www.digitalocean.com/community/tutorials/an-introduction-to-securing-your-linux-vps) +- 9 Ways To Keep Your VPS Secure - [namecheap](https://www.namecheap.com/blog/9-ways-to-keep-your-vps-secure/) + + + ## Requirements * Activated zkSync account @@ -30,6 +42,18 @@ To run the marketmaker: node marketmaker.js ``` +## Configuration Via Environment Variables + +It is __recommended__ to use environment variables to set your private keys. You can set `ETH_PRIVKEY`, `CRYPTOWATCH_API_KEY` and `INFURA_URL` using them. You can set them using `ETH_PRIVKEY=0x____`. For more informations on private keys read [this](https://linuxize.com/post/how-to-set-and-list-environment-variables-in-linux/). + +If your hosting service requires you to pass in configs via environment variables you can compress `config.json`: + +``` +cat config.json | tr -d ' ' | tr -d '\n' +``` + +and set it to the value of the `MM_CONFIG` environment variable to override the config file. + ## Settings #### Fee Token @@ -294,15 +318,3 @@ Sell the rip: "active": true } ``` - -## Configuration Via Environment Variables - -If your hosting service requires you to pass in configs via environment variables you can compress `config.json`: - -``` -cat config.json | tr -d ' ' | tr -d '\n' -``` - -and set it to the value of the `MM_CONFIG` environment variable to override the config file. - -You can also override the private key in the config file with the `ETH_PRIVKEY` environment variable, and the cryptowatch API key with the `CRYPTOWATCH_API_KEY` environment variable, and the Infura provider url with `INFURA_URL` From 53112965d72ce98b7ee8fa7bfa8a1a13b2f9bdbf Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Mon, 6 Jun 2022 10:57:35 +0200 Subject: [PATCH 142/160] improve wording --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9443c1a..a4d4761 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Soon we will add the ability to run standalone markets and this will not be an i __Do not share your private key__ -Running the bot on a VPS has many advantages. But you need to make sure your system is safe. If someone gains access to your system, all files can be compromised. There a quite a few good guides about how to keep your VPS safe: +Running the bot on a VPS has many advantages. But you need to make sure your system is safe. If someone gains access to your system, all files can be compromised __including your private key__. There a quite a few good guides about how to keep your VPS safe: - An Introduction to Securing your Linux VPS - [Digitalocean](https://www.digitalocean.com/community/tutorials/an-introduction-to-securing-your-linux-vps) - 9 Ways To Keep Your VPS Secure - [namecheap](https://www.namecheap.com/blog/9-ways-to-keep-your-vps-secure/) From 3645f09ddd0098714c9482f64b0bf429b4b876fc Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 12 Jun 2022 15:24:50 +0200 Subject: [PATCH 143/160] update/reduce default liquidity interval --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index ca6bf54..d61192b 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -114,7 +114,7 @@ zigzagws.on('error', console.error); function onWsOpen() { zigzagws.on('message', handleMessage); fillOrdersInterval = setInterval(fillOpenOrders, 200); - indicateLiquidityInterval = setInterval(indicateLiquidity, 12500); + indicateLiquidityInterval = setInterval(indicateLiquidity, 5000); for (let market in MM_CONFIG.pairs) { if (MM_CONFIG.pairs[market].active) { const msg = {op:"subscribemarket", args:[CHAIN_ID, market]}; From 9a7f83582e2a91876ed5781cd1c288130572f37e Mon Sep 17 00:00:00 2001 From: Trooper Date: Sun, 19 Jun 2022 20:16:39 +0200 Subject: [PATCH 144/160] add min eth balance of 0.01 --- marketmaker.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index d61192b..f2e6fd4 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -89,11 +89,9 @@ try { console.log(signKeyResult); } let accountId = await syncWallet.getAccountId(); - let account_state = await syncWallet.getAccountState(); WALLETS[accountId] = { 'ethWallet': ethWallet, 'syncWallet': syncWallet, - 'account_state': account_state, 'ORDER_BROADCASTING': false, } } @@ -103,7 +101,8 @@ try { } // Update account state loop -setInterval(updateAccountState, 900000); +await updateAccountState(); +setInterval(updateAccountState, 150000); let fillOrdersInterval, indicateLiquidityInterval; let zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); @@ -912,14 +911,23 @@ function rememberOrder(chainId, marketId, orderId, price, sellSymbol, sellQuanti } async function updateAccountState() { + let lowBalance = false; try { - Object.keys(WALLETS).forEach(accountId => { - (WALLETS[accountId]['syncWallet']).getAccountState().then((state) => { - WALLETS[accountId]['account_state'] = state; - }) + const promise = Object.keys(WALLETS).map(accountId => { + const accountState = WALLETS[accountId].syncWallet.getAccountState(); + const ethBalance = accountState?.committed?.balances?.ETH + ? ethers.utils.formatEther(accountState.committed.balances.ETH).toNumber() + : 0 + if (ethBalance < 0.01) { + lowBalance = true; + return; + } + WALLETS[accountId]['account_state'] = accountState; }); + await Promise.all(promise); } catch(err) { // pass } + if (lowBalance) throw new Error('Your ETH balance is to low to run the marketmaker bot. You need at least 0.01 ETH!') } From 02ccfd1138f147bbea7cc6ea0410e8dee18b4fca Mon Sep 17 00:00:00 2001 From: Trooper Date: Sun, 19 Jun 2022 20:18:57 +0200 Subject: [PATCH 145/160] revert --- marketmaker.js | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index f2e6fd4..d61192b 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -89,9 +89,11 @@ try { console.log(signKeyResult); } let accountId = await syncWallet.getAccountId(); + let account_state = await syncWallet.getAccountState(); WALLETS[accountId] = { 'ethWallet': ethWallet, 'syncWallet': syncWallet, + 'account_state': account_state, 'ORDER_BROADCASTING': false, } } @@ -101,8 +103,7 @@ try { } // Update account state loop -await updateAccountState(); -setInterval(updateAccountState, 150000); +setInterval(updateAccountState, 900000); let fillOrdersInterval, indicateLiquidityInterval; let zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); @@ -911,23 +912,14 @@ function rememberOrder(chainId, marketId, orderId, price, sellSymbol, sellQuanti } async function updateAccountState() { - let lowBalance = false; try { - const promise = Object.keys(WALLETS).map(accountId => { - const accountState = WALLETS[accountId].syncWallet.getAccountState(); - const ethBalance = accountState?.committed?.balances?.ETH - ? ethers.utils.formatEther(accountState.committed.balances.ETH).toNumber() - : 0 - if (ethBalance < 0.01) { - lowBalance = true; - return; - } - WALLETS[accountId]['account_state'] = accountState; + Object.keys(WALLETS).forEach(accountId => { + (WALLETS[accountId]['syncWallet']).getAccountState().then((state) => { + WALLETS[accountId]['account_state'] = state; + }) }); - await Promise.all(promise); } catch(err) { // pass } - if (lowBalance) throw new Error('Your ETH balance is to low to run the marketmaker bot. You need at least 0.01 ETH!') } From 3aa573c14c07b98295cfeefcb88745af0d44ae92 Mon Sep 17 00:00:00 2001 From: Trooper Date: Fri, 24 Jun 2022 19:28:53 +0200 Subject: [PATCH 146/160] fix number of splits for price = 0 --- marketmaker.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index d61192b..9888b09 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -747,11 +747,11 @@ function indicateLiquidity (pairs = MM_CONFIG.pairs) { // dont do splits if under 1000 USD const usdBaseBalance = baseBalance * marketInfo.baseAsset.usdPrice; const usdQuoteBalance = quoteBalance * marketInfo.quoteAsset.usdPrice; - let buySplits = (usdQuoteBalance < 1000) ? 1 : (mmConfig.numOrdersIndicated || 4); - let sellSplits = (usdBaseBalance < 1000) ? 1 : (mmConfig.numOrdersIndicated || 4); + let buySplits = (usdQuoteBalance && usdQuoteBalance < 1000) ? 1 : (mmConfig.numOrdersIndicated || 4); + let sellSplits = (usdBaseBalance && usdBaseBalance < 1000) ? 1 : (mmConfig.numOrdersIndicated || 4); - if (usdQuoteBalance < (10 * buySplits)) buySplits = Math.floor(usdQuoteBalance / 10) - if (usdBaseBalance < (10 * sellSplits)) sellSplits = Math.floor(usdBaseBalance / 10) + if (usdQuoteBalance && usdQuoteBalance < (10 * buySplits)) buySplits = Math.floor(usdQuoteBalance / 10) + if (usdBaseBalance && usdBaseBalance < (10 * sellSplits)) sellSplits = Math.floor(usdBaseBalance / 10) const liquidity = []; for (let i=1; i <= buySplits; i++) { From 903e1397006a74ee981cfe3a6ddf979e1dfa5ea5 Mon Sep 17 00:00:00 2001 From: Trooper Date: Mon, 27 Jun 2022 20:18:01 +0200 Subject: [PATCH 147/160] add Invert price feed --- README.md | 14 ++ marketmaker.js | 475 +++++++++++++++++++++++++------------------------ 2 files changed, 254 insertions(+), 235 deletions(-) diff --git a/README.md b/README.md index a4d4761..dc27c90 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,20 @@ With constant mode, you can set a fixed price to market make. The bot will not c } ``` +###### Invert price feed +For some pairs, you might just find a price feed for the inverse of the pair. If you want to mm for ZZ-USDC and only find a USDC-ZZ price feed. In those cases, you need to invert the fee. This will only work if the secondary price feed is inverted as well or set to null. +Example: +``` +"ETH-USDC": { + "side": "d", + "priceFeedPrimary": "uniswapv3:0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", + "priceFeedSecondary": null, + "invert": true, + .... +} +``` + + ## Pair Options These pair options can be set for each pair individual. You can even use more then on option per pair (though they might cancel each other out). diff --git a/marketmaker.js b/marketmaker.js index 9888b09..ae26203 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -32,7 +32,7 @@ else { MM_CONFIG = JSON.parse(mmConfigFile); } if (MM_CONFIG.feeToken) { - FEE_TOKEN = MM_CONFIG.feeToken; + FEE_TOKEN = MM_CONFIG.feeToken; } let activePairs = []; for (let marketId in MM_CONFIG.pairs) { @@ -48,7 +48,7 @@ const CHAIN_ID = parseInt(MM_CONFIG.zigzagChainId); const ETH_NETWORK = (CHAIN_ID === 1) ? "mainnet" : "rinkeby"; let ethersProvider; const providerUrl = (process.env.INFURA_URL || MM_CONFIG.infuraUrl); -if(providerUrl && ETH_NETWORK=="mainnet") { +if (providerUrl && ETH_NETWORK == "mainnet") { ethersProvider = ethers.getDefaultProvider(providerUrl); } else { ethersProvider = ethers.getDefaultProvider(ETH_NETWORK); @@ -62,7 +62,7 @@ try { syncProvider = await zksync.getDefaultProvider(ETH_NETWORK); const keys = []; const ethPrivKey = (process.env.ETH_PRIVKEY || MM_CONFIG.ethPrivKey); - if(ethPrivKey && ethPrivKey != "") { keys.push(ethPrivKey); } + if (ethPrivKey && ethPrivKey != "") { keys.push(ethPrivKey); } let ethPrivKeys; if (process.env.ETH_PRIVKEYS) { ethPrivKeys = JSON.parse(process.env.ETH_PRIVKEYS); @@ -70,14 +70,14 @@ try { else { ethPrivKeys = MM_CONFIG.ethPrivKeys; } - if(ethPrivKeys && ethPrivKeys.length > 0) { - ethPrivKeys.forEach( key => { - if(key != "" && !keys.includes(key)) { + if (ethPrivKeys && ethPrivKeys.length > 0) { + ethPrivKeys.forEach(key => { + if (key != "" && !keys.includes(key)) { keys.push(key); } }); } - for(let i=0; i { clearInterval(fillOrdersInterval) @@ -138,12 +138,12 @@ function onWsClose () { async function handleMessage(json) { const msg = JSON.parse(json); if (!(["lastprice", "liquidity2", "fillstatus", "marketinfo"]).includes(msg.op)) console.log(json.toString()); - switch(msg.op) { + switch (msg.op) { case 'error': const accountId = msg.args?.[1]; - if(msg.args[0] == 'fillrequest' && accountId) { + if (msg.args[0] == 'fillrequest' && accountId) { WALLETS[accountId]['ORDER_BROADCASTING'] = false; - } + } break; case 'orders': const orders = msg.args[0]; @@ -154,8 +154,8 @@ async function handleMessage(json) { if (fillable.fillable) { sendFillRequest(order, fillable.walletId); } else if ([ - "sending order already", - "badprice" + "sending order already", + "badprice" ].includes(fillable.reason)) { OPEN_ORDERS[orderId] = order; } @@ -166,14 +166,14 @@ async function handleMessage(json) { const orderId = msg.args[1]; const fillOrder = msg.args[3]; const wallet = WALLETS[fillOrder.accountId]; - if(!wallet) { - console.error("No wallet with this accountId: "+fillOrder.accountId); + if (!wallet) { + console.error("No wallet with this accountId: " + fillOrder.accountId); break } else { try { await broadcastFill(chainId, orderId, msg.args[2], fillOrder, wallet); } catch (e) { - const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'r',null,e.message]]]} + const orderCommitMsg = { op: "orderstatusupdate", args: [[[chainId, orderId, 'r', null, e.message]]] } zigzagws.send(JSON.stringify(orderCommitMsg)); console.error(e); } @@ -182,8 +182,8 @@ async function handleMessage(json) { break case "marketinfo": const marketInfo = msg.args[0]; - const marketId = marketInfo.alias; - if(!marketId) break + const marketId = marketInfo.alias; + if (!marketId) break let oldBaseFee = "N/A", oldQuoteFee = "N/A"; try { oldBaseFee = MARKETS[marketId].baseFee; @@ -196,18 +196,18 @@ async function handleMessage(json) { const newQuoteFee = MARKETS[marketId].quoteFee; console.log(`marketinfo ${marketId} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); if (FEE_TOKEN) break - if( - marketInfo.baseAsset.enabledForFees && - !FEE_TOKEN_LIST.includes(marketInfo.baseAsset.id) + if ( + marketInfo.baseAsset.enabledForFees && + !FEE_TOKEN_LIST.includes(marketInfo.baseAsset.id) ) { - FEE_TOKEN_LIST.push(marketInfo.baseAsset.id); - } - if( - marketInfo.quoteAsset.enabledForFees && - !FEE_TOKEN_LIST.includes(marketInfo.quoteAsset.id) + FEE_TOKEN_LIST.push(marketInfo.baseAsset.id); + } + if ( + marketInfo.quoteAsset.enabledForFees && + !FEE_TOKEN_LIST.includes(marketInfo.quoteAsset.id) ) { - FEE_TOKEN_LIST.push(marketInfo.quoteAsset.id); - } + FEE_TOKEN_LIST.push(marketInfo.quoteAsset.id); + } break default: break @@ -229,7 +229,7 @@ function isOrderFillable(order) { const expires = order[7]; const side = order[3]; const price = order[4]; - + const now = Date.now() / 1000 | 0; if (now > expires) { @@ -249,7 +249,7 @@ function isOrderFillable(order) { let quote; try { - quote = genQuote(chainId, marketId, side, baseQuantity); + quote = genQuote(chainId, marketId, side, baseQuantity); } catch (e) { return { fillable: false, reason: e.message } } @@ -263,7 +263,7 @@ function isOrderFillable(order) { const sellCurrency = (side === 's') ? market.quoteAsset.symbol : market.baseAsset.symbol; const sellDecimals = (side === 's') ? market.quoteAsset.decimals : market.baseAsset.decimals; const sellQuantity = (side === 's') ? quote.quoteQuantity : baseQuantity; - const neededBalanceBN = sellQuantity * 10**sellDecimals; + const neededBalanceBN = sellQuantity * 10 ** sellDecimals; let goodWalletIds = []; Object.keys(WALLETS).forEach(accountId => { const walletBalance = WALLETS[accountId]['account_state'].committed.balances[sellCurrency]; @@ -284,37 +284,39 @@ function isOrderFillable(order) { return { fillable: false, reason: "sending order already" }; } - return { fillable: true, reason: null, walletId: goodWalletIds[0]}; + return { fillable: true, reason: null, walletId: goodWalletIds[0] }; } function genQuote(chainId, marketId, side, baseQuantity) { - const market = MARKETS[marketId]; - if (CHAIN_ID !== chainId) throw new Error("badchain"); - if (!market) throw new Error("badmarket"); - if (!(['b','s']).includes(side)) throw new Error("badside"); - if (baseQuantity <= 0) throw new Error("badquantity"); - - validatePriceFeed(marketId); - - const mmConfig = MM_CONFIG.pairs[marketId]; - const mmSide = mmConfig.side || 'd'; - if (mmSide !== 'd' && mmSide === side) { - throw new Error("badside"); - } - const primaryPrice = PRICE_FEEDS[mmConfig.priceFeedPrimary]; - if (!primaryPrice) throw new Error("badprice"); - const SPREAD = mmConfig.minSpread + (baseQuantity * mmConfig.slippageRate); - let quoteQuantity; - if (side === 'b') { - quoteQuantity = (baseQuantity * primaryPrice * (1 + SPREAD)) + market.quoteFee; - } - else if (side === 's') { - quoteQuantity = (baseQuantity - market.baseFee) * primaryPrice * (1 - SPREAD); - } - const quotePrice = (quoteQuantity / baseQuantity).toPrecision(6); - if (quotePrice < 0) throw new Error("Amount is inadequate to pay fee"); - if (isNaN(quotePrice)) throw new Error("Internal Error. No price generated."); - return { quotePrice, quoteQuantity }; + const market = MARKETS[marketId]; + if (CHAIN_ID !== chainId) throw new Error("badchain"); + if (!market) throw new Error("badmarket"); + if (!(['b', 's']).includes(side)) throw new Error("badside"); + if (baseQuantity <= 0) throw new Error("badquantity"); + + validatePriceFeed(marketId); + + const mmConfig = MM_CONFIG.pairs[marketId]; + const mmSide = mmConfig.side || 'd'; + if (mmSide !== 'd' && mmSide === side) { + throw new Error("badside"); + } + const primaryPrice = (mmConfig.invert) + ? (1 / PRICE_FEEDS[mmConfig.priceFeedPrimary]) + : PRICE_FEEDS[mmConfig.priceFeedPrimary]; + if (!primaryPrice) throw new Error("badprice"); + const SPREAD = mmConfig.minSpread + (baseQuantity * mmConfig.slippageRate); + let quoteQuantity; + if (side === 'b') { + quoteQuantity = (baseQuantity * primaryPrice * (1 + SPREAD)) + market.quoteFee; + } + else if (side === 's') { + quoteQuantity = (baseQuantity - market.baseFee) * primaryPrice * (1 - SPREAD); + } + const quotePrice = (quoteQuantity / baseQuantity).toPrecision(6); + if (quotePrice < 0) throw new Error("Amount is inadequate to pay fee"); + if (isNaN(quotePrice)) throw new Error("Internal Error. No price generated."); + return { quotePrice, quoteQuantity }; } function validatePriceFeed(marketId) { @@ -344,81 +346,81 @@ function validatePriceFeed(marketId) { // If the secondary price feed varies from the primary price feed by more than 1%, assume something is broken const percentDiff = Math.abs(primaryPrice - secondaryPrice) / primaryPrice; if (percentDiff > 0.03) { - console.error("Primary and secondary price feeds do not match!"); - throw new Error("Circuit breaker triggered"); + console.error("Primary and secondary price feeds do not match!"); + throw new Error("Circuit breaker triggered"); } return true; } async function sendFillRequest(orderreceipt, accountId) { - const chainId = orderreceipt[0]; - const orderId = orderreceipt[1]; - const marketId = orderreceipt[2]; - const market = MARKETS[marketId]; - const baseCurrency = market.baseAssetId; - const quoteCurrency = market.quoteAssetId; - const side = orderreceipt[3]; - const baseQuantity = orderreceipt[5]; - const quoteQuantity = orderreceipt[6]; - const quote = genQuote(chainId, marketId, side, baseQuantity); - let tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol; - if (side === "b") { - tokenSell = market.baseAssetId; - tokenBuy = market.quoteAssetId; - - sellSymbol = market.baseAsset.symbol; - buySymbol = market.quoteAsset.symbol; - // Add 1 bip to to protect against rounding errors - sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals); - buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); - } else if (side === "s") { - tokenSell = market.quoteAssetId; - tokenBuy = market.baseAssetId; - - sellSymbol = market.quoteAsset.symbol; - buySymbol = market.baseAsset.symbol; - // Add 1 bip to to protect against rounding errors - sellQuantity = (quote.quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals); - buyQuantity = (baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); - } - const sellQuantityParsed = syncProvider.tokenSet.parseToken( - tokenSell, - sellQuantity - ); - const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount(sellQuantityParsed); - const tokenRatio = {}; - tokenRatio[tokenBuy] = buyQuantity; - tokenRatio[tokenSell] = sellQuantity; - const oneMinExpiry = (Date.now() / 1000 | 0) + 60; - const orderDetails = { - tokenSell, - tokenBuy, - amount: sellQuantityPacked, - ratio: zksync.utils.tokenRatio(tokenRatio), - validUntil: oneMinExpiry - } - const fillOrder = await WALLETS[accountId].syncWallet.getOrder(orderDetails); - - // Set wallet flag - WALLETS[accountId]['ORDER_BROADCASTING'] = true; - - // ORDER_BROADCASTING should not take longer as 5 sec - setTimeout(function() { - WALLETS[accountId]['ORDER_BROADCASTING'] = false; - }, 5000); - - const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] }; - zigzagws.send(JSON.stringify(resp)); - rememberOrder(chainId, - marketId, - orderId, - quote.quotePrice, - sellSymbol, - sellQuantity, - buySymbol, - buyQuantity - ); + const chainId = orderreceipt[0]; + const orderId = orderreceipt[1]; + const marketId = orderreceipt[2]; + const market = MARKETS[marketId]; + const baseCurrency = market.baseAssetId; + const quoteCurrency = market.quoteAssetId; + const side = orderreceipt[3]; + const baseQuantity = orderreceipt[5]; + const quoteQuantity = orderreceipt[6]; + const quote = genQuote(chainId, marketId, side, baseQuantity); + let tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol; + if (side === "b") { + tokenSell = market.baseAssetId; + tokenBuy = market.quoteAssetId; + + sellSymbol = market.baseAsset.symbol; + buySymbol = market.quoteAsset.symbol; + // Add 1 bip to to protect against rounding errors + sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals); + buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); + } else if (side === "s") { + tokenSell = market.quoteAssetId; + tokenBuy = market.baseAssetId; + + sellSymbol = market.quoteAsset.symbol; + buySymbol = market.baseAsset.symbol; + // Add 1 bip to to protect against rounding errors + sellQuantity = (quote.quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals); + buyQuantity = (baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); + } + const sellQuantityParsed = syncProvider.tokenSet.parseToken( + tokenSell, + sellQuantity + ); + const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount(sellQuantityParsed); + const tokenRatio = {}; + tokenRatio[tokenBuy] = buyQuantity; + tokenRatio[tokenSell] = sellQuantity; + const oneMinExpiry = (Date.now() / 1000 | 0) + 60; + const orderDetails = { + tokenSell, + tokenBuy, + amount: sellQuantityPacked, + ratio: zksync.utils.tokenRatio(tokenRatio), + validUntil: oneMinExpiry + } + const fillOrder = await WALLETS[accountId].syncWallet.getOrder(orderDetails); + + // Set wallet flag + WALLETS[accountId]['ORDER_BROADCASTING'] = true; + + // ORDER_BROADCASTING should not take longer as 5 sec + setTimeout(function () { + WALLETS[accountId]['ORDER_BROADCASTING'] = false; + }, 5000); + + const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] }; + zigzagws.send(JSON.stringify(resp)); + rememberOrder(chainId, + marketId, + orderId, + quote.quotePrice, + sellSymbol, + sellQuantity, + buySymbol, + buyQuantity + ); } async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { @@ -426,21 +428,21 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { const nonce = swapOffer.nonce; const userNonce = NONCES[swapOffer.accountId]; if (nonce <= userNonce) { - const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'r',null,"Order failed userNonce check."]]]} + const orderCommitMsg = { op: "orderstatusupdate", args: [[[chainId, orderId, 'r', null, "Order failed userNonce check."]]] } zigzagws.send(JSON.stringify(orderCommitMsg)); return; } // select token to match user's fee token let feeToken; if (FEE_TOKEN) { - feeToken = FEE_TOKEN + feeToken = FEE_TOKEN } else { - feeToken = (FEE_TOKEN_LIST.includes(swapOffer.tokenSell)) - ? swapOffer.tokenSell - : 'ETH' + feeToken = (FEE_TOKEN_LIST.includes(swapOffer.tokenSell)) + ? swapOffer.tokenSell + : 'ETH' } - - const randInt = (Math.random()*1000).toFixed(0); + + const randInt = (Math.random() * 1000).toFixed(0); console.time('syncswap' + randInt); const swap = await wallet['syncWallet'].syncSwap({ orders: [swapOffer, fillOrder], @@ -448,7 +450,7 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { nonce: fillOrder.nonce }); const txHash = swap.txHash.split(":")[1]; - const txHashMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'b',txHash]]]} + const txHashMsg = { op: "orderstatusupdate", args: [[[chainId, orderId, 'b', txHash]]] } zigzagws.send(JSON.stringify(txHashMsg)); console.timeEnd('syncswap' + randInt); @@ -465,19 +467,19 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { success = false; } console.timeEnd('receipt' + randInt); - console.log("Swap broadcast result", {swap, receipt}); + console.log("Swap broadcast result", { swap, receipt }); let newStatus, error; - if(success) { + if (success) { afterFill(chainId, orderId, wallet); newStatus = 'f'; error = null; - } else { + } else { newStatus = 'r'; error = swap.error.toString(); - } + } - const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,newStatus,txHash,error]]]} + const orderCommitMsg = { op: "orderstatusupdate", args: [[[chainId, orderId, newStatus, txHash, error]]] } zigzagws.send(JSON.stringify(orderCommitMsg)); } @@ -488,25 +490,25 @@ async function fillOpenOrders() { if (fillable.fillable) { sendFillRequest(order, fillable.walletId); delete OPEN_ORDERS[orderId]; - }else if (![ + } else if (![ "sending order already", "badprice" - ].includes(fillable.reason)) { + ].includes(fillable.reason)) { delete OPEN_ORDERS[orderId]; } } } async function setupPriceFeeds() { - const cryptowatch = [], chainlink = [], uniswapV3 = []; + const cryptowatch = [], chainlink = [], uniswapV3 = []; for (let market in MM_CONFIG.pairs) { const pairConfig = MM_CONFIG.pairs[market]; - if(!pairConfig.active) { continue; } + if (!pairConfig.active) { continue; } // This is needed to make the price feed backwards compatalbe with old constant mode: // "DYDX-USDC": { // "mode": "constant", // "initPrice": 20, - if(pairConfig.mode == "constant") { + if (pairConfig.mode == "constant") { const initPrice = pairConfig.initPrice; pairConfig['priceFeedPrimary'] = "constant:" + initPrice.toString(); } @@ -515,38 +517,38 @@ async function setupPriceFeeds() { // parse keys to lower case to match later PRICE_FEED keys if (primaryPriceFeed) { - MM_CONFIG.pairs[market].priceFeedPrimary = primaryPriceFeed.toLowerCase(); + MM_CONFIG.pairs[market].priceFeedPrimary = primaryPriceFeed.toLowerCase(); } if (secondaryPriceFeed) { - MM_CONFIG.pairs[market].priceFeedSecondary = secondaryPriceFeed.toLowerCase(); + MM_CONFIG.pairs[market].priceFeedSecondary = secondaryPriceFeed.toLowerCase(); } [primaryPriceFeed, secondaryPriceFeed].forEach(priceFeed => { - if(!priceFeed) { return; } + if (!priceFeed) { return; } const [provider, id] = priceFeed.split(':'); - switch(provider.toLowerCase()) { + switch (provider.toLowerCase()) { case 'cryptowatch': - if(!cryptowatch.includes(id)) { cryptowatch.push(id); } + if (!cryptowatch.includes(id)) { cryptowatch.push(id); } break; case 'chainlink': - if(!chainlink.includes(id)) { chainlink.push(id); } + if (!chainlink.includes(id)) { chainlink.push(id); } break; case 'uniswapv3': - if(!uniswapV3.includes(id)) { uniswapV3.push(id); } + if (!uniswapV3.includes(id)) { uniswapV3.push(id); } break; case 'constant': - PRICE_FEEDS['constant:'+id] = parseFloat(id); + PRICE_FEEDS['constant:' + id] = parseFloat(id); break; default: - throw new Error("Price feed provider "+provider+" is not available.") + throw new Error("Price feed provider " + provider + " is not available.") break; - } - }); - } - if(chainlink.length > 0) await chainlinkSetup(chainlink); - if(cryptowatch.length > 0) await cryptowatchWsSetup(cryptowatch); - if(uniswapV3.length > 0) await uniswapV3Setup(uniswapV3); - - console.log(PRICE_FEEDS); + } + }); + } + if (chainlink.length > 0) await chainlinkSetup(chainlink); + if (cryptowatch.length > 0) await cryptowatchWsSetup(cryptowatch); + if (uniswapV3.length > 0) await uniswapV3Setup(uniswapV3); + + console.log(PRICE_FEEDS); } async function cryptowatchWsSetup(cryptowatchMarketIds) { @@ -561,7 +563,7 @@ async function cryptowatchWsSetup(cryptowatchMarketIds) { const exchange = cryptowatchMarket.exchange; const pair = cryptowatchMarket.pair; const key = `market:${exchange}:${pair}`; - PRICE_FEEDS['cryptowatch:'+cryptowatchMarketIds[i]] = cryptowatchMarketPrices.result[key]; + PRICE_FEEDS['cryptowatch:' + cryptowatchMarketIds[i]] = cryptowatchMarketPrices.result[key]; } catch (e) { console.error("Could not set price feed for cryptowatch:" + cryptowatchMarketId); } @@ -592,7 +594,7 @@ async function cryptowatchWsSetup(cryptowatchMarketIds) { function onopen() { cryptowatch_ws.send(JSON.stringify(subscriptionMsg)); } - function onmessage (data) { + function onmessage(data) { const msg = JSON.parse(data); if (!msg.marketUpdate) return; @@ -602,7 +604,7 @@ async function cryptowatchWsSetup(cryptowatchMarketIds) { let price = ask / 2 + bid / 2; PRICE_FEEDS[marketId] = price; } - function onclose () { + function onclose() { setTimeout(cryptowatchWsSetup, 5000, cryptowatchMarketIds); } } @@ -618,9 +620,9 @@ async function chainlinkSetup(chainlinkMarketAddress) { // get inital price const response = await provider.latestRoundData(); - PRICE_FEEDS[key] = parseFloat(response.answer) / 10**decimals; + PRICE_FEEDS[key] = parseFloat(response.answer) / 10 ** decimals; } catch (e) { - throw new Error ("Error while setting up chainlink for "+address+", Error: "+e); + throw new Error("Error while setting up chainlink for " + address + ", Error: " + e); } }); await Promise.all(results); @@ -632,14 +634,14 @@ async function chainlinkUpdate() { await Promise.all(Object.keys(CHAINLINK_PROVIDERS).map(async (key) => { const [provider, decimals] = CHAINLINK_PROVIDERS[key]; const response = await provider.latestRoundData(); - PRICE_FEEDS[key] = parseFloat(response.answer) / 10**decimals; + PRICE_FEEDS[key] = parseFloat(response.answer) / 10 ** decimals; })); chainlink_error_counter = 0; } catch (err) { chainlink_error_counter += 1; console.log(`Failed to update chainlink, retry: ${err.message}`); - if(chainlink_error_counter > 4) { - throw new Error ("Failed to update chainlink since 150 seconds!") + if (chainlink_error_counter > 4) { + throw new Error("Failed to update chainlink since 150 seconds!") } } } @@ -649,39 +651,39 @@ async function uniswapV3Setup(uniswapV3Address) { try { const IUniswapV3PoolABI = JSON.parse(fs.readFileSync('ABIs/IUniswapV3Pool.abi')); const ERC20ABI = JSON.parse(fs.readFileSync('ABIs/ERC20.abi')); - + const provider = new ethers.Contract(address, IUniswapV3PoolABI, ethersProvider); - + let [ - slot0, - addressToken0, - addressToken1 - ] = await Promise.all ([ - provider.slot0(), - provider.token0(), - provider.token1() + slot0, + addressToken0, + addressToken1 + ] = await Promise.all([ + provider.slot0(), + provider.token0(), + provider.token1() ]); - + const tokenProvier0 = new ethers.Contract(addressToken0, ERC20ABI, ethersProvider); const tokenProvier1 = new ethers.Contract(addressToken1, ERC20ABI, ethersProvider); - + let [ - decimals0, - decimals1 - ] = await Promise.all ([ - tokenProvier0.decimals(), - tokenProvier1.decimals() + decimals0, + decimals1 + ] = await Promise.all([ + tokenProvier0.decimals(), + tokenProvier1.decimals() ]); - + const key = 'uniswapv3:' + address; - const decimalsRatio = (10**decimals0 / 10**decimals1); + const decimalsRatio = (10 ** decimals0 / 10 ** decimals1); UNISWAP_V3_PROVIDERS[key] = [provider, decimalsRatio]; // get inital price - const price = (slot0.sqrtPriceX96*slot0.sqrtPriceX96*decimalsRatio) / (2**192); + const price = (slot0.sqrtPriceX96 * slot0.sqrtPriceX96 * decimalsRatio) / (2 ** 192); PRICE_FEEDS[key] = price; } catch (e) { - throw new Error ("Error while setting up uniswapV3 for "+address+", Error: "+e); + throw new Error("Error while setting up uniswapV3 for " + address + ", Error: " + e); } }); await Promise.all(results); @@ -693,7 +695,7 @@ async function uniswapV3Update() { await Promise.all(Object.keys(UNISWAP_V3_PROVIDERS).map(async (key) => { const [provider, decimalsRatio] = UNISWAP_V3_PROVIDERS[key]; const slot0 = await provider.slot0(); - PRICE_FEEDS[key] = (slot0.sqrtPriceX96*slot0.sqrtPriceX96*decimalsRatio) / (2**192); + PRICE_FEEDS[key] = (slot0.sqrtPriceX96 * slot0.sqrtPriceX96 * decimalsRatio) / (2 ** 192); })); // reset error counter if successful uniswap_error_counter = 0; @@ -701,28 +703,31 @@ async function uniswapV3Update() { uniswap_error_counter += 1; console.log(`Failed to update uniswap, retry: ${err.message}`); console.log(err.message); - if(uniswap_error_counter > 4) { - throw new Error ("Failed to update uniswap since 150 seconds!") + if (uniswap_error_counter > 4) { + throw new Error("Failed to update uniswap since 150 seconds!") } } } -function indicateLiquidity (pairs = MM_CONFIG.pairs) { - for(const marketId in pairs) { +function indicateLiquidity(pairs = MM_CONFIG.pairs) { + for (const marketId in pairs) { const mmConfig = pairs[marketId]; - if(!mmConfig || !mmConfig.active) continue; + if (!mmConfig || !mmConfig.active) continue; try { validatePriceFeed(marketId); - } catch(e) { - console.error("Can not indicateLiquidity ("+marketId+") because: " + e); + } catch (e) { + console.error("Can not indicateLiquidity (" + marketId + ") because: " + e); continue; } const marketInfo = MARKETS[marketId]; if (!marketInfo) continue; - const midPrice = PRICE_FEEDS[mmConfig.priceFeedPrimary]; + const midPrice = (mmConfig.invert) + ? (1 / PRICE_FEEDS[mmConfig.priceFeedPrimary]) + : PRICE_FEEDS[mmConfig.priceFeedPrimary]; + if (!midPrice) continue; const expires = (Date.now() / 1000 | 0) + 10; // 10s expiry @@ -739,8 +744,8 @@ function indicateLiquidity (pairs = MM_CONFIG.pairs) { maxQuoteBalance = walletQuote; } }); - const baseBalance = maxBaseBalance / 10**marketInfo.baseAsset.decimals; - const quoteBalance = maxQuoteBalance / 10**marketInfo.quoteAsset.decimals; + const baseBalance = maxBaseBalance / 10 ** marketInfo.baseAsset.decimals; + const quoteBalance = maxQuoteBalance / 10 ** marketInfo.quoteAsset.decimals; const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize); @@ -749,23 +754,23 @@ function indicateLiquidity (pairs = MM_CONFIG.pairs) { const usdQuoteBalance = quoteBalance * marketInfo.quoteAsset.usdPrice; let buySplits = (usdQuoteBalance && usdQuoteBalance < 1000) ? 1 : (mmConfig.numOrdersIndicated || 4); let sellSplits = (usdBaseBalance && usdBaseBalance < 1000) ? 1 : (mmConfig.numOrdersIndicated || 4); - + if (usdQuoteBalance && usdQuoteBalance < (10 * buySplits)) buySplits = Math.floor(usdQuoteBalance / 10) if (usdBaseBalance && usdBaseBalance < (10 * sellSplits)) sellSplits = Math.floor(usdBaseBalance / 10) - + const liquidity = []; - for (let i=1; i <= buySplits; i++) { - const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i/buySplits)); - if ((['b','d']).includes(side)) { + for (let i = 1; i <= buySplits; i++) { + const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i / buySplits)); + if ((['b', 'd']).includes(side)) { liquidity.push(["b", buyPrice, maxBuySize / buySplits, expires]); } } - for (let i=1; i <= sellSplits; i++) { - const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i/sellSplits)); - if ((['s','d']).includes(side)) { - liquidity.push(["s", sellPrice, maxSellSize / sellSplits, expires]); - } - } + for (let i = 1; i <= sellSplits; i++) { + const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i / sellSplits)); + if ((['s', 'd']).includes(side)) { + liquidity.push(["s", sellPrice, maxSellSize / sellSplits, expires]); + } + } const msg = { op: "indicateliq2", args: [CHAIN_ID, marketId, liquidity] }; try { @@ -777,7 +782,7 @@ function indicateLiquidity (pairs = MM_CONFIG.pairs) { } } -function cancelLiquidity (chainId, marketId) { +function cancelLiquidity(chainId, marketId) { const msg = { op: "indicateliq2", args: [chainId, marketId, []] }; try { zigzagws.send(JSON.stringify(msg)); @@ -789,18 +794,18 @@ function cancelLiquidity (chainId, marketId) { async function afterFill(chainId, orderId, wallet) { const order = PAST_ORDER_LIST[orderId]; - if(!order) { return; } + if (!order) { return; } const marketId = order.marketId; const mmConfig = MM_CONFIG.pairs[marketId]; - if(!mmConfig) { return; } + if (!mmConfig) { return; } // update account state from order const account_state = wallet['account_state'].committed.balances; - const buyTokenParsed = syncProvider.tokenSet.parseToken ( + const buyTokenParsed = syncProvider.tokenSet.parseToken( order.buySymbol, order.buyQuantity ); - const sellTokenParsed = syncProvider.tokenSet.parseToken ( + const sellTokenParsed = syncProvider.tokenSet.parseToken( order.sellSymbol, order.sellQuantity ); @@ -810,12 +815,12 @@ async function afterFill(chainId, orderId, wallet) { const oldSellTokenParsed = ethers.BigNumber.from(oldSellBalance); account_state[order.buySymbol] = (oldBuyTokenParsed.add(buyTokenParsed)).toString(); account_state[order.sellSymbol] = (oldSellTokenParsed.sub(sellTokenParsed)).toString(); - + const indicateMarket = {}; indicateMarket[marketId] = mmConfig; - if(mmConfig.delayAfterFill) { + if (mmConfig.delayAfterFill) { let delayAfterFillMinSize - if( + if ( !Array.isArray(mmConfig.delayAfterFill) || !mmConfig.delayAfterFill[1] ) { @@ -824,28 +829,28 @@ async function afterFill(chainId, orderId, wallet) { delayAfterFillMinSize = mmConfig.delayAfterFill[1] } - if(order.baseQuantity > delayAfterFillMinSize) { + if (order.baseQuantity > delayAfterFillMinSize) { // no array -> old config // or array and buyQuantity over minSize mmConfig.active = false; - cancelLiquidity (chainId, marketId); + cancelLiquidity(chainId, marketId); console.log(`Set ${marketId} passive for ${mmConfig.delayAfterFill} seconds.`); setTimeout(() => { mmConfig.active = true; console.log(`Set ${marketId} active.`); indicateLiquidity(indicateMarket); - }, mmConfig.delayAfterFill * 1000); - } + }, mmConfig.delayAfterFill * 1000); + } } // increaseSpreadAfterFill size might not be set - const increaseSpreadAfterFillMinSize = (mmConfig.increaseSpreadAfterFill?.[2]) + const increaseSpreadAfterFillMinSize = (mmConfig.increaseSpreadAfterFill?.[2]) ? mmConfig.increaseSpreadAfterFill[2] : 0 - if( + if ( mmConfig.increaseSpreadAfterFill && order.baseQuantity > increaseSpreadAfterFillMinSize - + ) { const [spread, time] = mmConfig.increaseSpreadAfterFill; mmConfig.minSpread = mmConfig.minSpread + spread; @@ -859,10 +864,10 @@ async function afterFill(chainId, orderId, wallet) { } // changeSizeAfterFill size might not be set - const changeSizeAfterFillMinSize = (mmConfig.changeSizeAfterFill?.[2]) + const changeSizeAfterFillMinSize = (mmConfig.changeSizeAfterFill?.[2]) ? mmConfig.changeSizeAfterFill[2] : 0 - if( + if ( mmConfig.changeSizeAfterFill && order.baseQuantity > changeSizeAfterFillMinSize ) { @@ -872,7 +877,7 @@ async function afterFill(chainId, orderId, wallet) { indicateLiquidity(indicateMarket); setTimeout(() => { mmConfig.maxSize = mmConfig.maxSize - size; - console.log(`Changed ${marketId} maxSize by ${(size* (-1))}.`); + console.log(`Changed ${marketId} maxSize by ${(size * (-1))}.`); indicateLiquidity(indicateMarket); }, time * 1000); } @@ -888,7 +893,7 @@ function rememberOrder(chainId, marketId, orderId, price, sellSymbol, sellQuanti const [baseSymbol, quoteSymbol] = marketId.split('-') let baseQuantity, quoteQuantity; - if(sellSymbol === baseSymbol) { + if (sellSymbol === baseSymbol) { baseQuantity = sellQuantity; quoteQuantity = buyQuantity; } else { @@ -907,7 +912,7 @@ function rememberOrder(chainId, marketId, orderId, price, sellSymbol, sellQuanti 'sellQuantity': sellQuantity, 'buySymbol': buySymbol, 'buyQuantity': buyQuantity, - 'expiry':expiry + 'expiry': expiry }; } @@ -918,7 +923,7 @@ async function updateAccountState() { WALLETS[accountId]['account_state'] = state; }) }); - } catch(err) { + } catch (err) { // pass } } From e6fe732a05999a705c693959420971c923d3414d Mon Sep 17 00:00:00 2001 From: Trooper Date: Mon, 27 Jun 2022 20:19:24 +0200 Subject: [PATCH 148/160] revert format --- marketmaker.js | 475 ++++++++++++++++++++++++------------------------- 1 file changed, 237 insertions(+), 238 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index ae26203..259cd3a 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -32,7 +32,7 @@ else { MM_CONFIG = JSON.parse(mmConfigFile); } if (MM_CONFIG.feeToken) { - FEE_TOKEN = MM_CONFIG.feeToken; + FEE_TOKEN = MM_CONFIG.feeToken; } let activePairs = []; for (let marketId in MM_CONFIG.pairs) { @@ -48,7 +48,7 @@ const CHAIN_ID = parseInt(MM_CONFIG.zigzagChainId); const ETH_NETWORK = (CHAIN_ID === 1) ? "mainnet" : "rinkeby"; let ethersProvider; const providerUrl = (process.env.INFURA_URL || MM_CONFIG.infuraUrl); -if (providerUrl && ETH_NETWORK == "mainnet") { +if(providerUrl && ETH_NETWORK=="mainnet") { ethersProvider = ethers.getDefaultProvider(providerUrl); } else { ethersProvider = ethers.getDefaultProvider(ETH_NETWORK); @@ -62,7 +62,7 @@ try { syncProvider = await zksync.getDefaultProvider(ETH_NETWORK); const keys = []; const ethPrivKey = (process.env.ETH_PRIVKEY || MM_CONFIG.ethPrivKey); - if (ethPrivKey && ethPrivKey != "") { keys.push(ethPrivKey); } + if(ethPrivKey && ethPrivKey != "") { keys.push(ethPrivKey); } let ethPrivKeys; if (process.env.ETH_PRIVKEYS) { ethPrivKeys = JSON.parse(process.env.ETH_PRIVKEYS); @@ -70,14 +70,14 @@ try { else { ethPrivKeys = MM_CONFIG.ethPrivKeys; } - if (ethPrivKeys && ethPrivKeys.length > 0) { - ethPrivKeys.forEach(key => { - if (key != "" && !keys.includes(key)) { + if(ethPrivKeys && ethPrivKeys.length > 0) { + ethPrivKeys.forEach( key => { + if(key != "" && !keys.includes(key)) { keys.push(key); } }); } - for (let i = 0; i < keys.length; i++) { + for(let i=0; i { clearInterval(fillOrdersInterval) @@ -138,12 +138,12 @@ function onWsClose() { async function handleMessage(json) { const msg = JSON.parse(json); if (!(["lastprice", "liquidity2", "fillstatus", "marketinfo"]).includes(msg.op)) console.log(json.toString()); - switch (msg.op) { + switch(msg.op) { case 'error': const accountId = msg.args?.[1]; - if (msg.args[0] == 'fillrequest' && accountId) { + if(msg.args[0] == 'fillrequest' && accountId) { WALLETS[accountId]['ORDER_BROADCASTING'] = false; - } + } break; case 'orders': const orders = msg.args[0]; @@ -154,8 +154,8 @@ async function handleMessage(json) { if (fillable.fillable) { sendFillRequest(order, fillable.walletId); } else if ([ - "sending order already", - "badprice" + "sending order already", + "badprice" ].includes(fillable.reason)) { OPEN_ORDERS[orderId] = order; } @@ -166,14 +166,14 @@ async function handleMessage(json) { const orderId = msg.args[1]; const fillOrder = msg.args[3]; const wallet = WALLETS[fillOrder.accountId]; - if (!wallet) { - console.error("No wallet with this accountId: " + fillOrder.accountId); + if(!wallet) { + console.error("No wallet with this accountId: "+fillOrder.accountId); break } else { try { await broadcastFill(chainId, orderId, msg.args[2], fillOrder, wallet); } catch (e) { - const orderCommitMsg = { op: "orderstatusupdate", args: [[[chainId, orderId, 'r', null, e.message]]] } + const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'r',null,e.message]]]} zigzagws.send(JSON.stringify(orderCommitMsg)); console.error(e); } @@ -182,8 +182,8 @@ async function handleMessage(json) { break case "marketinfo": const marketInfo = msg.args[0]; - const marketId = marketInfo.alias; - if (!marketId) break + const marketId = marketInfo.alias; + if(!marketId) break let oldBaseFee = "N/A", oldQuoteFee = "N/A"; try { oldBaseFee = MARKETS[marketId].baseFee; @@ -196,18 +196,18 @@ async function handleMessage(json) { const newQuoteFee = MARKETS[marketId].quoteFee; console.log(`marketinfo ${marketId} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); if (FEE_TOKEN) break - if ( - marketInfo.baseAsset.enabledForFees && - !FEE_TOKEN_LIST.includes(marketInfo.baseAsset.id) + if( + marketInfo.baseAsset.enabledForFees && + !FEE_TOKEN_LIST.includes(marketInfo.baseAsset.id) ) { - FEE_TOKEN_LIST.push(marketInfo.baseAsset.id); - } - if ( - marketInfo.quoteAsset.enabledForFees && - !FEE_TOKEN_LIST.includes(marketInfo.quoteAsset.id) + FEE_TOKEN_LIST.push(marketInfo.baseAsset.id); + } + if( + marketInfo.quoteAsset.enabledForFees && + !FEE_TOKEN_LIST.includes(marketInfo.quoteAsset.id) ) { - FEE_TOKEN_LIST.push(marketInfo.quoteAsset.id); - } + FEE_TOKEN_LIST.push(marketInfo.quoteAsset.id); + } break default: break @@ -229,7 +229,7 @@ function isOrderFillable(order) { const expires = order[7]; const side = order[3]; const price = order[4]; - + const now = Date.now() / 1000 | 0; if (now > expires) { @@ -249,7 +249,7 @@ function isOrderFillable(order) { let quote; try { - quote = genQuote(chainId, marketId, side, baseQuantity); + quote = genQuote(chainId, marketId, side, baseQuantity); } catch (e) { return { fillable: false, reason: e.message } } @@ -263,7 +263,7 @@ function isOrderFillable(order) { const sellCurrency = (side === 's') ? market.quoteAsset.symbol : market.baseAsset.symbol; const sellDecimals = (side === 's') ? market.quoteAsset.decimals : market.baseAsset.decimals; const sellQuantity = (side === 's') ? quote.quoteQuantity : baseQuantity; - const neededBalanceBN = sellQuantity * 10 ** sellDecimals; + const neededBalanceBN = sellQuantity * 10**sellDecimals; let goodWalletIds = []; Object.keys(WALLETS).forEach(accountId => { const walletBalance = WALLETS[accountId]['account_state'].committed.balances[sellCurrency]; @@ -284,39 +284,39 @@ function isOrderFillable(order) { return { fillable: false, reason: "sending order already" }; } - return { fillable: true, reason: null, walletId: goodWalletIds[0] }; + return { fillable: true, reason: null, walletId: goodWalletIds[0]}; } function genQuote(chainId, marketId, side, baseQuantity) { - const market = MARKETS[marketId]; - if (CHAIN_ID !== chainId) throw new Error("badchain"); - if (!market) throw new Error("badmarket"); - if (!(['b', 's']).includes(side)) throw new Error("badside"); - if (baseQuantity <= 0) throw new Error("badquantity"); - - validatePriceFeed(marketId); - - const mmConfig = MM_CONFIG.pairs[marketId]; - const mmSide = mmConfig.side || 'd'; - if (mmSide !== 'd' && mmSide === side) { - throw new Error("badside"); - } - const primaryPrice = (mmConfig.invert) - ? (1 / PRICE_FEEDS[mmConfig.priceFeedPrimary]) - : PRICE_FEEDS[mmConfig.priceFeedPrimary]; - if (!primaryPrice) throw new Error("badprice"); - const SPREAD = mmConfig.minSpread + (baseQuantity * mmConfig.slippageRate); - let quoteQuantity; - if (side === 'b') { - quoteQuantity = (baseQuantity * primaryPrice * (1 + SPREAD)) + market.quoteFee; - } - else if (side === 's') { - quoteQuantity = (baseQuantity - market.baseFee) * primaryPrice * (1 - SPREAD); - } - const quotePrice = (quoteQuantity / baseQuantity).toPrecision(6); - if (quotePrice < 0) throw new Error("Amount is inadequate to pay fee"); - if (isNaN(quotePrice)) throw new Error("Internal Error. No price generated."); - return { quotePrice, quoteQuantity }; + const market = MARKETS[marketId]; + if (CHAIN_ID !== chainId) throw new Error("badchain"); + if (!market) throw new Error("badmarket"); + if (!(['b','s']).includes(side)) throw new Error("badside"); + if (baseQuantity <= 0) throw new Error("badquantity"); + + validatePriceFeed(marketId); + + const mmConfig = MM_CONFIG.pairs[marketId]; + const mmSide = mmConfig.side || 'd'; + if (mmSide !== 'd' && mmSide === side) { + throw new Error("badside"); + } + const primaryPrice = (mmConfig.invert) + ? (1 / PRICE_FEEDS[mmConfig.priceFeedPrimary]) + : PRICE_FEEDS[mmConfig.priceFeedPrimary]; + if (!primaryPrice) throw new Error("badprice"); + const SPREAD = mmConfig.minSpread + (baseQuantity * mmConfig.slippageRate); + let quoteQuantity; + if (side === 'b') { + quoteQuantity = (baseQuantity * primaryPrice * (1 + SPREAD)) + market.quoteFee; + } + else if (side === 's') { + quoteQuantity = (baseQuantity - market.baseFee) * primaryPrice * (1 - SPREAD); + } + const quotePrice = (quoteQuantity / baseQuantity).toPrecision(6); + if (quotePrice < 0) throw new Error("Amount is inadequate to pay fee"); + if (isNaN(quotePrice)) throw new Error("Internal Error. No price generated."); + return { quotePrice, quoteQuantity }; } function validatePriceFeed(marketId) { @@ -346,81 +346,81 @@ function validatePriceFeed(marketId) { // If the secondary price feed varies from the primary price feed by more than 1%, assume something is broken const percentDiff = Math.abs(primaryPrice - secondaryPrice) / primaryPrice; if (percentDiff > 0.03) { - console.error("Primary and secondary price feeds do not match!"); - throw new Error("Circuit breaker triggered"); + console.error("Primary and secondary price feeds do not match!"); + throw new Error("Circuit breaker triggered"); } return true; } async function sendFillRequest(orderreceipt, accountId) { - const chainId = orderreceipt[0]; - const orderId = orderreceipt[1]; - const marketId = orderreceipt[2]; - const market = MARKETS[marketId]; - const baseCurrency = market.baseAssetId; - const quoteCurrency = market.quoteAssetId; - const side = orderreceipt[3]; - const baseQuantity = orderreceipt[5]; - const quoteQuantity = orderreceipt[6]; - const quote = genQuote(chainId, marketId, side, baseQuantity); - let tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol; - if (side === "b") { - tokenSell = market.baseAssetId; - tokenBuy = market.quoteAssetId; - - sellSymbol = market.baseAsset.symbol; - buySymbol = market.quoteAsset.symbol; - // Add 1 bip to to protect against rounding errors - sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals); - buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); - } else if (side === "s") { - tokenSell = market.quoteAssetId; - tokenBuy = market.baseAssetId; - - sellSymbol = market.quoteAsset.symbol; - buySymbol = market.baseAsset.symbol; - // Add 1 bip to to protect against rounding errors - sellQuantity = (quote.quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals); - buyQuantity = (baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); - } - const sellQuantityParsed = syncProvider.tokenSet.parseToken( - tokenSell, - sellQuantity - ); - const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount(sellQuantityParsed); - const tokenRatio = {}; - tokenRatio[tokenBuy] = buyQuantity; - tokenRatio[tokenSell] = sellQuantity; - const oneMinExpiry = (Date.now() / 1000 | 0) + 60; - const orderDetails = { - tokenSell, - tokenBuy, - amount: sellQuantityPacked, - ratio: zksync.utils.tokenRatio(tokenRatio), - validUntil: oneMinExpiry - } - const fillOrder = await WALLETS[accountId].syncWallet.getOrder(orderDetails); - - // Set wallet flag - WALLETS[accountId]['ORDER_BROADCASTING'] = true; - - // ORDER_BROADCASTING should not take longer as 5 sec - setTimeout(function () { - WALLETS[accountId]['ORDER_BROADCASTING'] = false; - }, 5000); - - const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] }; - zigzagws.send(JSON.stringify(resp)); - rememberOrder(chainId, - marketId, - orderId, - quote.quotePrice, - sellSymbol, - sellQuantity, - buySymbol, - buyQuantity - ); + const chainId = orderreceipt[0]; + const orderId = orderreceipt[1]; + const marketId = orderreceipt[2]; + const market = MARKETS[marketId]; + const baseCurrency = market.baseAssetId; + const quoteCurrency = market.quoteAssetId; + const side = orderreceipt[3]; + const baseQuantity = orderreceipt[5]; + const quoteQuantity = orderreceipt[6]; + const quote = genQuote(chainId, marketId, side, baseQuantity); + let tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol; + if (side === "b") { + tokenSell = market.baseAssetId; + tokenBuy = market.quoteAssetId; + + sellSymbol = market.baseAsset.symbol; + buySymbol = market.quoteAsset.symbol; + // Add 1 bip to to protect against rounding errors + sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals); + buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); + } else if (side === "s") { + tokenSell = market.quoteAssetId; + tokenBuy = market.baseAssetId; + + sellSymbol = market.quoteAsset.symbol; + buySymbol = market.baseAsset.symbol; + // Add 1 bip to to protect against rounding errors + sellQuantity = (quote.quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals); + buyQuantity = (baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); + } + const sellQuantityParsed = syncProvider.tokenSet.parseToken( + tokenSell, + sellQuantity + ); + const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount(sellQuantityParsed); + const tokenRatio = {}; + tokenRatio[tokenBuy] = buyQuantity; + tokenRatio[tokenSell] = sellQuantity; + const oneMinExpiry = (Date.now() / 1000 | 0) + 60; + const orderDetails = { + tokenSell, + tokenBuy, + amount: sellQuantityPacked, + ratio: zksync.utils.tokenRatio(tokenRatio), + validUntil: oneMinExpiry + } + const fillOrder = await WALLETS[accountId].syncWallet.getOrder(orderDetails); + + // Set wallet flag + WALLETS[accountId]['ORDER_BROADCASTING'] = true; + + // ORDER_BROADCASTING should not take longer as 5 sec + setTimeout(function() { + WALLETS[accountId]['ORDER_BROADCASTING'] = false; + }, 5000); + + const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] }; + zigzagws.send(JSON.stringify(resp)); + rememberOrder(chainId, + marketId, + orderId, + quote.quotePrice, + sellSymbol, + sellQuantity, + buySymbol, + buyQuantity + ); } async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { @@ -428,21 +428,21 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { const nonce = swapOffer.nonce; const userNonce = NONCES[swapOffer.accountId]; if (nonce <= userNonce) { - const orderCommitMsg = { op: "orderstatusupdate", args: [[[chainId, orderId, 'r', null, "Order failed userNonce check."]]] } + const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'r',null,"Order failed userNonce check."]]]} zigzagws.send(JSON.stringify(orderCommitMsg)); return; } // select token to match user's fee token let feeToken; if (FEE_TOKEN) { - feeToken = FEE_TOKEN + feeToken = FEE_TOKEN } else { - feeToken = (FEE_TOKEN_LIST.includes(swapOffer.tokenSell)) - ? swapOffer.tokenSell - : 'ETH' + feeToken = (FEE_TOKEN_LIST.includes(swapOffer.tokenSell)) + ? swapOffer.tokenSell + : 'ETH' } - - const randInt = (Math.random() * 1000).toFixed(0); + + const randInt = (Math.random()*1000).toFixed(0); console.time('syncswap' + randInt); const swap = await wallet['syncWallet'].syncSwap({ orders: [swapOffer, fillOrder], @@ -450,7 +450,7 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { nonce: fillOrder.nonce }); const txHash = swap.txHash.split(":")[1]; - const txHashMsg = { op: "orderstatusupdate", args: [[[chainId, orderId, 'b', txHash]]] } + const txHashMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'b',txHash]]]} zigzagws.send(JSON.stringify(txHashMsg)); console.timeEnd('syncswap' + randInt); @@ -467,19 +467,19 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { success = false; } console.timeEnd('receipt' + randInt); - console.log("Swap broadcast result", { swap, receipt }); + console.log("Swap broadcast result", {swap, receipt}); let newStatus, error; - if (success) { + if(success) { afterFill(chainId, orderId, wallet); newStatus = 'f'; error = null; - } else { + } else { newStatus = 'r'; error = swap.error.toString(); - } + } - const orderCommitMsg = { op: "orderstatusupdate", args: [[[chainId, orderId, newStatus, txHash, error]]] } + const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,newStatus,txHash,error]]]} zigzagws.send(JSON.stringify(orderCommitMsg)); } @@ -490,25 +490,25 @@ async function fillOpenOrders() { if (fillable.fillable) { sendFillRequest(order, fillable.walletId); delete OPEN_ORDERS[orderId]; - } else if (![ + }else if (![ "sending order already", "badprice" - ].includes(fillable.reason)) { + ].includes(fillable.reason)) { delete OPEN_ORDERS[orderId]; } } } async function setupPriceFeeds() { - const cryptowatch = [], chainlink = [], uniswapV3 = []; + const cryptowatch = [], chainlink = [], uniswapV3 = []; for (let market in MM_CONFIG.pairs) { const pairConfig = MM_CONFIG.pairs[market]; - if (!pairConfig.active) { continue; } + if(!pairConfig.active) { continue; } // This is needed to make the price feed backwards compatalbe with old constant mode: // "DYDX-USDC": { // "mode": "constant", // "initPrice": 20, - if (pairConfig.mode == "constant") { + if(pairConfig.mode == "constant") { const initPrice = pairConfig.initPrice; pairConfig['priceFeedPrimary'] = "constant:" + initPrice.toString(); } @@ -517,38 +517,38 @@ async function setupPriceFeeds() { // parse keys to lower case to match later PRICE_FEED keys if (primaryPriceFeed) { - MM_CONFIG.pairs[market].priceFeedPrimary = primaryPriceFeed.toLowerCase(); + MM_CONFIG.pairs[market].priceFeedPrimary = primaryPriceFeed.toLowerCase(); } if (secondaryPriceFeed) { - MM_CONFIG.pairs[market].priceFeedSecondary = secondaryPriceFeed.toLowerCase(); + MM_CONFIG.pairs[market].priceFeedSecondary = secondaryPriceFeed.toLowerCase(); } [primaryPriceFeed, secondaryPriceFeed].forEach(priceFeed => { - if (!priceFeed) { return; } + if(!priceFeed) { return; } const [provider, id] = priceFeed.split(':'); - switch (provider.toLowerCase()) { + switch(provider.toLowerCase()) { case 'cryptowatch': - if (!cryptowatch.includes(id)) { cryptowatch.push(id); } + if(!cryptowatch.includes(id)) { cryptowatch.push(id); } break; case 'chainlink': - if (!chainlink.includes(id)) { chainlink.push(id); } + if(!chainlink.includes(id)) { chainlink.push(id); } break; case 'uniswapv3': - if (!uniswapV3.includes(id)) { uniswapV3.push(id); } + if(!uniswapV3.includes(id)) { uniswapV3.push(id); } break; case 'constant': - PRICE_FEEDS['constant:' + id] = parseFloat(id); + PRICE_FEEDS['constant:'+id] = parseFloat(id); break; default: - throw new Error("Price feed provider " + provider + " is not available.") + throw new Error("Price feed provider "+provider+" is not available.") break; - } - }); - } - if (chainlink.length > 0) await chainlinkSetup(chainlink); - if (cryptowatch.length > 0) await cryptowatchWsSetup(cryptowatch); - if (uniswapV3.length > 0) await uniswapV3Setup(uniswapV3); - - console.log(PRICE_FEEDS); + } + }); + } + if(chainlink.length > 0) await chainlinkSetup(chainlink); + if(cryptowatch.length > 0) await cryptowatchWsSetup(cryptowatch); + if(uniswapV3.length > 0) await uniswapV3Setup(uniswapV3); + + console.log(PRICE_FEEDS); } async function cryptowatchWsSetup(cryptowatchMarketIds) { @@ -563,7 +563,7 @@ async function cryptowatchWsSetup(cryptowatchMarketIds) { const exchange = cryptowatchMarket.exchange; const pair = cryptowatchMarket.pair; const key = `market:${exchange}:${pair}`; - PRICE_FEEDS['cryptowatch:' + cryptowatchMarketIds[i]] = cryptowatchMarketPrices.result[key]; + PRICE_FEEDS['cryptowatch:'+cryptowatchMarketIds[i]] = cryptowatchMarketPrices.result[key]; } catch (e) { console.error("Could not set price feed for cryptowatch:" + cryptowatchMarketId); } @@ -594,7 +594,7 @@ async function cryptowatchWsSetup(cryptowatchMarketIds) { function onopen() { cryptowatch_ws.send(JSON.stringify(subscriptionMsg)); } - function onmessage(data) { + function onmessage (data) { const msg = JSON.parse(data); if (!msg.marketUpdate) return; @@ -604,7 +604,7 @@ async function cryptowatchWsSetup(cryptowatchMarketIds) { let price = ask / 2 + bid / 2; PRICE_FEEDS[marketId] = price; } - function onclose() { + function onclose () { setTimeout(cryptowatchWsSetup, 5000, cryptowatchMarketIds); } } @@ -620,9 +620,9 @@ async function chainlinkSetup(chainlinkMarketAddress) { // get inital price const response = await provider.latestRoundData(); - PRICE_FEEDS[key] = parseFloat(response.answer) / 10 ** decimals; + PRICE_FEEDS[key] = parseFloat(response.answer) / 10**decimals; } catch (e) { - throw new Error("Error while setting up chainlink for " + address + ", Error: " + e); + throw new Error ("Error while setting up chainlink for "+address+", Error: "+e); } }); await Promise.all(results); @@ -634,14 +634,14 @@ async function chainlinkUpdate() { await Promise.all(Object.keys(CHAINLINK_PROVIDERS).map(async (key) => { const [provider, decimals] = CHAINLINK_PROVIDERS[key]; const response = await provider.latestRoundData(); - PRICE_FEEDS[key] = parseFloat(response.answer) / 10 ** decimals; + PRICE_FEEDS[key] = parseFloat(response.answer) / 10**decimals; })); chainlink_error_counter = 0; } catch (err) { chainlink_error_counter += 1; console.log(`Failed to update chainlink, retry: ${err.message}`); - if (chainlink_error_counter > 4) { - throw new Error("Failed to update chainlink since 150 seconds!") + if(chainlink_error_counter > 4) { + throw new Error ("Failed to update chainlink since 150 seconds!") } } } @@ -651,39 +651,39 @@ async function uniswapV3Setup(uniswapV3Address) { try { const IUniswapV3PoolABI = JSON.parse(fs.readFileSync('ABIs/IUniswapV3Pool.abi')); const ERC20ABI = JSON.parse(fs.readFileSync('ABIs/ERC20.abi')); - + const provider = new ethers.Contract(address, IUniswapV3PoolABI, ethersProvider); - + let [ - slot0, - addressToken0, - addressToken1 - ] = await Promise.all([ - provider.slot0(), - provider.token0(), - provider.token1() + slot0, + addressToken0, + addressToken1 + ] = await Promise.all ([ + provider.slot0(), + provider.token0(), + provider.token1() ]); - + const tokenProvier0 = new ethers.Contract(addressToken0, ERC20ABI, ethersProvider); const tokenProvier1 = new ethers.Contract(addressToken1, ERC20ABI, ethersProvider); - + let [ - decimals0, - decimals1 - ] = await Promise.all([ - tokenProvier0.decimals(), - tokenProvier1.decimals() + decimals0, + decimals1 + ] = await Promise.all ([ + tokenProvier0.decimals(), + tokenProvier1.decimals() ]); - + const key = 'uniswapv3:' + address; - const decimalsRatio = (10 ** decimals0 / 10 ** decimals1); + const decimalsRatio = (10**decimals0 / 10**decimals1); UNISWAP_V3_PROVIDERS[key] = [provider, decimalsRatio]; // get inital price - const price = (slot0.sqrtPriceX96 * slot0.sqrtPriceX96 * decimalsRatio) / (2 ** 192); + const price = (slot0.sqrtPriceX96*slot0.sqrtPriceX96*decimalsRatio) / (2**192); PRICE_FEEDS[key] = price; } catch (e) { - throw new Error("Error while setting up uniswapV3 for " + address + ", Error: " + e); + throw new Error ("Error while setting up uniswapV3 for "+address+", Error: "+e); } }); await Promise.all(results); @@ -695,7 +695,7 @@ async function uniswapV3Update() { await Promise.all(Object.keys(UNISWAP_V3_PROVIDERS).map(async (key) => { const [provider, decimalsRatio] = UNISWAP_V3_PROVIDERS[key]; const slot0 = await provider.slot0(); - PRICE_FEEDS[key] = (slot0.sqrtPriceX96 * slot0.sqrtPriceX96 * decimalsRatio) / (2 ** 192); + PRICE_FEEDS[key] = (slot0.sqrtPriceX96*slot0.sqrtPriceX96*decimalsRatio) / (2**192); })); // reset error counter if successful uniswap_error_counter = 0; @@ -703,31 +703,30 @@ async function uniswapV3Update() { uniswap_error_counter += 1; console.log(`Failed to update uniswap, retry: ${err.message}`); console.log(err.message); - if (uniswap_error_counter > 4) { - throw new Error("Failed to update uniswap since 150 seconds!") + if(uniswap_error_counter > 4) { + throw new Error ("Failed to update uniswap since 150 seconds!") } } } -function indicateLiquidity(pairs = MM_CONFIG.pairs) { - for (const marketId in pairs) { +function indicateLiquidity (pairs = MM_CONFIG.pairs) { + for(const marketId in pairs) { const mmConfig = pairs[marketId]; - if (!mmConfig || !mmConfig.active) continue; + if(!mmConfig || !mmConfig.active) continue; try { validatePriceFeed(marketId); - } catch (e) { - console.error("Can not indicateLiquidity (" + marketId + ") because: " + e); + } catch(e) { + console.error("Can not indicateLiquidity ("+marketId+") because: " + e); continue; } const marketInfo = MARKETS[marketId]; if (!marketInfo) continue; - + const midPrice = (mmConfig.invert) ? (1 / PRICE_FEEDS[mmConfig.priceFeedPrimary]) : PRICE_FEEDS[mmConfig.priceFeedPrimary]; - if (!midPrice) continue; const expires = (Date.now() / 1000 | 0) + 10; // 10s expiry @@ -744,8 +743,8 @@ function indicateLiquidity(pairs = MM_CONFIG.pairs) { maxQuoteBalance = walletQuote; } }); - const baseBalance = maxBaseBalance / 10 ** marketInfo.baseAsset.decimals; - const quoteBalance = maxQuoteBalance / 10 ** marketInfo.quoteAsset.decimals; + const baseBalance = maxBaseBalance / 10**marketInfo.baseAsset.decimals; + const quoteBalance = maxQuoteBalance / 10**marketInfo.quoteAsset.decimals; const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize); @@ -754,23 +753,23 @@ function indicateLiquidity(pairs = MM_CONFIG.pairs) { const usdQuoteBalance = quoteBalance * marketInfo.quoteAsset.usdPrice; let buySplits = (usdQuoteBalance && usdQuoteBalance < 1000) ? 1 : (mmConfig.numOrdersIndicated || 4); let sellSplits = (usdBaseBalance && usdBaseBalance < 1000) ? 1 : (mmConfig.numOrdersIndicated || 4); - + if (usdQuoteBalance && usdQuoteBalance < (10 * buySplits)) buySplits = Math.floor(usdQuoteBalance / 10) if (usdBaseBalance && usdBaseBalance < (10 * sellSplits)) sellSplits = Math.floor(usdBaseBalance / 10) - + const liquidity = []; - for (let i = 1; i <= buySplits; i++) { - const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i / buySplits)); - if ((['b', 'd']).includes(side)) { + for (let i=1; i <= buySplits; i++) { + const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i/buySplits)); + if ((['b','d']).includes(side)) { liquidity.push(["b", buyPrice, maxBuySize / buySplits, expires]); } } - for (let i = 1; i <= sellSplits; i++) { - const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i / sellSplits)); - if ((['s', 'd']).includes(side)) { - liquidity.push(["s", sellPrice, maxSellSize / sellSplits, expires]); - } - } + for (let i=1; i <= sellSplits; i++) { + const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i/sellSplits)); + if ((['s','d']).includes(side)) { + liquidity.push(["s", sellPrice, maxSellSize / sellSplits, expires]); + } + } const msg = { op: "indicateliq2", args: [CHAIN_ID, marketId, liquidity] }; try { @@ -782,7 +781,7 @@ function indicateLiquidity(pairs = MM_CONFIG.pairs) { } } -function cancelLiquidity(chainId, marketId) { +function cancelLiquidity (chainId, marketId) { const msg = { op: "indicateliq2", args: [chainId, marketId, []] }; try { zigzagws.send(JSON.stringify(msg)); @@ -794,18 +793,18 @@ function cancelLiquidity(chainId, marketId) { async function afterFill(chainId, orderId, wallet) { const order = PAST_ORDER_LIST[orderId]; - if (!order) { return; } + if(!order) { return; } const marketId = order.marketId; const mmConfig = MM_CONFIG.pairs[marketId]; - if (!mmConfig) { return; } + if(!mmConfig) { return; } // update account state from order const account_state = wallet['account_state'].committed.balances; - const buyTokenParsed = syncProvider.tokenSet.parseToken( + const buyTokenParsed = syncProvider.tokenSet.parseToken ( order.buySymbol, order.buyQuantity ); - const sellTokenParsed = syncProvider.tokenSet.parseToken( + const sellTokenParsed = syncProvider.tokenSet.parseToken ( order.sellSymbol, order.sellQuantity ); @@ -815,12 +814,12 @@ async function afterFill(chainId, orderId, wallet) { const oldSellTokenParsed = ethers.BigNumber.from(oldSellBalance); account_state[order.buySymbol] = (oldBuyTokenParsed.add(buyTokenParsed)).toString(); account_state[order.sellSymbol] = (oldSellTokenParsed.sub(sellTokenParsed)).toString(); - + const indicateMarket = {}; indicateMarket[marketId] = mmConfig; - if (mmConfig.delayAfterFill) { + if(mmConfig.delayAfterFill) { let delayAfterFillMinSize - if ( + if( !Array.isArray(mmConfig.delayAfterFill) || !mmConfig.delayAfterFill[1] ) { @@ -829,28 +828,28 @@ async function afterFill(chainId, orderId, wallet) { delayAfterFillMinSize = mmConfig.delayAfterFill[1] } - if (order.baseQuantity > delayAfterFillMinSize) { + if(order.baseQuantity > delayAfterFillMinSize) { // no array -> old config // or array and buyQuantity over minSize mmConfig.active = false; - cancelLiquidity(chainId, marketId); + cancelLiquidity (chainId, marketId); console.log(`Set ${marketId} passive for ${mmConfig.delayAfterFill} seconds.`); setTimeout(() => { mmConfig.active = true; console.log(`Set ${marketId} active.`); indicateLiquidity(indicateMarket); - }, mmConfig.delayAfterFill * 1000); - } + }, mmConfig.delayAfterFill * 1000); + } } // increaseSpreadAfterFill size might not be set - const increaseSpreadAfterFillMinSize = (mmConfig.increaseSpreadAfterFill?.[2]) + const increaseSpreadAfterFillMinSize = (mmConfig.increaseSpreadAfterFill?.[2]) ? mmConfig.increaseSpreadAfterFill[2] : 0 - if ( + if( mmConfig.increaseSpreadAfterFill && order.baseQuantity > increaseSpreadAfterFillMinSize - + ) { const [spread, time] = mmConfig.increaseSpreadAfterFill; mmConfig.minSpread = mmConfig.minSpread + spread; @@ -864,10 +863,10 @@ async function afterFill(chainId, orderId, wallet) { } // changeSizeAfterFill size might not be set - const changeSizeAfterFillMinSize = (mmConfig.changeSizeAfterFill?.[2]) + const changeSizeAfterFillMinSize = (mmConfig.changeSizeAfterFill?.[2]) ? mmConfig.changeSizeAfterFill[2] : 0 - if ( + if( mmConfig.changeSizeAfterFill && order.baseQuantity > changeSizeAfterFillMinSize ) { @@ -877,7 +876,7 @@ async function afterFill(chainId, orderId, wallet) { indicateLiquidity(indicateMarket); setTimeout(() => { mmConfig.maxSize = mmConfig.maxSize - size; - console.log(`Changed ${marketId} maxSize by ${(size * (-1))}.`); + console.log(`Changed ${marketId} maxSize by ${(size* (-1))}.`); indicateLiquidity(indicateMarket); }, time * 1000); } @@ -893,7 +892,7 @@ function rememberOrder(chainId, marketId, orderId, price, sellSymbol, sellQuanti const [baseSymbol, quoteSymbol] = marketId.split('-') let baseQuantity, quoteQuantity; - if (sellSymbol === baseSymbol) { + if(sellSymbol === baseSymbol) { baseQuantity = sellQuantity; quoteQuantity = buyQuantity; } else { @@ -912,7 +911,7 @@ function rememberOrder(chainId, marketId, orderId, price, sellSymbol, sellQuanti 'sellQuantity': sellQuantity, 'buySymbol': buySymbol, 'buyQuantity': buyQuantity, - 'expiry': expiry + 'expiry':expiry }; } @@ -923,7 +922,7 @@ async function updateAccountState() { WALLETS[accountId]['account_state'] = state; }) }); - } catch (err) { + } catch(err) { // pass } } From f6834cb075e323a4b742aede1651e9bfb92eceda Mon Sep 17 00:00:00 2001 From: Trooper Date: Mon, 8 Aug 2022 22:39:51 +0200 Subject: [PATCH 149/160] update package json --- package-lock.json | 1689 ++++++++++++++++++++++++--------------------- package.json | 2 +- 2 files changed, 908 insertions(+), 783 deletions(-) diff --git a/package-lock.json b/package-lock.json index bc8d7f1..4a0eb64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "ethers": "^5.5.1", "node-fetch": "^3.1.0", "ws": "^8.2.3", - "zksync": "^0.11.6" + "zksync": "^0.13.1" }, "devDependencies": {}, "engines": { @@ -21,9 +21,9 @@ } }, "node_modules/@ethersproject/abi": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.5.0.tgz", - "integrity": "sha512-loW7I4AohP5KycATvc0MgujU6JyCHPqHdeoo9z3Nr9xEiNioxa65ccdm1+fsoJhkuhdRtfcL8cfyGamz2AxZ5w==", + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.6.4.tgz", + "integrity": "sha512-TTeZUlCeIHG6527/2goZA6gW5F8Emoc7MrZDC7hhP84aRGvW3TEdTnZR08Ls88YXM1m2SuK42Osw/jSi3uO8gg==", "funding": [ { "type": "individual", @@ -35,21 +35,21 @@ } ], "dependencies": { - "@ethersproject/address": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/constants": "^5.5.0", - "@ethersproject/hash": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/strings": "^5.5.0" + "@ethersproject/address": "^5.6.1", + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/constants": "^5.6.1", + "@ethersproject/hash": "^5.6.1", + "@ethersproject/keccak256": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/strings": "^5.6.1" } }, "node_modules/@ethersproject/abstract-provider": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.5.1.tgz", - "integrity": "sha512-m+MA/ful6eKbxpr99xUYeRvLkfnlqzrF8SZ46d/xFB1A7ZVknYc/sXJG0RcufF52Qn2jeFj1hhcoQ7IXjNKUqg==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.6.1.tgz", + "integrity": "sha512-BxlIgogYJtp1FS8Muvj8YfdClk3unZH0vRMVX791Z9INBNT/kuACZ9GzaY1Y4yFq+YSy6/w4gzj3HCRKrK9hsQ==", "funding": [ { "type": "individual", @@ -61,19 +61,19 @@ } ], "dependencies": { - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/networks": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/transactions": "^5.5.0", - "@ethersproject/web": "^5.5.0" + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/networks": "^5.6.3", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/transactions": "^5.6.2", + "@ethersproject/web": "^5.6.1" } }, "node_modules/@ethersproject/abstract-signer": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.5.0.tgz", - "integrity": "sha512-lj//7r250MXVLKI7sVarXAbZXbv9P50lgmJQGr2/is82EwEb8r7HrxsmMqAjTsztMYy7ohrIhGMIml+Gx4D3mA==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.6.2.tgz", + "integrity": "sha512-n1r6lttFBG0t2vNiI3HoWaS/KdOt8xyDjzlP2cuevlWLG6EX0OwcKLyG/Kp/cuwNxdy/ous+R/DEMdTUwWQIjQ==", "funding": [ { "type": "individual", @@ -85,17 +85,17 @@ } ], "dependencies": { - "@ethersproject/abstract-provider": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0" + "@ethersproject/abstract-provider": "^5.6.1", + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0" } }, "node_modules/@ethersproject/address": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.5.0.tgz", - "integrity": "sha512-l4Nj0eWlTUh6ro5IbPTgbpT4wRbdH5l8CQf7icF7sb/SI3Nhd9Y9HzhonTSTi6CefI0necIw7LJqQPopPLZyWw==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.6.1.tgz", + "integrity": "sha512-uOgF0kS5MJv9ZvCz7x6T2EXJSzotiybApn4XlOgoTX0xdtyVIJ7pF+6cGPxiEq/dpBiTfMiw7Yc81JcwhSYA0Q==", "funding": [ { "type": "individual", @@ -107,17 +107,17 @@ } ], "dependencies": { - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/rlp": "^5.5.0" + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/keccak256": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/rlp": "^5.6.1" } }, "node_modules/@ethersproject/base64": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.5.0.tgz", - "integrity": "sha512-tdayUKhU1ljrlHzEWbStXazDpsx4eg1dBXUSI6+mHlYklOXoXF6lZvw8tnD6oVaWfnMxAgRSKROg3cVKtCcppA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.6.1.tgz", + "integrity": "sha512-qB76rjop6a0RIYYMiB4Eh/8n+Hxu2NIZm8S/Q7kNo5pmZfXhHGHmS4MinUainiBC54SCyRnwzL+KZjj8zbsSsw==", "funding": [ { "type": "individual", @@ -129,13 +129,13 @@ } ], "dependencies": { - "@ethersproject/bytes": "^5.5.0" + "@ethersproject/bytes": "^5.6.1" } }, "node_modules/@ethersproject/basex": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.5.0.tgz", - "integrity": "sha512-ZIodwhHpVJ0Y3hUCfUucmxKsWQA5TMnavp5j/UOuDdzZWzJlRmuOjcTMIGgHCYuZmHt36BfiSyQPSRskPxbfaQ==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.6.1.tgz", + "integrity": "sha512-a52MkVz4vuBXR06nvflPMotld1FJWSj2QT0985v7P/emPZO00PucFAkbcmq2vpVU7Ts7umKiSI6SppiLykVWsA==", "funding": [ { "type": "individual", @@ -147,14 +147,14 @@ } ], "dependencies": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/properties": "^5.5.0" + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/properties": "^5.6.0" } }, "node_modules/@ethersproject/bignumber": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.5.0.tgz", - "integrity": "sha512-6Xytlwvy6Rn3U3gKEc1vP7nR92frHkv6wtVr95LFR3jREXiCPzdWxKQ1cx4JGQBXxcguAwjA8murlYN2TSiEbg==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.6.2.tgz", + "integrity": "sha512-v7+EEUbhGqT3XJ9LMPsKvXYHFc8eHxTowFCG/HgJErmq4XHJ2WR7aeyICg3uTOAQ7Icn0GFHAohXEhxQHq4Ubw==", "funding": [ { "type": "individual", @@ -166,15 +166,15 @@ } ], "dependencies": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "bn.js": "^4.11.9" + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "bn.js": "^5.2.1" } }, "node_modules/@ethersproject/bytes": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.5.0.tgz", - "integrity": "sha512-ABvc7BHWhZU9PNM/tANm/Qx4ostPGadAuQzWTr3doklZOhDlmcBqclrQe/ZXUIj3K8wC28oYeuRa+A37tX9kog==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.6.1.tgz", + "integrity": "sha512-NwQt7cKn5+ZE4uDn+X5RAXLp46E1chXoaMmrxAyA0rblpxz8t58lVkrHXoRIn0lz1joQElQ8410GqhTqMOwc6g==", "funding": [ { "type": "individual", @@ -186,13 +186,13 @@ } ], "dependencies": { - "@ethersproject/logger": "^5.5.0" + "@ethersproject/logger": "^5.6.0" } }, "node_modules/@ethersproject/constants": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.5.0.tgz", - "integrity": "sha512-2MsRRVChkvMWR+GyMGY4N1sAX9Mt3J9KykCsgUFd/1mwS0UH1qw+Bv9k1UJb3X3YJYFco9H20pjSlOIfCG5HYQ==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.6.1.tgz", + "integrity": "sha512-QSq9WVnZbxXYFftrjSjZDUshp6/eKp6qrtdBtUCm0QxCV5z1fG/w3kdlcsjMCQuQHUnAclKoK7XpXMezhRDOLg==", "funding": [ { "type": "individual", @@ -204,13 +204,13 @@ } ], "dependencies": { - "@ethersproject/bignumber": "^5.5.0" + "@ethersproject/bignumber": "^5.6.2" } }, "node_modules/@ethersproject/contracts": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.5.0.tgz", - "integrity": "sha512-2viY7NzyvJkh+Ug17v7g3/IJC8HqZBDcOjYARZLdzRxrfGlRgmYgl6xPRKVbEzy1dWKw/iv7chDcS83pg6cLxg==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.6.2.tgz", + "integrity": "sha512-hguUA57BIKi6WY0kHvZp6PwPlWF87MCeB4B7Z7AbUpTxfFXFdn/3b0GmjZPagIHS+3yhcBJDnuEfU4Xz+Ks/8g==", "funding": [ { "type": "individual", @@ -222,22 +222,22 @@ } ], "dependencies": { - "@ethersproject/abi": "^5.5.0", - "@ethersproject/abstract-provider": "^5.5.0", - "@ethersproject/abstract-signer": "^5.5.0", - "@ethersproject/address": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/constants": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/transactions": "^5.5.0" + "@ethersproject/abi": "^5.6.3", + "@ethersproject/abstract-provider": "^5.6.1", + "@ethersproject/abstract-signer": "^5.6.2", + "@ethersproject/address": "^5.6.1", + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/constants": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/transactions": "^5.6.2" } }, "node_modules/@ethersproject/hash": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.5.0.tgz", - "integrity": "sha512-dnGVpK1WtBjmnp3mUT0PlU2MpapnwWI0PibldQEq1408tQBAbZpPidkWoVVuNMOl/lISO3+4hXZWCL3YV7qzfg==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.6.1.tgz", + "integrity": "sha512-L1xAHurbaxG8VVul4ankNX5HgQ8PNCTrnVXEiFnE9xoRnaUcgfD12tZINtDinSllxPLCtGwguQxJ5E6keE84pA==", "funding": [ { "type": "individual", @@ -249,20 +249,20 @@ } ], "dependencies": { - "@ethersproject/abstract-signer": "^5.5.0", - "@ethersproject/address": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/strings": "^5.5.0" + "@ethersproject/abstract-signer": "^5.6.2", + "@ethersproject/address": "^5.6.1", + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/keccak256": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/strings": "^5.6.1" } }, "node_modules/@ethersproject/hdnode": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.5.0.tgz", - "integrity": "sha512-mcSOo9zeUg1L0CoJH7zmxwUG5ggQHU1UrRf8jyTYy6HxdZV+r0PBoL1bxr+JHIPXRzS6u/UW4mEn43y0tmyF8Q==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.6.2.tgz", + "integrity": "sha512-tERxW8Ccf9CxW2db3WsN01Qao3wFeRsfYY9TCuhmG0xNpl2IO8wgXU3HtWIZ49gUWPggRy4Yg5axU0ACaEKf1Q==", "funding": [ { "type": "individual", @@ -274,24 +274,24 @@ } ], "dependencies": { - "@ethersproject/abstract-signer": "^5.5.0", - "@ethersproject/basex": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/pbkdf2": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/sha2": "^5.5.0", - "@ethersproject/signing-key": "^5.5.0", - "@ethersproject/strings": "^5.5.0", - "@ethersproject/transactions": "^5.5.0", - "@ethersproject/wordlists": "^5.5.0" + "@ethersproject/abstract-signer": "^5.6.2", + "@ethersproject/basex": "^5.6.1", + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/pbkdf2": "^5.6.1", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/sha2": "^5.6.1", + "@ethersproject/signing-key": "^5.6.2", + "@ethersproject/strings": "^5.6.1", + "@ethersproject/transactions": "^5.6.2", + "@ethersproject/wordlists": "^5.6.1" } }, "node_modules/@ethersproject/json-wallets": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.5.0.tgz", - "integrity": "sha512-9lA21XQnCdcS72xlBn1jfQdj2A1VUxZzOzi9UkNdnokNKke/9Ya2xA9aIK1SC3PQyBDLt4C+dfps7ULpkvKikQ==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.6.1.tgz", + "integrity": "sha512-KfyJ6Zwz3kGeX25nLihPwZYlDqamO6pfGKNnVMWWfEVVp42lTfCZVXXy5Ie8IZTN0HKwAngpIPi7gk4IJzgmqQ==", "funding": [ { "type": "individual", @@ -303,25 +303,25 @@ } ], "dependencies": { - "@ethersproject/abstract-signer": "^5.5.0", - "@ethersproject/address": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/hdnode": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/pbkdf2": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/random": "^5.5.0", - "@ethersproject/strings": "^5.5.0", - "@ethersproject/transactions": "^5.5.0", + "@ethersproject/abstract-signer": "^5.6.2", + "@ethersproject/address": "^5.6.1", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/hdnode": "^5.6.2", + "@ethersproject/keccak256": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/pbkdf2": "^5.6.1", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/random": "^5.6.1", + "@ethersproject/strings": "^5.6.1", + "@ethersproject/transactions": "^5.6.2", "aes-js": "3.0.0", "scrypt-js": "3.0.1" } }, "node_modules/@ethersproject/keccak256": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.5.0.tgz", - "integrity": "sha512-5VoFCTjo2rYbBe1l2f4mccaRFN/4VQEYFwwn04aJV2h7qf4ZvI2wFxUE1XOX+snbwCLRzIeikOqtAoPwMza9kg==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.6.1.tgz", + "integrity": "sha512-bB7DQHCTRDooZZdL3lk9wpL0+XuG3XLGHLh3cePnybsO3V0rdCAOQGpn/0R3aODmnTOOkCATJiD2hnL+5bwthA==", "funding": [ { "type": "individual", @@ -333,14 +333,14 @@ } ], "dependencies": { - "@ethersproject/bytes": "^5.5.0", + "@ethersproject/bytes": "^5.6.1", "js-sha3": "0.8.0" } }, "node_modules/@ethersproject/logger": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.5.0.tgz", - "integrity": "sha512-rIY/6WPm7T8n3qS2vuHTUBPdXHl+rGxWxW5okDfo9J4Z0+gRRZT0msvUdIJkE4/HS29GUMziwGaaKO2bWONBrg==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.6.0.tgz", + "integrity": "sha512-BiBWllUROH9w+P21RzoxJKzqoqpkyM1pRnEKG69bulE9TSQD8SAIvTQqIMZmmCO8pUNkgLP1wndX1gKghSpBmg==", "funding": [ { "type": "individual", @@ -353,9 +353,9 @@ ] }, "node_modules/@ethersproject/networks": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.5.0.tgz", - "integrity": "sha512-KWfP3xOnJeF89Uf/FCJdV1a2aDJe5XTN2N52p4fcQ34QhDqQFkgQKZ39VGtiqUgHcLI8DfT0l9azC3KFTunqtA==", + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.6.4.tgz", + "integrity": "sha512-KShHeHPahHI2UlWdtDMn2lJETcbtaJge4k7XSjDR9h79QTd6yQJmv6Cp2ZA4JdqWnhszAOLSuJEd9C0PRw7hSQ==", "funding": [ { "type": "individual", @@ -367,13 +367,13 @@ } ], "dependencies": { - "@ethersproject/logger": "^5.5.0" + "@ethersproject/logger": "^5.6.0" } }, "node_modules/@ethersproject/pbkdf2": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.5.0.tgz", - "integrity": "sha512-SaDvQFvXPnz1QGpzr6/HToLifftSXGoXrbpZ6BvoZhmx4bNLHrxDe8MZisuecyOziP1aVEwzC2Hasj+86TgWVg==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.6.1.tgz", + "integrity": "sha512-k4gRQ+D93zDRPNUfmduNKq065uadC2YjMP/CqwwX5qG6R05f47boq6pLZtV/RnC4NZAYOPH1Cyo54q0c9sshRQ==", "funding": [ { "type": "individual", @@ -385,14 +385,14 @@ } ], "dependencies": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/sha2": "^5.5.0" + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/sha2": "^5.6.1" } }, "node_modules/@ethersproject/properties": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.5.0.tgz", - "integrity": "sha512-l3zRQg3JkD8EL3CPjNK5g7kMx4qSwiR60/uk5IVjd3oq1MZR5qUg40CNOoEJoX5wc3DyY5bt9EbMk86C7x0DNA==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.6.0.tgz", + "integrity": "sha512-szoOkHskajKePTJSZ46uHUWWkbv7TzP2ypdEK6jGMqJaEt2sb0jCgfBo0gH0m2HBpRixMuJ6TBRaQCF7a9DoCg==", "funding": [ { "type": "individual", @@ -404,13 +404,13 @@ } ], "dependencies": { - "@ethersproject/logger": "^5.5.0" + "@ethersproject/logger": "^5.6.0" } }, "node_modules/@ethersproject/providers": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.5.0.tgz", - "integrity": "sha512-xqMbDnS/FPy+J/9mBLKddzyLLAQFjrVff5g00efqxPzcAwXiR+SiCGVy6eJ5iAIirBOATjx7QLhDNPGV+AEQsw==", + "version": "5.6.8", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.6.8.tgz", + "integrity": "sha512-Wf+CseT/iOJjrGtAOf3ck9zS7AgPmr2fZ3N97r4+YXN3mBePTG2/bJ8DApl9mVwYL+RpYbNxMEkEp4mPGdwG/w==", "funding": [ { "type": "individual", @@ -422,23 +422,24 @@ } ], "dependencies": { - "@ethersproject/abstract-provider": "^5.5.0", - "@ethersproject/abstract-signer": "^5.5.0", - "@ethersproject/address": "^5.5.0", - "@ethersproject/basex": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/constants": "^5.5.0", - "@ethersproject/hash": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/networks": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/random": "^5.5.0", - "@ethersproject/rlp": "^5.5.0", - "@ethersproject/sha2": "^5.5.0", - "@ethersproject/strings": "^5.5.0", - "@ethersproject/transactions": "^5.5.0", - "@ethersproject/web": "^5.5.0", + "@ethersproject/abstract-provider": "^5.6.1", + "@ethersproject/abstract-signer": "^5.6.2", + "@ethersproject/address": "^5.6.1", + "@ethersproject/base64": "^5.6.1", + "@ethersproject/basex": "^5.6.1", + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/constants": "^5.6.1", + "@ethersproject/hash": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/networks": "^5.6.3", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/random": "^5.6.1", + "@ethersproject/rlp": "^5.6.1", + "@ethersproject/sha2": "^5.6.1", + "@ethersproject/strings": "^5.6.1", + "@ethersproject/transactions": "^5.6.2", + "@ethersproject/web": "^5.6.1", "bech32": "1.1.4", "ws": "7.4.6" } @@ -464,9 +465,9 @@ } }, "node_modules/@ethersproject/random": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.5.0.tgz", - "integrity": "sha512-egGYZwZ/YIFKMHcoBUo8t3a8Hb/TKYX8BCBoLjudVCZh892welR3jOxgOmb48xznc9bTcMm7Tpwc1gHC1PFNFQ==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.6.1.tgz", + "integrity": "sha512-/wtPNHwbmng+5yi3fkipA8YBT59DdkGRoC2vWk09Dci/q5DlgnMkhIycjHlavrvrjJBkFjO/ueLyT+aUDfc4lA==", "funding": [ { "type": "individual", @@ -478,14 +479,14 @@ } ], "dependencies": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0" + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0" } }, "node_modules/@ethersproject/rlp": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.5.0.tgz", - "integrity": "sha512-hLv8XaQ8PTI9g2RHoQGf/WSxBfTB/NudRacbzdxmst5VHAqd1sMibWG7SENzT5Dj3yZ3kJYx+WiRYEcQTAkcYA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.6.1.tgz", + "integrity": "sha512-uYjmcZx+DKlFUk7a5/W9aQVaoEC7+1MOBgNtvNg13+RnuUwT4F0zTovC0tmay5SmRslb29V1B7Y5KCri46WhuQ==", "funding": [ { "type": "individual", @@ -497,14 +498,14 @@ } ], "dependencies": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0" + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0" } }, "node_modules/@ethersproject/sha2": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.5.0.tgz", - "integrity": "sha512-B5UBoglbCiHamRVPLA110J+2uqsifpZaTmid2/7W5rbtYVz6gus6/hSDieIU/6gaKIDcOj12WnOdiymEUHIAOA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.6.1.tgz", + "integrity": "sha512-5K2GyqcW7G4Yo3uenHegbXRPDgARpWUiXc6RiF7b6i/HXUoWlb7uCARh7BAHg7/qT/Q5ydofNwiZcim9qpjB6g==", "funding": [ { "type": "individual", @@ -516,15 +517,15 @@ } ], "dependencies": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0", "hash.js": "1.1.7" } }, "node_modules/@ethersproject/signing-key": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.5.0.tgz", - "integrity": "sha512-5VmseH7qjtNmDdZBswavhotYbWB0bOwKIlOTSlX14rKn5c11QmJwGt4GHeo7NrL/Ycl7uo9AHvEqs5xZgFBTng==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.6.2.tgz", + "integrity": "sha512-jVbu0RuP7EFpw82vHcL+GP35+KaNruVAZM90GxgQnGqB6crhBqW/ozBfFvdeImtmb4qPko0uxXjn8l9jpn0cwQ==", "funding": [ { "type": "individual", @@ -536,18 +537,18 @@ } ], "dependencies": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "bn.js": "^4.11.9", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "bn.js": "^5.2.1", "elliptic": "6.5.4", "hash.js": "1.1.7" } }, "node_modules/@ethersproject/solidity": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.5.0.tgz", - "integrity": "sha512-9NgZs9LhGMj6aCtHXhtmFQ4AN4sth5HuFXVvAQtzmm0jpSCNOTGtrHZJAeYTh7MBjRR8brylWZxBZR9zDStXbw==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.6.1.tgz", + "integrity": "sha512-KWqVLkUUoLBfL1iwdzUVlkNqAUIFMpbbeH0rgCfKmJp0vFtY4AsaN91gHKo9ZZLkC4UOm3cI3BmMV4N53BOq4g==", "funding": [ { "type": "individual", @@ -559,18 +560,18 @@ } ], "dependencies": { - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/sha2": "^5.5.0", - "@ethersproject/strings": "^5.5.0" + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/keccak256": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/sha2": "^5.6.1", + "@ethersproject/strings": "^5.6.1" } }, "node_modules/@ethersproject/strings": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.5.0.tgz", - "integrity": "sha512-9fy3TtF5LrX/wTrBaT8FGE6TDJyVjOvXynXJz5MT5azq+E6D92zuKNx7i29sWW2FjVOaWjAsiZ1ZWznuduTIIQ==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.6.1.tgz", + "integrity": "sha512-2X1Lgk6Jyfg26MUnsHiT456U9ijxKUybz8IM1Vih+NJxYtXhmvKBcHOmvGqpFSVJ0nQ4ZCoIViR8XlRw1v/+Cw==", "funding": [ { "type": "individual", @@ -582,15 +583,15 @@ } ], "dependencies": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/constants": "^5.5.0", - "@ethersproject/logger": "^5.5.0" + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/constants": "^5.6.1", + "@ethersproject/logger": "^5.6.0" } }, "node_modules/@ethersproject/transactions": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.5.0.tgz", - "integrity": "sha512-9RZYSKX26KfzEd/1eqvv8pLauCKzDTub0Ko4LfIgaERvRuwyaNV78mJs7cpIgZaDl6RJui4o49lHwwCM0526zA==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.6.2.tgz", + "integrity": "sha512-BuV63IRPHmJvthNkkt9G70Ullx6AcM+SDc+a8Aw/8Yew6YwT51TcBKEp1P4oOQ/bP25I18JJr7rcFRgFtU9B2Q==", "funding": [ { "type": "individual", @@ -602,21 +603,21 @@ } ], "dependencies": { - "@ethersproject/address": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/constants": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/rlp": "^5.5.0", - "@ethersproject/signing-key": "^5.5.0" + "@ethersproject/address": "^5.6.1", + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/constants": "^5.6.1", + "@ethersproject/keccak256": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/rlp": "^5.6.1", + "@ethersproject/signing-key": "^5.6.2" } }, "node_modules/@ethersproject/units": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.5.0.tgz", - "integrity": "sha512-7+DpjiZk4v6wrikj+TCyWWa9dXLNU73tSTa7n0TSJDxkYbV3Yf1eRh9ToMLlZtuctNYu9RDNNy2USq3AdqSbag==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.6.1.tgz", + "integrity": "sha512-rEfSEvMQ7obcx3KWD5EWWx77gqv54K6BKiZzKxkQJqtpriVsICrktIQmKl8ReNToPeIYPnFHpXvKpi068YFZXw==", "funding": [ { "type": "individual", @@ -628,15 +629,15 @@ } ], "dependencies": { - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/constants": "^5.5.0", - "@ethersproject/logger": "^5.5.0" + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/constants": "^5.6.1", + "@ethersproject/logger": "^5.6.0" } }, "node_modules/@ethersproject/wallet": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.5.0.tgz", - "integrity": "sha512-Mlu13hIctSYaZmUOo7r2PhNSd8eaMPVXe1wxrz4w4FCE4tDYBywDH+bAR1Xz2ADyXGwqYMwstzTrtUVIsKDO0Q==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.6.2.tgz", + "integrity": "sha512-lrgh0FDQPuOnHcF80Q3gHYsSUODp6aJLAdDmDV0xKCN/T7D99ta1jGVhulg3PY8wiXEngD0DfM0I2XKXlrqJfg==", "funding": [ { "type": "individual", @@ -648,27 +649,27 @@ } ], "dependencies": { - "@ethersproject/abstract-provider": "^5.5.0", - "@ethersproject/abstract-signer": "^5.5.0", - "@ethersproject/address": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/hash": "^5.5.0", - "@ethersproject/hdnode": "^5.5.0", - "@ethersproject/json-wallets": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/random": "^5.5.0", - "@ethersproject/signing-key": "^5.5.0", - "@ethersproject/transactions": "^5.5.0", - "@ethersproject/wordlists": "^5.5.0" + "@ethersproject/abstract-provider": "^5.6.1", + "@ethersproject/abstract-signer": "^5.6.2", + "@ethersproject/address": "^5.6.1", + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/hash": "^5.6.1", + "@ethersproject/hdnode": "^5.6.2", + "@ethersproject/json-wallets": "^5.6.1", + "@ethersproject/keccak256": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/random": "^5.6.1", + "@ethersproject/signing-key": "^5.6.2", + "@ethersproject/transactions": "^5.6.2", + "@ethersproject/wordlists": "^5.6.1" } }, "node_modules/@ethersproject/web": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.5.0.tgz", - "integrity": "sha512-BEgY0eL5oH4mAo37TNYVrFeHsIXLRxggCRG/ksRIxI2X5uj5IsjGmcNiRN/VirQOlBxcUhCgHhaDLG4m6XAVoA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.6.1.tgz", + "integrity": "sha512-/vSyzaQlNXkO1WV+RneYKqCJwualcUdx/Z3gseVovZP0wIlOFcCE1hkRhKBH8ImKbGQbMl9EAAyJFrJu7V0aqA==", "funding": [ { "type": "individual", @@ -680,17 +681,17 @@ } ], "dependencies": { - "@ethersproject/base64": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/strings": "^5.5.0" + "@ethersproject/base64": "^5.6.1", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/strings": "^5.6.1" } }, "node_modules/@ethersproject/wordlists": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.5.0.tgz", - "integrity": "sha512-bL0UTReWDiaQJJYOC9sh/XcRu/9i2jMrzf8VLRmPKx58ckSlOJiohODkECCO50dtLZHcGU6MLXQ4OOrgBwP77Q==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.6.1.tgz", + "integrity": "sha512-wiPRgBpNbNwCQFoCr8bcWO8o5I810cqO6mkdtKfLKFlLxeCWcnzDi4Alu8iyNzlhYuS9npCwivMbRWF19dyblw==", "funding": [ { "type": "individual", @@ -702,17 +703,17 @@ } ], "dependencies": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/hash": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/strings": "^5.5.0" + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/hash": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/strings": "^5.6.1" } }, "node_modules/aes-js": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", - "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==" }, "node_modules/axios": { "version": "0.21.4", @@ -728,19 +729,19 @@ "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" }, "node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" }, "node_modules/brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" }, "node_modules/bufferutil": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", - "integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz", + "integrity": "sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==", "hasInstallScript": true, "dependencies": { "node-gyp-build": "^4.3.0" @@ -792,14 +793,18 @@ } }, "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", "dependencies": { - "object-keys": "^1.0.12" + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/dotenv": { @@ -824,31 +829,39 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, "node_modules/es-abstract": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", "dependencies": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", "get-intrinsic": "^1.1.1", "get-symbol-description": "^1.0.0", "has": "^1.0.3", - "has-symbols": "^1.0.2", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", + "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", + "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -874,19 +887,23 @@ } }, "node_modules/es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "hasInstallScript": true, "dependencies": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" } }, "node_modules/es6-iterator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", "dependencies": { "d": "1", "es5-ext": "^0.10.35", @@ -903,9 +920,9 @@ } }, "node_modules/ethers": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.5.1.tgz", - "integrity": "sha512-RodEvUFZI+EmFcE6bwkuJqpCYHazdzeR1nMzg+YWQSmQEsNtfl1KHGfp/FWZYl48bI/g7cgBeP2IlPthjiVngw==", + "version": "5.6.9", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.6.9.tgz", + "integrity": "sha512-lMGC2zv9HC5EC+8r429WaWu3uWJUCgUCt8xxKCFqkrFuBDZXDYIdzDUECxzjf2BMF8IVBByY1EBoGSL3RTm8RA==", "funding": [ { "type": "individual", @@ -917,36 +934,36 @@ } ], "dependencies": { - "@ethersproject/abi": "5.5.0", - "@ethersproject/abstract-provider": "5.5.1", - "@ethersproject/abstract-signer": "5.5.0", - "@ethersproject/address": "5.5.0", - "@ethersproject/base64": "5.5.0", - "@ethersproject/basex": "5.5.0", - "@ethersproject/bignumber": "5.5.0", - "@ethersproject/bytes": "5.5.0", - "@ethersproject/constants": "5.5.0", - "@ethersproject/contracts": "5.5.0", - "@ethersproject/hash": "5.5.0", - "@ethersproject/hdnode": "5.5.0", - "@ethersproject/json-wallets": "5.5.0", - "@ethersproject/keccak256": "5.5.0", - "@ethersproject/logger": "5.5.0", - "@ethersproject/networks": "5.5.0", - "@ethersproject/pbkdf2": "5.5.0", - "@ethersproject/properties": "5.5.0", - "@ethersproject/providers": "5.5.0", - "@ethersproject/random": "5.5.0", - "@ethersproject/rlp": "5.5.0", - "@ethersproject/sha2": "5.5.0", - "@ethersproject/signing-key": "5.5.0", - "@ethersproject/solidity": "5.5.0", - "@ethersproject/strings": "5.5.0", - "@ethersproject/transactions": "5.5.0", - "@ethersproject/units": "5.5.0", - "@ethersproject/wallet": "5.5.0", - "@ethersproject/web": "5.5.0", - "@ethersproject/wordlists": "5.5.0" + "@ethersproject/abi": "5.6.4", + "@ethersproject/abstract-provider": "5.6.1", + "@ethersproject/abstract-signer": "5.6.2", + "@ethersproject/address": "5.6.1", + "@ethersproject/base64": "5.6.1", + "@ethersproject/basex": "5.6.1", + "@ethersproject/bignumber": "5.6.2", + "@ethersproject/bytes": "5.6.1", + "@ethersproject/constants": "5.6.1", + "@ethersproject/contracts": "5.6.2", + "@ethersproject/hash": "5.6.1", + "@ethersproject/hdnode": "5.6.2", + "@ethersproject/json-wallets": "5.6.1", + "@ethersproject/keccak256": "5.6.1", + "@ethersproject/logger": "5.6.0", + "@ethersproject/networks": "5.6.4", + "@ethersproject/pbkdf2": "5.6.1", + "@ethersproject/properties": "5.6.0", + "@ethersproject/providers": "5.6.8", + "@ethersproject/random": "5.6.1", + "@ethersproject/rlp": "5.6.1", + "@ethersproject/sha2": "5.6.1", + "@ethersproject/signing-key": "5.6.2", + "@ethersproject/solidity": "5.6.1", + "@ethersproject/strings": "5.6.1", + "@ethersproject/transactions": "5.6.2", + "@ethersproject/units": "5.6.1", + "@ethersproject/wallet": "5.6.2", + "@ethersproject/web": "5.6.1", + "@ethersproject/wordlists": "5.6.1" } }, "node_modules/ext": { @@ -958,14 +975,14 @@ } }, "node_modules/ext/node_modules/type": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", - "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==" + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" }, "node_modules/fetch-blob": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz", - "integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", "funding": [ { "type": "github", @@ -985,9 +1002,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", "funding": [ { "type": "individual", @@ -1019,14 +1036,39 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", - "has-symbols": "^1.0.1" + "has-symbols": "^1.0.3" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1059,17 +1101,28 @@ } }, "node_modules/has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "engines": { "node": ">= 0.4" }, @@ -1103,7 +1156,7 @@ "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -1180,9 +1233,9 @@ } }, "node_modules/is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "engines": { "node": ">= 0.4" }, @@ -1191,9 +1244,9 @@ } }, "node_modules/is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -1220,9 +1273,12 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", - "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dependencies": { + "call-bind": "^1.0.2" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1258,14 +1314,14 @@ "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "node_modules/is-weakref": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", - "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", "dependencies": { - "call-bind": "^1.0.0" + "call-bind": "^1.0.2" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1284,17 +1340,17 @@ "node_modules/minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "node_modules/node-domexception": { "version": "1.0.0", @@ -1315,9 +1371,9 @@ } }, "node_modules/node-fetch": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.3.tgz", - "integrity": "sha512-AXP18u4pidSZ1xYXRDPY/8jdv3RAozIt/WLNR/MBGZAz+xjtlr90RvCnsvHQRiXyWliZF/CpytExp32UU67/SA==", + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -1332,9 +1388,9 @@ } }, "node_modules/node-gyp-build": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", + "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -1342,9 +1398,9 @@ } }, "node_modules/object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1358,13 +1414,13 @@ } }, "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.3.tgz", + "integrity": "sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==", "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", "object-keys": "^1.1.1" }, "engines": { @@ -1398,6 +1454,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/scrypt-js": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", @@ -1417,24 +1489,26 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1454,13 +1528,13 @@ } }, "node_modules/unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" }, "funding": { @@ -1468,9 +1542,9 @@ } }, "node_modules/utf-8-validate": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz", - "integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", + "integrity": "sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==", "hasInstallScript": true, "dependencies": { "node-gyp-build": "^4.3.0" @@ -1480,9 +1554,9 @@ } }, "node_modules/web-streams-polyfill": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", - "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", "engines": { "node": ">= 8" } @@ -1532,9 +1606,9 @@ } }, "node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", "engines": { "node": ">=10.0.0" }, @@ -1554,15 +1628,15 @@ "node_modules/yaeti": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", "engines": { "node": ">=0.10.32" } }, "node_modules/zksync": { - "version": "0.11.6", - "resolved": "https://registry.npmjs.org/zksync/-/zksync-0.11.6.tgz", - "integrity": "sha512-p54+KjmKhd5MUe0rWZDcqM3k9UT2BqpYMp2fOpl5m+Hp9GLn+PMAiKYaWtzgjnYvZ7ceOdKq8CEHKKEbpLyZ0A==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/zksync/-/zksync-0.13.1.tgz", + "integrity": "sha512-1WRMommc8qTuwh3qk6/dv34Eb+PnKBmM41sQMaAjkC47aw2Xm4RuqJfvHXPoKnZjtIhBAzXA25rwevxqGukQHw==", "dependencies": { "axios": "^0.21.2", "websocket": "^1.0.30", @@ -1582,234 +1656,235 @@ }, "dependencies": { "@ethersproject/abi": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.5.0.tgz", - "integrity": "sha512-loW7I4AohP5KycATvc0MgujU6JyCHPqHdeoo9z3Nr9xEiNioxa65ccdm1+fsoJhkuhdRtfcL8cfyGamz2AxZ5w==", + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.6.4.tgz", + "integrity": "sha512-TTeZUlCeIHG6527/2goZA6gW5F8Emoc7MrZDC7hhP84aRGvW3TEdTnZR08Ls88YXM1m2SuK42Osw/jSi3uO8gg==", "requires": { - "@ethersproject/address": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/constants": "^5.5.0", - "@ethersproject/hash": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/strings": "^5.5.0" + "@ethersproject/address": "^5.6.1", + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/constants": "^5.6.1", + "@ethersproject/hash": "^5.6.1", + "@ethersproject/keccak256": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/strings": "^5.6.1" } }, "@ethersproject/abstract-provider": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.5.1.tgz", - "integrity": "sha512-m+MA/ful6eKbxpr99xUYeRvLkfnlqzrF8SZ46d/xFB1A7ZVknYc/sXJG0RcufF52Qn2jeFj1hhcoQ7IXjNKUqg==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.6.1.tgz", + "integrity": "sha512-BxlIgogYJtp1FS8Muvj8YfdClk3unZH0vRMVX791Z9INBNT/kuACZ9GzaY1Y4yFq+YSy6/w4gzj3HCRKrK9hsQ==", "requires": { - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/networks": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/transactions": "^5.5.0", - "@ethersproject/web": "^5.5.0" + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/networks": "^5.6.3", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/transactions": "^5.6.2", + "@ethersproject/web": "^5.6.1" } }, "@ethersproject/abstract-signer": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.5.0.tgz", - "integrity": "sha512-lj//7r250MXVLKI7sVarXAbZXbv9P50lgmJQGr2/is82EwEb8r7HrxsmMqAjTsztMYy7ohrIhGMIml+Gx4D3mA==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.6.2.tgz", + "integrity": "sha512-n1r6lttFBG0t2vNiI3HoWaS/KdOt8xyDjzlP2cuevlWLG6EX0OwcKLyG/Kp/cuwNxdy/ous+R/DEMdTUwWQIjQ==", "requires": { - "@ethersproject/abstract-provider": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0" + "@ethersproject/abstract-provider": "^5.6.1", + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0" } }, "@ethersproject/address": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.5.0.tgz", - "integrity": "sha512-l4Nj0eWlTUh6ro5IbPTgbpT4wRbdH5l8CQf7icF7sb/SI3Nhd9Y9HzhonTSTi6CefI0necIw7LJqQPopPLZyWw==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.6.1.tgz", + "integrity": "sha512-uOgF0kS5MJv9ZvCz7x6T2EXJSzotiybApn4XlOgoTX0xdtyVIJ7pF+6cGPxiEq/dpBiTfMiw7Yc81JcwhSYA0Q==", "requires": { - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/rlp": "^5.5.0" + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/keccak256": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/rlp": "^5.6.1" } }, "@ethersproject/base64": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.5.0.tgz", - "integrity": "sha512-tdayUKhU1ljrlHzEWbStXazDpsx4eg1dBXUSI6+mHlYklOXoXF6lZvw8tnD6oVaWfnMxAgRSKROg3cVKtCcppA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.6.1.tgz", + "integrity": "sha512-qB76rjop6a0RIYYMiB4Eh/8n+Hxu2NIZm8S/Q7kNo5pmZfXhHGHmS4MinUainiBC54SCyRnwzL+KZjj8zbsSsw==", "requires": { - "@ethersproject/bytes": "^5.5.0" + "@ethersproject/bytes": "^5.6.1" } }, "@ethersproject/basex": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.5.0.tgz", - "integrity": "sha512-ZIodwhHpVJ0Y3hUCfUucmxKsWQA5TMnavp5j/UOuDdzZWzJlRmuOjcTMIGgHCYuZmHt36BfiSyQPSRskPxbfaQ==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.6.1.tgz", + "integrity": "sha512-a52MkVz4vuBXR06nvflPMotld1FJWSj2QT0985v7P/emPZO00PucFAkbcmq2vpVU7Ts7umKiSI6SppiLykVWsA==", "requires": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/properties": "^5.5.0" + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/properties": "^5.6.0" } }, "@ethersproject/bignumber": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.5.0.tgz", - "integrity": "sha512-6Xytlwvy6Rn3U3gKEc1vP7nR92frHkv6wtVr95LFR3jREXiCPzdWxKQ1cx4JGQBXxcguAwjA8murlYN2TSiEbg==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.6.2.tgz", + "integrity": "sha512-v7+EEUbhGqT3XJ9LMPsKvXYHFc8eHxTowFCG/HgJErmq4XHJ2WR7aeyICg3uTOAQ7Icn0GFHAohXEhxQHq4Ubw==", "requires": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "bn.js": "^4.11.9" + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "bn.js": "^5.2.1" } }, "@ethersproject/bytes": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.5.0.tgz", - "integrity": "sha512-ABvc7BHWhZU9PNM/tANm/Qx4ostPGadAuQzWTr3doklZOhDlmcBqclrQe/ZXUIj3K8wC28oYeuRa+A37tX9kog==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.6.1.tgz", + "integrity": "sha512-NwQt7cKn5+ZE4uDn+X5RAXLp46E1chXoaMmrxAyA0rblpxz8t58lVkrHXoRIn0lz1joQElQ8410GqhTqMOwc6g==", "requires": { - "@ethersproject/logger": "^5.5.0" + "@ethersproject/logger": "^5.6.0" } }, "@ethersproject/constants": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.5.0.tgz", - "integrity": "sha512-2MsRRVChkvMWR+GyMGY4N1sAX9Mt3J9KykCsgUFd/1mwS0UH1qw+Bv9k1UJb3X3YJYFco9H20pjSlOIfCG5HYQ==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.6.1.tgz", + "integrity": "sha512-QSq9WVnZbxXYFftrjSjZDUshp6/eKp6qrtdBtUCm0QxCV5z1fG/w3kdlcsjMCQuQHUnAclKoK7XpXMezhRDOLg==", "requires": { - "@ethersproject/bignumber": "^5.5.0" + "@ethersproject/bignumber": "^5.6.2" } }, "@ethersproject/contracts": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.5.0.tgz", - "integrity": "sha512-2viY7NzyvJkh+Ug17v7g3/IJC8HqZBDcOjYARZLdzRxrfGlRgmYgl6xPRKVbEzy1dWKw/iv7chDcS83pg6cLxg==", - "requires": { - "@ethersproject/abi": "^5.5.0", - "@ethersproject/abstract-provider": "^5.5.0", - "@ethersproject/abstract-signer": "^5.5.0", - "@ethersproject/address": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/constants": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/transactions": "^5.5.0" + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.6.2.tgz", + "integrity": "sha512-hguUA57BIKi6WY0kHvZp6PwPlWF87MCeB4B7Z7AbUpTxfFXFdn/3b0GmjZPagIHS+3yhcBJDnuEfU4Xz+Ks/8g==", + "requires": { + "@ethersproject/abi": "^5.6.3", + "@ethersproject/abstract-provider": "^5.6.1", + "@ethersproject/abstract-signer": "^5.6.2", + "@ethersproject/address": "^5.6.1", + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/constants": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/transactions": "^5.6.2" } }, "@ethersproject/hash": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.5.0.tgz", - "integrity": "sha512-dnGVpK1WtBjmnp3mUT0PlU2MpapnwWI0PibldQEq1408tQBAbZpPidkWoVVuNMOl/lISO3+4hXZWCL3YV7qzfg==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.6.1.tgz", + "integrity": "sha512-L1xAHurbaxG8VVul4ankNX5HgQ8PNCTrnVXEiFnE9xoRnaUcgfD12tZINtDinSllxPLCtGwguQxJ5E6keE84pA==", "requires": { - "@ethersproject/abstract-signer": "^5.5.0", - "@ethersproject/address": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/strings": "^5.5.0" + "@ethersproject/abstract-signer": "^5.6.2", + "@ethersproject/address": "^5.6.1", + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/keccak256": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/strings": "^5.6.1" } }, "@ethersproject/hdnode": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.5.0.tgz", - "integrity": "sha512-mcSOo9zeUg1L0CoJH7zmxwUG5ggQHU1UrRf8jyTYy6HxdZV+r0PBoL1bxr+JHIPXRzS6u/UW4mEn43y0tmyF8Q==", - "requires": { - "@ethersproject/abstract-signer": "^5.5.0", - "@ethersproject/basex": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/pbkdf2": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/sha2": "^5.5.0", - "@ethersproject/signing-key": "^5.5.0", - "@ethersproject/strings": "^5.5.0", - "@ethersproject/transactions": "^5.5.0", - "@ethersproject/wordlists": "^5.5.0" + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.6.2.tgz", + "integrity": "sha512-tERxW8Ccf9CxW2db3WsN01Qao3wFeRsfYY9TCuhmG0xNpl2IO8wgXU3HtWIZ49gUWPggRy4Yg5axU0ACaEKf1Q==", + "requires": { + "@ethersproject/abstract-signer": "^5.6.2", + "@ethersproject/basex": "^5.6.1", + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/pbkdf2": "^5.6.1", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/sha2": "^5.6.1", + "@ethersproject/signing-key": "^5.6.2", + "@ethersproject/strings": "^5.6.1", + "@ethersproject/transactions": "^5.6.2", + "@ethersproject/wordlists": "^5.6.1" } }, "@ethersproject/json-wallets": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.5.0.tgz", - "integrity": "sha512-9lA21XQnCdcS72xlBn1jfQdj2A1VUxZzOzi9UkNdnokNKke/9Ya2xA9aIK1SC3PQyBDLt4C+dfps7ULpkvKikQ==", - "requires": { - "@ethersproject/abstract-signer": "^5.5.0", - "@ethersproject/address": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/hdnode": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/pbkdf2": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/random": "^5.5.0", - "@ethersproject/strings": "^5.5.0", - "@ethersproject/transactions": "^5.5.0", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.6.1.tgz", + "integrity": "sha512-KfyJ6Zwz3kGeX25nLihPwZYlDqamO6pfGKNnVMWWfEVVp42lTfCZVXXy5Ie8IZTN0HKwAngpIPi7gk4IJzgmqQ==", + "requires": { + "@ethersproject/abstract-signer": "^5.6.2", + "@ethersproject/address": "^5.6.1", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/hdnode": "^5.6.2", + "@ethersproject/keccak256": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/pbkdf2": "^5.6.1", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/random": "^5.6.1", + "@ethersproject/strings": "^5.6.1", + "@ethersproject/transactions": "^5.6.2", "aes-js": "3.0.0", "scrypt-js": "3.0.1" } }, "@ethersproject/keccak256": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.5.0.tgz", - "integrity": "sha512-5VoFCTjo2rYbBe1l2f4mccaRFN/4VQEYFwwn04aJV2h7qf4ZvI2wFxUE1XOX+snbwCLRzIeikOqtAoPwMza9kg==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.6.1.tgz", + "integrity": "sha512-bB7DQHCTRDooZZdL3lk9wpL0+XuG3XLGHLh3cePnybsO3V0rdCAOQGpn/0R3aODmnTOOkCATJiD2hnL+5bwthA==", "requires": { - "@ethersproject/bytes": "^5.5.0", + "@ethersproject/bytes": "^5.6.1", "js-sha3": "0.8.0" } }, "@ethersproject/logger": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.5.0.tgz", - "integrity": "sha512-rIY/6WPm7T8n3qS2vuHTUBPdXHl+rGxWxW5okDfo9J4Z0+gRRZT0msvUdIJkE4/HS29GUMziwGaaKO2bWONBrg==" + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.6.0.tgz", + "integrity": "sha512-BiBWllUROH9w+P21RzoxJKzqoqpkyM1pRnEKG69bulE9TSQD8SAIvTQqIMZmmCO8pUNkgLP1wndX1gKghSpBmg==" }, "@ethersproject/networks": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.5.0.tgz", - "integrity": "sha512-KWfP3xOnJeF89Uf/FCJdV1a2aDJe5XTN2N52p4fcQ34QhDqQFkgQKZ39VGtiqUgHcLI8DfT0l9azC3KFTunqtA==", + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.6.4.tgz", + "integrity": "sha512-KShHeHPahHI2UlWdtDMn2lJETcbtaJge4k7XSjDR9h79QTd6yQJmv6Cp2ZA4JdqWnhszAOLSuJEd9C0PRw7hSQ==", "requires": { - "@ethersproject/logger": "^5.5.0" + "@ethersproject/logger": "^5.6.0" } }, "@ethersproject/pbkdf2": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.5.0.tgz", - "integrity": "sha512-SaDvQFvXPnz1QGpzr6/HToLifftSXGoXrbpZ6BvoZhmx4bNLHrxDe8MZisuecyOziP1aVEwzC2Hasj+86TgWVg==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.6.1.tgz", + "integrity": "sha512-k4gRQ+D93zDRPNUfmduNKq065uadC2YjMP/CqwwX5qG6R05f47boq6pLZtV/RnC4NZAYOPH1Cyo54q0c9sshRQ==", "requires": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/sha2": "^5.5.0" + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/sha2": "^5.6.1" } }, "@ethersproject/properties": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.5.0.tgz", - "integrity": "sha512-l3zRQg3JkD8EL3CPjNK5g7kMx4qSwiR60/uk5IVjd3oq1MZR5qUg40CNOoEJoX5wc3DyY5bt9EbMk86C7x0DNA==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.6.0.tgz", + "integrity": "sha512-szoOkHskajKePTJSZ46uHUWWkbv7TzP2ypdEK6jGMqJaEt2sb0jCgfBo0gH0m2HBpRixMuJ6TBRaQCF7a9DoCg==", "requires": { - "@ethersproject/logger": "^5.5.0" + "@ethersproject/logger": "^5.6.0" } }, "@ethersproject/providers": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.5.0.tgz", - "integrity": "sha512-xqMbDnS/FPy+J/9mBLKddzyLLAQFjrVff5g00efqxPzcAwXiR+SiCGVy6eJ5iAIirBOATjx7QLhDNPGV+AEQsw==", - "requires": { - "@ethersproject/abstract-provider": "^5.5.0", - "@ethersproject/abstract-signer": "^5.5.0", - "@ethersproject/address": "^5.5.0", - "@ethersproject/basex": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/constants": "^5.5.0", - "@ethersproject/hash": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/networks": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/random": "^5.5.0", - "@ethersproject/rlp": "^5.5.0", - "@ethersproject/sha2": "^5.5.0", - "@ethersproject/strings": "^5.5.0", - "@ethersproject/transactions": "^5.5.0", - "@ethersproject/web": "^5.5.0", + "version": "5.6.8", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.6.8.tgz", + "integrity": "sha512-Wf+CseT/iOJjrGtAOf3ck9zS7AgPmr2fZ3N97r4+YXN3mBePTG2/bJ8DApl9mVwYL+RpYbNxMEkEp4mPGdwG/w==", + "requires": { + "@ethersproject/abstract-provider": "^5.6.1", + "@ethersproject/abstract-signer": "^5.6.2", + "@ethersproject/address": "^5.6.1", + "@ethersproject/base64": "^5.6.1", + "@ethersproject/basex": "^5.6.1", + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/constants": "^5.6.1", + "@ethersproject/hash": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/networks": "^5.6.3", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/random": "^5.6.1", + "@ethersproject/rlp": "^5.6.1", + "@ethersproject/sha2": "^5.6.1", + "@ethersproject/strings": "^5.6.1", + "@ethersproject/transactions": "^5.6.2", + "@ethersproject/web": "^5.6.1", "bech32": "1.1.4", "ws": "7.4.6" }, @@ -1823,145 +1898,145 @@ } }, "@ethersproject/random": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.5.0.tgz", - "integrity": "sha512-egGYZwZ/YIFKMHcoBUo8t3a8Hb/TKYX8BCBoLjudVCZh892welR3jOxgOmb48xznc9bTcMm7Tpwc1gHC1PFNFQ==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.6.1.tgz", + "integrity": "sha512-/wtPNHwbmng+5yi3fkipA8YBT59DdkGRoC2vWk09Dci/q5DlgnMkhIycjHlavrvrjJBkFjO/ueLyT+aUDfc4lA==", "requires": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0" + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0" } }, "@ethersproject/rlp": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.5.0.tgz", - "integrity": "sha512-hLv8XaQ8PTI9g2RHoQGf/WSxBfTB/NudRacbzdxmst5VHAqd1sMibWG7SENzT5Dj3yZ3kJYx+WiRYEcQTAkcYA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.6.1.tgz", + "integrity": "sha512-uYjmcZx+DKlFUk7a5/W9aQVaoEC7+1MOBgNtvNg13+RnuUwT4F0zTovC0tmay5SmRslb29V1B7Y5KCri46WhuQ==", "requires": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0" + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0" } }, "@ethersproject/sha2": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.5.0.tgz", - "integrity": "sha512-B5UBoglbCiHamRVPLA110J+2uqsifpZaTmid2/7W5rbtYVz6gus6/hSDieIU/6gaKIDcOj12WnOdiymEUHIAOA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.6.1.tgz", + "integrity": "sha512-5K2GyqcW7G4Yo3uenHegbXRPDgARpWUiXc6RiF7b6i/HXUoWlb7uCARh7BAHg7/qT/Q5ydofNwiZcim9qpjB6g==", "requires": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0", "hash.js": "1.1.7" } }, "@ethersproject/signing-key": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.5.0.tgz", - "integrity": "sha512-5VmseH7qjtNmDdZBswavhotYbWB0bOwKIlOTSlX14rKn5c11QmJwGt4GHeo7NrL/Ycl7uo9AHvEqs5xZgFBTng==", - "requires": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "bn.js": "^4.11.9", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.6.2.tgz", + "integrity": "sha512-jVbu0RuP7EFpw82vHcL+GP35+KaNruVAZM90GxgQnGqB6crhBqW/ozBfFvdeImtmb4qPko0uxXjn8l9jpn0cwQ==", + "requires": { + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "bn.js": "^5.2.1", "elliptic": "6.5.4", "hash.js": "1.1.7" } }, "@ethersproject/solidity": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.5.0.tgz", - "integrity": "sha512-9NgZs9LhGMj6aCtHXhtmFQ4AN4sth5HuFXVvAQtzmm0jpSCNOTGtrHZJAeYTh7MBjRR8brylWZxBZR9zDStXbw==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.6.1.tgz", + "integrity": "sha512-KWqVLkUUoLBfL1iwdzUVlkNqAUIFMpbbeH0rgCfKmJp0vFtY4AsaN91gHKo9ZZLkC4UOm3cI3BmMV4N53BOq4g==", "requires": { - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/sha2": "^5.5.0", - "@ethersproject/strings": "^5.5.0" + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/keccak256": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/sha2": "^5.6.1", + "@ethersproject/strings": "^5.6.1" } }, "@ethersproject/strings": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.5.0.tgz", - "integrity": "sha512-9fy3TtF5LrX/wTrBaT8FGE6TDJyVjOvXynXJz5MT5azq+E6D92zuKNx7i29sWW2FjVOaWjAsiZ1ZWznuduTIIQ==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.6.1.tgz", + "integrity": "sha512-2X1Lgk6Jyfg26MUnsHiT456U9ijxKUybz8IM1Vih+NJxYtXhmvKBcHOmvGqpFSVJ0nQ4ZCoIViR8XlRw1v/+Cw==", "requires": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/constants": "^5.5.0", - "@ethersproject/logger": "^5.5.0" + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/constants": "^5.6.1", + "@ethersproject/logger": "^5.6.0" } }, "@ethersproject/transactions": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.5.0.tgz", - "integrity": "sha512-9RZYSKX26KfzEd/1eqvv8pLauCKzDTub0Ko4LfIgaERvRuwyaNV78mJs7cpIgZaDl6RJui4o49lHwwCM0526zA==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.6.2.tgz", + "integrity": "sha512-BuV63IRPHmJvthNkkt9G70Ullx6AcM+SDc+a8Aw/8Yew6YwT51TcBKEp1P4oOQ/bP25I18JJr7rcFRgFtU9B2Q==", "requires": { - "@ethersproject/address": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/constants": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/rlp": "^5.5.0", - "@ethersproject/signing-key": "^5.5.0" + "@ethersproject/address": "^5.6.1", + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/constants": "^5.6.1", + "@ethersproject/keccak256": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/rlp": "^5.6.1", + "@ethersproject/signing-key": "^5.6.2" } }, "@ethersproject/units": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.5.0.tgz", - "integrity": "sha512-7+DpjiZk4v6wrikj+TCyWWa9dXLNU73tSTa7n0TSJDxkYbV3Yf1eRh9ToMLlZtuctNYu9RDNNy2USq3AdqSbag==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.6.1.tgz", + "integrity": "sha512-rEfSEvMQ7obcx3KWD5EWWx77gqv54K6BKiZzKxkQJqtpriVsICrktIQmKl8ReNToPeIYPnFHpXvKpi068YFZXw==", "requires": { - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/constants": "^5.5.0", - "@ethersproject/logger": "^5.5.0" + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/constants": "^5.6.1", + "@ethersproject/logger": "^5.6.0" } }, "@ethersproject/wallet": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.5.0.tgz", - "integrity": "sha512-Mlu13hIctSYaZmUOo7r2PhNSd8eaMPVXe1wxrz4w4FCE4tDYBywDH+bAR1Xz2ADyXGwqYMwstzTrtUVIsKDO0Q==", - "requires": { - "@ethersproject/abstract-provider": "^5.5.0", - "@ethersproject/abstract-signer": "^5.5.0", - "@ethersproject/address": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/hash": "^5.5.0", - "@ethersproject/hdnode": "^5.5.0", - "@ethersproject/json-wallets": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/random": "^5.5.0", - "@ethersproject/signing-key": "^5.5.0", - "@ethersproject/transactions": "^5.5.0", - "@ethersproject/wordlists": "^5.5.0" + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.6.2.tgz", + "integrity": "sha512-lrgh0FDQPuOnHcF80Q3gHYsSUODp6aJLAdDmDV0xKCN/T7D99ta1jGVhulg3PY8wiXEngD0DfM0I2XKXlrqJfg==", + "requires": { + "@ethersproject/abstract-provider": "^5.6.1", + "@ethersproject/abstract-signer": "^5.6.2", + "@ethersproject/address": "^5.6.1", + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/hash": "^5.6.1", + "@ethersproject/hdnode": "^5.6.2", + "@ethersproject/json-wallets": "^5.6.1", + "@ethersproject/keccak256": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/random": "^5.6.1", + "@ethersproject/signing-key": "^5.6.2", + "@ethersproject/transactions": "^5.6.2", + "@ethersproject/wordlists": "^5.6.1" } }, "@ethersproject/web": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.5.0.tgz", - "integrity": "sha512-BEgY0eL5oH4mAo37TNYVrFeHsIXLRxggCRG/ksRIxI2X5uj5IsjGmcNiRN/VirQOlBxcUhCgHhaDLG4m6XAVoA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.6.1.tgz", + "integrity": "sha512-/vSyzaQlNXkO1WV+RneYKqCJwualcUdx/Z3gseVovZP0wIlOFcCE1hkRhKBH8ImKbGQbMl9EAAyJFrJu7V0aqA==", "requires": { - "@ethersproject/base64": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/strings": "^5.5.0" + "@ethersproject/base64": "^5.6.1", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/strings": "^5.6.1" } }, "@ethersproject/wordlists": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.5.0.tgz", - "integrity": "sha512-bL0UTReWDiaQJJYOC9sh/XcRu/9i2jMrzf8VLRmPKx58ckSlOJiohODkECCO50dtLZHcGU6MLXQ4OOrgBwP77Q==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.6.1.tgz", + "integrity": "sha512-wiPRgBpNbNwCQFoCr8bcWO8o5I810cqO6mkdtKfLKFlLxeCWcnzDi4Alu8iyNzlhYuS9npCwivMbRWF19dyblw==", "requires": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/hash": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/strings": "^5.5.0" + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/hash": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "@ethersproject/strings": "^5.6.1" } }, "aes-js": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", - "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==" }, "axios": { "version": "0.21.4", @@ -1977,19 +2052,19 @@ "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" }, "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" }, "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" }, "bufferutil": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", - "integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz", + "integrity": "sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==", "requires": { "node-gyp-build": "^4.3.0" } @@ -2031,11 +2106,12 @@ } }, "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", "requires": { - "object-keys": "^1.0.12" + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" } }, "dotenv": { @@ -2055,33 +2131,43 @@ "inherits": "^2.0.4", "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + } } }, "es-abstract": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", "requires": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", "get-intrinsic": "^1.1.1", "get-symbol-description": "^1.0.0", "has": "^1.0.3", - "has-symbols": "^1.0.2", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", + "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", + "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" } }, "es-to-primitive": { @@ -2095,19 +2181,19 @@ } }, "es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" } }, "es6-iterator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", "requires": { "d": "1", "es5-ext": "^0.10.35", @@ -2124,40 +2210,40 @@ } }, "ethers": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.5.1.tgz", - "integrity": "sha512-RodEvUFZI+EmFcE6bwkuJqpCYHazdzeR1nMzg+YWQSmQEsNtfl1KHGfp/FWZYl48bI/g7cgBeP2IlPthjiVngw==", - "requires": { - "@ethersproject/abi": "5.5.0", - "@ethersproject/abstract-provider": "5.5.1", - "@ethersproject/abstract-signer": "5.5.0", - "@ethersproject/address": "5.5.0", - "@ethersproject/base64": "5.5.0", - "@ethersproject/basex": "5.5.0", - "@ethersproject/bignumber": "5.5.0", - "@ethersproject/bytes": "5.5.0", - "@ethersproject/constants": "5.5.0", - "@ethersproject/contracts": "5.5.0", - "@ethersproject/hash": "5.5.0", - "@ethersproject/hdnode": "5.5.0", - "@ethersproject/json-wallets": "5.5.0", - "@ethersproject/keccak256": "5.5.0", - "@ethersproject/logger": "5.5.0", - "@ethersproject/networks": "5.5.0", - "@ethersproject/pbkdf2": "5.5.0", - "@ethersproject/properties": "5.5.0", - "@ethersproject/providers": "5.5.0", - "@ethersproject/random": "5.5.0", - "@ethersproject/rlp": "5.5.0", - "@ethersproject/sha2": "5.5.0", - "@ethersproject/signing-key": "5.5.0", - "@ethersproject/solidity": "5.5.0", - "@ethersproject/strings": "5.5.0", - "@ethersproject/transactions": "5.5.0", - "@ethersproject/units": "5.5.0", - "@ethersproject/wallet": "5.5.0", - "@ethersproject/web": "5.5.0", - "@ethersproject/wordlists": "5.5.0" + "version": "5.6.9", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.6.9.tgz", + "integrity": "sha512-lMGC2zv9HC5EC+8r429WaWu3uWJUCgUCt8xxKCFqkrFuBDZXDYIdzDUECxzjf2BMF8IVBByY1EBoGSL3RTm8RA==", + "requires": { + "@ethersproject/abi": "5.6.4", + "@ethersproject/abstract-provider": "5.6.1", + "@ethersproject/abstract-signer": "5.6.2", + "@ethersproject/address": "5.6.1", + "@ethersproject/base64": "5.6.1", + "@ethersproject/basex": "5.6.1", + "@ethersproject/bignumber": "5.6.2", + "@ethersproject/bytes": "5.6.1", + "@ethersproject/constants": "5.6.1", + "@ethersproject/contracts": "5.6.2", + "@ethersproject/hash": "5.6.1", + "@ethersproject/hdnode": "5.6.2", + "@ethersproject/json-wallets": "5.6.1", + "@ethersproject/keccak256": "5.6.1", + "@ethersproject/logger": "5.6.0", + "@ethersproject/networks": "5.6.4", + "@ethersproject/pbkdf2": "5.6.1", + "@ethersproject/properties": "5.6.0", + "@ethersproject/providers": "5.6.8", + "@ethersproject/random": "5.6.1", + "@ethersproject/rlp": "5.6.1", + "@ethersproject/sha2": "5.6.1", + "@ethersproject/signing-key": "5.6.2", + "@ethersproject/solidity": "5.6.1", + "@ethersproject/strings": "5.6.1", + "@ethersproject/transactions": "5.6.2", + "@ethersproject/units": "5.6.1", + "@ethersproject/wallet": "5.6.2", + "@ethersproject/web": "5.6.1", + "@ethersproject/wordlists": "5.6.1" } }, "ext": { @@ -2169,25 +2255,25 @@ }, "dependencies": { "type": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", - "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==" + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" } } }, "fetch-blob": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz", - "integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", "requires": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" }, "formdata-polyfill": { "version": "4.0.10", @@ -2202,14 +2288,30 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", - "has-symbols": "^1.0.1" + "has-symbols": "^1.0.3" } }, "get-symbol-description": { @@ -2230,14 +2332,22 @@ } }, "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "requires": { + "get-intrinsic": "^1.1.1" + } }, "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "has-tostringtag": { "version": "1.0.0", @@ -2259,7 +2369,7 @@ "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", "requires": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -2312,14 +2422,14 @@ } }, "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" }, "is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "requires": { "has-tostringtag": "^1.0.0" } @@ -2334,9 +2444,12 @@ } }, "is-shared-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", - "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "requires": { + "call-bind": "^1.0.2" + } }, "is-string": { "version": "1.0.7", @@ -2357,14 +2470,14 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "is-weakref": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", - "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", "requires": { - "call-bind": "^1.0.0" + "call-bind": "^1.0.2" } }, "js-sha3": { @@ -2380,17 +2493,17 @@ "minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "node-domexception": { "version": "1.0.0", @@ -2398,9 +2511,9 @@ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" }, "node-fetch": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.3.tgz", - "integrity": "sha512-AXP18u4pidSZ1xYXRDPY/8jdv3RAozIt/WLNR/MBGZAz+xjtlr90RvCnsvHQRiXyWliZF/CpytExp32UU67/SA==", + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", "requires": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -2408,14 +2521,14 @@ } }, "node-gyp-build": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==" + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", + "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==" }, "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==" + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" }, "object-keys": { "version": "1.1.1", @@ -2423,13 +2536,13 @@ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.3.tgz", + "integrity": "sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==", "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", "object-keys": "^1.1.1" } }, @@ -2448,6 +2561,16 @@ "es-abstract": "^1.19.1" } }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, "scrypt-js": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", @@ -2464,21 +2587,23 @@ } }, "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" } }, "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" } }, "type": { @@ -2495,28 +2620,28 @@ } }, "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" } }, "utf-8-validate": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz", - "integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", + "integrity": "sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==", "requires": { "node-gyp-build": "^4.3.0" } }, "web-streams-polyfill": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", - "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==" }, "websocket": { "version": "1.0.34", @@ -2554,20 +2679,20 @@ } }, "ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", "requires": {} }, "yaeti": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==" }, "zksync": { - "version": "0.11.6", - "resolved": "https://registry.npmjs.org/zksync/-/zksync-0.11.6.tgz", - "integrity": "sha512-p54+KjmKhd5MUe0rWZDcqM3k9UT2BqpYMp2fOpl5m+Hp9GLn+PMAiKYaWtzgjnYvZ7ceOdKq8CEHKKEbpLyZ0A==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/zksync/-/zksync-0.13.1.tgz", + "integrity": "sha512-1WRMommc8qTuwh3qk6/dv34Eb+PnKBmM41sQMaAjkC47aw2Xm4RuqJfvHXPoKnZjtIhBAzXA25rwevxqGukQHw==", "requires": { "axios": "^0.21.2", "websocket": "^1.0.30", diff --git a/package.json b/package.json index 19da3cb..30d02df 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "ethers": "^5.5.1", "node-fetch": "^3.1.0", "ws": "^8.2.3", - "zksync": "0.11.6" + "zksync": "^0.13.1" }, "type": "module", "devDependencies": {}, From c30088b3ab62374084685c956d913cfe6750a5d7 Mon Sep 17 00:00:00 2001 From: Trooper Date: Mon, 8 Aug 2022 22:44:18 +0200 Subject: [PATCH 150/160] update sign order --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index aaaf375..21ea112 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -400,7 +400,7 @@ async function sendFillRequest(orderreceipt, accountId) { ratio: zksync.utils.tokenRatio(tokenRatio), validUntil: oneMinExpiry } - const fillOrder = await WALLETS[accountId].syncWallet.getOrder(orderDetails); + const fillOrder = await WALLETS[accountId].syncWallet.signOrder(orderDetails); // Set wallet flag WALLETS[accountId]['ORDER_BROADCASTING'] = true; From 7d71d0c2a852244cd994e41cde9e438b017dca54 Mon Sep 17 00:00:00 2001 From: Trooper Date: Tue, 9 Aug 2022 00:43:29 +0200 Subject: [PATCH 151/160] add goerli zksync --- marketmaker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketmaker.js b/marketmaker.js index 21ea112..428564a 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -45,7 +45,7 @@ console.log("ACTIVE PAIRS", activePairs); // Connect to zksync const CHAIN_ID = parseInt(MM_CONFIG.zigzagChainId); -const ETH_NETWORK = (CHAIN_ID === 1) ? "mainnet" : "rinkeby"; +const ETH_NETWORK = (CHAIN_ID === 1) ? "mainnet" : "goerli"; let ethersProvider; const providerUrl = (process.env.INFURA_URL || MM_CONFIG.infuraUrl); if(providerUrl && ETH_NETWORK=="mainnet") { From 1053c72900c66ff359e8e4ec2bc6f361e2cabb25 Mon Sep 17 00:00:00 2001 From: Trooper Date: Tue, 9 Aug 2022 23:22:06 +0200 Subject: [PATCH 152/160] fix naming goerli --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dc27c90..8f07c4a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Zigzag Market Maker -This is the reference market maker for Zigzag zksync markets. It works on both Rinkeby and Mainnet. +This is the reference market maker for Zigzag zksync markets. It works on both Goerli and Mainnet. This market maker uses existing price feeds to set bids and asks for a market. For now, in order to provide liquidity for a market, there must be an existing market with **greater** liquidity listed on Cryptowatch, via either Uniswap or some other centralized exchange. It is crucial that the oracle market have more liquidity than the Zigzag one so that you are not prone to oracle attacks. @@ -77,9 +77,9 @@ With the defualt setting the bot will pay the zkSync fee wiht the same token as - "zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com" - "zigzagChainId": 1 -#### Rinkeby zkSync +#### Goerli zkSync - "zigzagWsUrl": "wss://secret-thicket-93345.herokuapp.com" -- "zigzagChainId": 1000 +- "zigzagChainId": 1002 You can add, remove, and configure pair settings in the `pairs` section. A pair setting looks like this: From 13815434915b92563efc8a608f662003e0ac7da6 Mon Sep 17 00:00:00 2001 From: Trooper Date: Fri, 12 Aug 2022 12:01:28 +0200 Subject: [PATCH 153/160] enable uniswap mainnet feed for goerli --- marketmaker.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 428564a..7fcde82 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -46,12 +46,15 @@ console.log("ACTIVE PAIRS", activePairs); // Connect to zksync const CHAIN_ID = parseInt(MM_CONFIG.zigzagChainId); const ETH_NETWORK = (CHAIN_ID === 1) ? "mainnet" : "goerli"; -let ethersProvider; -const providerUrl = (process.env.INFURA_URL || MM_CONFIG.infuraUrl); -if(providerUrl && ETH_NETWORK=="mainnet") { - ethersProvider = ethers.getDefaultProvider(providerUrl); +const infureKey = (process.env.infura || MM_CONFIG.infura); +let ethersProvider = null; +if (infureKey) { + ethersProvider = new ethers.providers.InfuraProvider( + "mainnet", + infureKey + ); } else { - ethersProvider = ethers.getDefaultProvider(ETH_NETWORK); + ethersProvider = new ethers.getDefaultProvider("mainnet"); } // Start price feeds From 6a0054003bfcdf707f2f714afdccd28b00fe15fa Mon Sep 17 00:00:00 2001 From: Trooper Date: Fri, 12 Aug 2022 12:03:38 +0200 Subject: [PATCH 154/160] add INFURA to read me --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8f07c4a..687b702 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ node marketmaker.js ## Configuration Via Environment Variables -It is __recommended__ to use environment variables to set your private keys. You can set `ETH_PRIVKEY`, `CRYPTOWATCH_API_KEY` and `INFURA_URL` using them. You can set them using `ETH_PRIVKEY=0x____`. For more informations on private keys read [this](https://linuxize.com/post/how-to-set-and-list-environment-variables-in-linux/). +It is __recommended__ to use environment variables to set your private keys. You can set `ETH_PRIVKEY`, `CRYPTOWATCH_API_KEY` and `INFURA` using them. You can set them using `ETH_PRIVKEY=0x____`. For more informations on private keys read [this](https://linuxize.com/post/how-to-set-and-list-environment-variables-in-linux/). If your hosting service requires you to pass in configs via environment variables you can compress `config.json`: From 3d118192ce8027b3c93d3531a936143af4d8838e Mon Sep 17 00:00:00 2001 From: Trooper Date: Fri, 26 Aug 2022 13:32:19 +0200 Subject: [PATCH 155/160] remove FEE_TOKEN if no longer available for fees --- marketmaker.js | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 7fcde82..955ec5b 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -199,18 +199,26 @@ async function handleMessage(json) { const newQuoteFee = MARKETS[marketId].quoteFee; console.log(`marketinfo ${marketId} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); if (FEE_TOKEN) break - if( - marketInfo.baseAsset.enabledForFees && - !FEE_TOKEN_LIST.includes(marketInfo.baseAsset.id) - ) { - FEE_TOKEN_LIST.push(marketInfo.baseAsset.id); - } - if( - marketInfo.quoteAsset.enabledForFees && - !FEE_TOKEN_LIST.includes(marketInfo.quoteAsset.id) - ) { - FEE_TOKEN_LIST.push(marketInfo.quoteAsset.id); - } + if(marketInfo.baseAsset.enabledForFees) { + if (!FEE_TOKEN_LIST.includes(marketInfo.baseAsset.id)) { + FEE_TOKEN_LIST.push(marketInfo.baseAsset.id); + } + } else { + if (FEE_TOKEN_LIST.includes(marketInfo.baseAsset.id)) { + const index = FEE_TOKEN_LIST.indexOf(marketInfo.baseAsset.id); + FEE_TOKEN_LIST.splice(index, 1); + } + } + if(marketInfo.quoteAsset.enabledForFees) { + if (!FEE_TOKEN_LIST.includes(marketInfo.quoteAsset.id)) { + FEE_TOKEN_LIST.push(marketInfo.quoteAsset.id); + } + } else { + if (FEE_TOKEN_LIST.includes(marketInfo.quoteAsset.id)) { + const index = FEE_TOKEN_LIST.indexOf(marketInfo.quoteAsset.id); + FEE_TOKEN_LIST.splice(index, 1); + } + } break default: break From 551b0debec58113f5a495648d22e3212e6f6ec76 Mon Sep 17 00:00:00 2001 From: Trooper Date: Fri, 26 Aug 2022 13:33:53 +0200 Subject: [PATCH 156/160] fix logic --- marketmaker.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 955ec5b..6655951 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -204,8 +204,8 @@ async function handleMessage(json) { FEE_TOKEN_LIST.push(marketInfo.baseAsset.id); } } else { - if (FEE_TOKEN_LIST.includes(marketInfo.baseAsset.id)) { - const index = FEE_TOKEN_LIST.indexOf(marketInfo.baseAsset.id); + const index = FEE_TOKEN_LIST.indexOf(marketInfo.baseAsset.id); + if (index >= 0) { FEE_TOKEN_LIST.splice(index, 1); } } @@ -214,8 +214,8 @@ async function handleMessage(json) { FEE_TOKEN_LIST.push(marketInfo.quoteAsset.id); } } else { - if (FEE_TOKEN_LIST.includes(marketInfo.quoteAsset.id)) { - const index = FEE_TOKEN_LIST.indexOf(marketInfo.quoteAsset.id); + const index = FEE_TOKEN_LIST.indexOf(marketInfo.quoteAsset.id); + if (index >= 0) { FEE_TOKEN_LIST.splice(index, 1); } } From cd9052d01d49e85a60b167b371da84207d8140b6 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 19 Mar 2023 14:04:38 +0100 Subject: [PATCH 157/160] format and add sendMessage make sure ws is ready --- marketmaker.js | 1479 ++++++++++++++++++++++++------------------------ 1 file changed, 731 insertions(+), 748 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 6655951..bab43f2 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -1,9 +1,9 @@ -import WebSocket from 'ws'; +import WebSocket from "ws"; import * as zksync from "zksync"; -import ethers from 'ethers'; -import dotenv from 'dotenv'; -import fetch from 'node-fetch'; -import fs from 'fs'; +import ethers from "ethers"; +import dotenv from "dotenv"; +import fetch from "node-fetch"; +import fs from "fs"; dotenv.config(); @@ -25,34 +25,30 @@ let chainlink_error_counter = 0; // Load MM config let MM_CONFIG; if (process.env.MM_CONFIG) { - MM_CONFIG = JSON.parse(process.env.MM_CONFIG); -} -else { - const mmConfigFile = fs.readFileSync("config.json", "utf8"); - MM_CONFIG = JSON.parse(mmConfigFile); + MM_CONFIG = JSON.parse(process.env.MM_CONFIG); +} else { + const mmConfigFile = fs.readFileSync("config.json", "utf8"); + MM_CONFIG = JSON.parse(mmConfigFile); } if (MM_CONFIG.feeToken) { FEE_TOKEN = MM_CONFIG.feeToken; } let activePairs = []; for (let marketId in MM_CONFIG.pairs) { - const pair = MM_CONFIG.pairs[marketId]; - if (pair.active) { - activePairs.push(marketId); - } + const pair = MM_CONFIG.pairs[marketId]; + if (pair.active) { + activePairs.push(marketId); + } } console.log("ACTIVE PAIRS", activePairs); // Connect to zksync const CHAIN_ID = parseInt(MM_CONFIG.zigzagChainId); -const ETH_NETWORK = (CHAIN_ID === 1) ? "mainnet" : "goerli"; -const infureKey = (process.env.infura || MM_CONFIG.infura); +const ETH_NETWORK = CHAIN_ID === 1 ? "mainnet" : "goerli"; +const infureKey = process.env.infura || MM_CONFIG.infura; let ethersProvider = null; if (infureKey) { - ethersProvider = new ethers.providers.InfuraProvider( - "mainnet", - infureKey - ); + ethersProvider = new ethers.providers.InfuraProvider("mainnet", infureKey); } else { ethersProvider = new ethers.getDefaultProvider("mainnet"); } @@ -62,47 +58,48 @@ await setupPriceFeeds(); let syncProvider; try { - syncProvider = await zksync.getDefaultProvider(ETH_NETWORK); - const keys = []; - const ethPrivKey = (process.env.ETH_PRIVKEY || MM_CONFIG.ethPrivKey); - if(ethPrivKey && ethPrivKey != "") { keys.push(ethPrivKey); } - let ethPrivKeys; - if (process.env.ETH_PRIVKEYS) { - ethPrivKeys = JSON.parse(process.env.ETH_PRIVKEYS); - } - else { - ethPrivKeys = MM_CONFIG.ethPrivKeys; - } - if(ethPrivKeys && ethPrivKeys.length > 0) { - ethPrivKeys.forEach( key => { - if(key != "" && !keys.includes(key)) { - keys.push(key); - } - }); - } - for(let i=0; i 0) { + ethPrivKeys.forEach((key) => { + if (key != "" && !keys.includes(key)) { + keys.push(key); + } + }); + } + for (let i = 0; i < keys.length; i++) { + let ethWallet = new ethers.Wallet(keys[i]); + let syncWallet = await zksync.Wallet.fromEthSigner(ethWallet, syncProvider); + if (!(await syncWallet.isSigningKeySet())) { + console.log("setting sign key"); + const signKeyResult = await syncWallet.setSigningKey({ + feeToken: "ETH", + ethAuthType: "ECDSA", + }); + console.log(signKeyResult); + } + let accountId = await syncWallet.getAccountId(); + let account_state = await syncWallet.getAccountState(); + WALLETS[accountId] = { + ethWallet: ethWallet, + syncWallet: syncWallet, + account_state: account_state, + ORDER_BROADCASTING: false, + }; + } } catch (e) { - console.log(e); - throw new Error("Could not connect to zksync API"); + console.log(e); + throw new Error("Could not connect to zksync API"); } // Update account state loop @@ -110,219 +107,215 @@ setInterval(updateAccountState, 900000); let fillOrdersInterval, indicateLiquidityInterval; let zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); -zigzagws.on('open', onWsOpen); -zigzagws.on('close', onWsClose); -zigzagws.on('error', console.error); +zigzagws.on("open", onWsOpen); +zigzagws.on("close", onWsClose); +zigzagws.on("error", console.error); function onWsOpen() { - zigzagws.on('message', handleMessage); - fillOrdersInterval = setInterval(fillOpenOrders, 200); - indicateLiquidityInterval = setInterval(indicateLiquidity, 5000); - for (let market in MM_CONFIG.pairs) { - if (MM_CONFIG.pairs[market].active) { - const msg = {op:"subscribemarket", args:[CHAIN_ID, market]}; - zigzagws.send(JSON.stringify(msg)); - } + zigzagws.on("message", handleMessage); + fillOrdersInterval = setInterval(fillOpenOrders, 200); + indicateLiquidityInterval = setInterval(indicateLiquidity, 5000); + for (let market in MM_CONFIG.pairs) { + if (MM_CONFIG.pairs[market].active) { + const msg = { op: "subscribemarket", args: [CHAIN_ID, market] }; + sendMessage(JSON.stringify(msg)); } + } } -function onWsClose () { - console.log("Websocket closed. Restarting"); - setTimeout(() => { - clearInterval(fillOrdersInterval) - clearInterval(indicateLiquidityInterval) - zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); - zigzagws.on('open', onWsOpen); - zigzagws.on('close', onWsClose); - zigzagws.on('error', console.error); - }, 5000); +function onWsClose() { + console.log("Websocket closed. Restarting"); + setTimeout(() => { + clearInterval(fillOrdersInterval); + clearInterval(indicateLiquidityInterval); + zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); + zigzagws.on("open", onWsOpen); + zigzagws.on("close", onWsClose); + zigzagws.on("error", console.error); + }, 5000); } async function handleMessage(json) { - const msg = JSON.parse(json); - if (!(["lastprice", "liquidity2", "fillstatus", "marketinfo"]).includes(msg.op)) console.log(json.toString()); - switch(msg.op) { - case 'error': - const accountId = msg.args?.[1]; - if(msg.args[0] == 'fillrequest' && accountId) { - WALLETS[accountId]['ORDER_BROADCASTING'] = false; - } - break; - case 'orders': - const orders = msg.args[0]; - orders.forEach(order => { - const orderId = order[1]; - const fillable = isOrderFillable(order); - console.log(fillable); - if (fillable.fillable) { - sendFillRequest(order, fillable.walletId); - } else if ([ - "sending order already", - "badprice" - ].includes(fillable.reason)) { - OPEN_ORDERS[orderId] = order; - } - }); - break - case "userordermatch": - const chainId = msg.args[0]; - const orderId = msg.args[1]; - const fillOrder = msg.args[3]; - const wallet = WALLETS[fillOrder.accountId]; - if(!wallet) { - console.error("No wallet with this accountId: "+fillOrder.accountId); - break - } else { - try { - await broadcastFill(chainId, orderId, msg.args[2], fillOrder, wallet); - } catch (e) { - const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'r',null,e.message]]]} - zigzagws.send(JSON.stringify(orderCommitMsg)); - console.error(e); - } - wallet['ORDER_BROADCASTING'] = false; - } - break - case "marketinfo": - const marketInfo = msg.args[0]; - const marketId = marketInfo.alias; - if(!marketId) break - let oldBaseFee = "N/A", oldQuoteFee = "N/A"; - try { - oldBaseFee = MARKETS[marketId].baseFee; - oldQuoteFee = MARKETS[marketId].quoteFee; - } catch (e) { - // pass, no old marketInfo - } - MARKETS[marketId] = marketInfo; - const newBaseFee = MARKETS[marketId].baseFee; - const newQuoteFee = MARKETS[marketId].quoteFee; - console.log(`marketinfo ${marketId} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); - if (FEE_TOKEN) break - if(marketInfo.baseAsset.enabledForFees) { - if (!FEE_TOKEN_LIST.includes(marketInfo.baseAsset.id)) { - FEE_TOKEN_LIST.push(marketInfo.baseAsset.id); - } - } else { - const index = FEE_TOKEN_LIST.indexOf(marketInfo.baseAsset.id); - if (index >= 0) { - FEE_TOKEN_LIST.splice(index, 1); - } - } - if(marketInfo.quoteAsset.enabledForFees) { - if (!FEE_TOKEN_LIST.includes(marketInfo.quoteAsset.id)) { - FEE_TOKEN_LIST.push(marketInfo.quoteAsset.id); - } - } else { - const index = FEE_TOKEN_LIST.indexOf(marketInfo.quoteAsset.id); - if (index >= 0) { - FEE_TOKEN_LIST.splice(index, 1); - } - } - break - default: - break - } + const msg = JSON.parse(json); + if (!["lastprice", "liquidity2", "fillstatus", "marketinfo"].includes(msg.op)) console.log(json.toString()); + switch (msg.op) { + case "error": + const accountId = msg.args?.[1]; + if (msg.args[0] == "fillrequest" && accountId) { + WALLETS[accountId]["ORDER_BROADCASTING"] = false; + } + break; + case "orders": + const orders = msg.args[0]; + orders.forEach((order) => { + const orderId = order[1]; + const fillable = isOrderFillable(order); + console.log(fillable); + if (fillable.fillable) { + sendFillRequest(order, fillable.walletId); + } else if (["sending order already", "badprice"].includes(fillable.reason)) { + OPEN_ORDERS[orderId] = order; + } + }); + break; + case "userordermatch": + const chainId = msg.args[0]; + const orderId = msg.args[1]; + const fillOrder = msg.args[3]; + const wallet = WALLETS[fillOrder.accountId]; + if (!wallet) { + console.error("No wallet with this accountId: " + fillOrder.accountId); + break; + } else { + try { + await broadcastFill(chainId, orderId, msg.args[2], fillOrder, wallet); + } catch (e) { + const orderCommitMsg = { + op: "orderstatusupdate", + args: [[[chainId, orderId, "r", null, e.message]]], + }; + sendMessage(JSON.stringify(orderCommitMsg)); + console.error(e); + } + wallet["ORDER_BROADCASTING"] = false; + } + break; + case "marketinfo": + const marketInfo = msg.args[0]; + const marketId = marketInfo.alias; + if (!marketId) break; + let oldBaseFee = "N/A", + oldQuoteFee = "N/A"; + try { + oldBaseFee = MARKETS[marketId].baseFee; + oldQuoteFee = MARKETS[marketId].quoteFee; + } catch (e) { + // pass, no old marketInfo + } + MARKETS[marketId] = marketInfo; + const newBaseFee = MARKETS[marketId].baseFee; + const newQuoteFee = MARKETS[marketId].quoteFee; + console.log(`marketinfo ${marketId} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); + if (FEE_TOKEN) break; + if (marketInfo.baseAsset.enabledForFees) { + if (!FEE_TOKEN_LIST.includes(marketInfo.baseAsset.id)) { + FEE_TOKEN_LIST.push(marketInfo.baseAsset.id); + } + } else { + const index = FEE_TOKEN_LIST.indexOf(marketInfo.baseAsset.id); + if (index >= 0) { + FEE_TOKEN_LIST.splice(index, 1); + } + } + if (marketInfo.quoteAsset.enabledForFees) { + if (!FEE_TOKEN_LIST.includes(marketInfo.quoteAsset.id)) { + FEE_TOKEN_LIST.push(marketInfo.quoteAsset.id); + } + } else { + const index = FEE_TOKEN_LIST.indexOf(marketInfo.quoteAsset.id); + if (index >= 0) { + FEE_TOKEN_LIST.splice(index, 1); + } + } + break; + default: + break; + } } function isOrderFillable(order) { - const chainId = order[0]; - const marketId = order[2]; - const market = MARKETS[marketId]; - const mmConfig = MM_CONFIG.pairs[marketId]; - const mmSide = (mmConfig.side) ? mmConfig.side : 'd'; - if (chainId != CHAIN_ID) return { fillable: false, reason: "badchain" } - if (!market) return { fillable: false, reason: "badmarket" } - if (!mmConfig.active) return { fillable: false, reason: "inactivemarket" } - - const baseQuantity = order[5]; - const quoteQuantity = order[6]; - const expires = order[7]; - const side = order[3]; - const price = order[4]; - - const now = Date.now() / 1000 | 0; - - if (now > expires) { - return { fillable: false, reason: "expired" }; - } + const chainId = order[0]; + const marketId = order[2]; + const market = MARKETS[marketId]; + const mmConfig = MM_CONFIG.pairs[marketId]; + const mmSide = mmConfig.side ? mmConfig.side : "d"; + if (chainId != CHAIN_ID) return { fillable: false, reason: "badchain" }; + if (!market) return { fillable: false, reason: "badmarket" }; + if (!mmConfig.active) return { fillable: false, reason: "inactivemarket" }; - if (mmSide !== 'd' && mmSide == side) { - return { fillable: false, reason: "badside" }; - } + const baseQuantity = order[5]; + const quoteQuantity = order[6]; + const expires = order[7]; + const side = order[3]; + const price = order[4]; - if (baseQuantity < mmConfig.minSize) { - return { fillable: false, reason: "badsize" }; - } - else if (baseQuantity > mmConfig.maxSize) { - return { fillable: false, reason: "badsize" }; - } + const now = (Date.now() / 1000) | 0; - let quote; - try { - quote = genQuote(chainId, marketId, side, baseQuantity); - } catch (e) { - return { fillable: false, reason: e.message } - } - if (side == 's' && price > quote.quotePrice) { - return { fillable: false, reason: "badprice" }; - } - else if (side == 'b' && price < quote.quotePrice) { - return { fillable: false, reason: "badprice" }; - } + if (now > expires) { + return { fillable: false, reason: "expired" }; + } - const sellCurrency = (side === 's') ? market.quoteAsset.symbol : market.baseAsset.symbol; - const sellDecimals = (side === 's') ? market.quoteAsset.decimals : market.baseAsset.decimals; - const sellQuantity = (side === 's') ? quote.quoteQuantity : baseQuantity; - const neededBalanceBN = sellQuantity * 10**sellDecimals; - let goodWalletIds = []; - Object.keys(WALLETS).forEach(accountId => { - const walletBalance = WALLETS[accountId]['account_state'].committed.balances[sellCurrency]; - if (Number(walletBalance) > (neededBalanceBN * 1.05)) { - goodWalletIds.push(accountId); - } - }); + if (mmSide !== "d" && mmSide == side) { + return { fillable: false, reason: "badside" }; + } - if (goodWalletIds.length === 0) { - return { fillable: false, reason: "badbalance" }; - } + if (baseQuantity < mmConfig.minSize) { + return { fillable: false, reason: "badsize" }; + } else if (baseQuantity > mmConfig.maxSize) { + return { fillable: false, reason: "badsize" }; + } - goodWalletIds = goodWalletIds.filter(accountId => { - return !WALLETS[accountId]['ORDER_BROADCASTING']; - }); + let quote; + try { + quote = genQuote(chainId, marketId, side, baseQuantity); + } catch (e) { + return { fillable: false, reason: e.message }; + } + if (side == "s" && price > quote.quotePrice) { + return { fillable: false, reason: "badprice" }; + } else if (side == "b" && price < quote.quotePrice) { + return { fillable: false, reason: "badprice" }; + } - if (goodWalletIds.length === 0) { - return { fillable: false, reason: "sending order already" }; + const sellCurrency = side === "s" ? market.quoteAsset.symbol : market.baseAsset.symbol; + const sellDecimals = side === "s" ? market.quoteAsset.decimals : market.baseAsset.decimals; + const sellQuantity = side === "s" ? quote.quoteQuantity : baseQuantity; + const neededBalanceBN = sellQuantity * 10 ** sellDecimals; + let goodWalletIds = []; + Object.keys(WALLETS).forEach((accountId) => { + const walletBalance = WALLETS[accountId]["account_state"].committed.balances[sellCurrency]; + if (Number(walletBalance) > neededBalanceBN * 1.05) { + goodWalletIds.push(accountId); } + }); + + if (goodWalletIds.length === 0) { + return { fillable: false, reason: "badbalance" }; + } + + goodWalletIds = goodWalletIds.filter((accountId) => { + return !WALLETS[accountId]["ORDER_BROADCASTING"]; + }); + + if (goodWalletIds.length === 0) { + return { fillable: false, reason: "sending order already" }; + } - return { fillable: true, reason: null, walletId: goodWalletIds[0]}; + return { fillable: true, reason: null, walletId: goodWalletIds[0] }; } function genQuote(chainId, marketId, side, baseQuantity) { const market = MARKETS[marketId]; if (CHAIN_ID !== chainId) throw new Error("badchain"); if (!market) throw new Error("badmarket"); - if (!(['b','s']).includes(side)) throw new Error("badside"); + if (!["b", "s"].includes(side)) throw new Error("badside"); if (baseQuantity <= 0) throw new Error("badquantity"); validatePriceFeed(marketId); const mmConfig = MM_CONFIG.pairs[marketId]; - const mmSide = mmConfig.side || 'd'; - if (mmSide !== 'd' && mmSide === side) { - throw new Error("badside"); + const mmSide = mmConfig.side || "d"; + if (mmSide !== "d" && mmSide === side) { + throw new Error("badside"); } - const primaryPrice = (mmConfig.invert) - ? (1 / PRICE_FEEDS[mmConfig.priceFeedPrimary]) - : PRICE_FEEDS[mmConfig.priceFeedPrimary]; + const primaryPrice = mmConfig.invert ? 1 / PRICE_FEEDS[mmConfig.priceFeedPrimary] : PRICE_FEEDS[mmConfig.priceFeedPrimary]; if (!primaryPrice) throw new Error("badprice"); - const SPREAD = mmConfig.minSpread + (baseQuantity * mmConfig.slippageRate); + const SPREAD = mmConfig.minSpread + baseQuantity * mmConfig.slippageRate; let quoteQuantity; - if (side === 'b') { - quoteQuantity = (baseQuantity * primaryPrice * (1 + SPREAD)) + market.quoteFee; - } - else if (side === 's') { - quoteQuantity = (baseQuantity - market.baseFee) * primaryPrice * (1 - SPREAD); + if (side === "b") { + quoteQuantity = baseQuantity * primaryPrice * (1 + SPREAD) + market.quoteFee; + } else if (side === "s") { + quoteQuantity = (baseQuantity - market.baseFee) * primaryPrice * (1 - SPREAD); } const quotePrice = Number((quoteQuantity / baseQuantity).toPrecision(6)); if (quotePrice < 0) throw new Error("Amount is inadequate to pay fee"); @@ -331,37 +324,36 @@ function genQuote(chainId, marketId, side, baseQuantity) { } function validatePriceFeed(marketId) { - const mmConfig = MM_CONFIG.pairs[marketId]; - const primaryPriceFeedId = mmConfig.priceFeedPrimary; - const secondaryPriceFeedId = mmConfig.priceFeedSecondary; - - // Constant mode checks - const [mode, price] = primaryPriceFeedId.split(':'); - if (mode === "constant") { - if (price > 0) return true; - else throw new Error("No initPrice available"); - } - - // Check if primary price exists - const primaryPrice = PRICE_FEEDS[primaryPriceFeedId]; - if (!primaryPrice) throw new Error("Primary price feed unavailable"); + const mmConfig = MM_CONFIG.pairs[marketId]; + const primaryPriceFeedId = mmConfig.priceFeedPrimary; + const secondaryPriceFeedId = mmConfig.priceFeedSecondary; + + // Constant mode checks + const [mode, price] = primaryPriceFeedId.split(":"); + if (mode === "constant") { + if (price > 0) return true; + else throw new Error("No initPrice available"); + } + // Check if primary price exists + const primaryPrice = PRICE_FEEDS[primaryPriceFeedId]; + if (!primaryPrice) throw new Error("Primary price feed unavailable"); - // If there is no secondary price feed, the price auto-validates - if (!secondaryPriceFeedId) return true; + // If there is no secondary price feed, the price auto-validates + if (!secondaryPriceFeedId) return true; - // Check if secondary price exists - const secondaryPrice = PRICE_FEEDS[secondaryPriceFeedId]; - if (!secondaryPrice) throw new Error("Secondary price feed unavailable"); + // Check if secondary price exists + const secondaryPrice = PRICE_FEEDS[secondaryPriceFeedId]; + if (!secondaryPrice) throw new Error("Secondary price feed unavailable"); - // If the secondary price feed varies from the primary price feed by more than 1%, assume something is broken - const percentDiff = Math.abs(primaryPrice - secondaryPrice) / primaryPrice; - if (percentDiff > 0.03) { - console.error("Primary and secondary price feeds do not match!"); - throw new Error("Circuit breaker triggered"); - } + // If the secondary price feed varies from the primary price feed by more than 1%, assume something is broken + const percentDiff = Math.abs(primaryPrice - secondaryPrice) / primaryPrice; + if (percentDiff > 0.03) { + console.error("Primary and secondary price feeds do not match!"); + throw new Error("Circuit breaker triggered"); + } - return true; + return true; } async function sendFillRequest(orderreceipt, accountId) { @@ -377,564 +369,555 @@ async function sendFillRequest(orderreceipt, accountId) { const quote = genQuote(chainId, marketId, side, baseQuantity); let tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol; if (side === "b") { - tokenSell = market.baseAssetId; - tokenBuy = market.quoteAssetId; - - sellSymbol = market.baseAsset.symbol; - buySymbol = market.quoteAsset.symbol; - // Add 1 bip to to protect against rounding errors - sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals); - buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); + tokenSell = market.baseAssetId; + tokenBuy = market.quoteAssetId; + + sellSymbol = market.baseAsset.symbol; + buySymbol = market.quoteAsset.symbol; + // Add 1 bip to to protect against rounding errors + sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals); + buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); } else if (side === "s") { - tokenSell = market.quoteAssetId; - tokenBuy = market.baseAssetId; - - sellSymbol = market.quoteAsset.symbol; - buySymbol = market.baseAsset.symbol; - // Add 1 bip to to protect against rounding errors - sellQuantity = (quote.quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals); - buyQuantity = (baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); - } - const sellQuantityParsed = syncProvider.tokenSet.parseToken( - tokenSell, - sellQuantity - ); + tokenSell = market.quoteAssetId; + tokenBuy = market.baseAssetId; + + sellSymbol = market.quoteAsset.symbol; + buySymbol = market.baseAsset.symbol; + // Add 1 bip to to protect against rounding errors + sellQuantity = (quote.quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals); + buyQuantity = (baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); + } + const sellQuantityParsed = syncProvider.tokenSet.parseToken(tokenSell, sellQuantity); const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount(sellQuantityParsed); const tokenRatio = {}; tokenRatio[tokenBuy] = buyQuantity; tokenRatio[tokenSell] = sellQuantity; - const oneMinExpiry = (Date.now() / 1000 | 0) + 60; + const oneMinExpiry = ((Date.now() / 1000) | 0) + 60; const orderDetails = { - tokenSell, - tokenBuy, - amount: sellQuantityPacked, - ratio: zksync.utils.tokenRatio(tokenRatio), - validUntil: oneMinExpiry - } + tokenSell, + tokenBuy, + amount: sellQuantityPacked, + ratio: zksync.utils.tokenRatio(tokenRatio), + validUntil: oneMinExpiry, + }; const fillOrder = await WALLETS[accountId].syncWallet.signOrder(orderDetails); // Set wallet flag - WALLETS[accountId]['ORDER_BROADCASTING'] = true; + WALLETS[accountId]["ORDER_BROADCASTING"] = true; // ORDER_BROADCASTING should not take longer as 5 sec - setTimeout(function() { - WALLETS[accountId]['ORDER_BROADCASTING'] = false; + setTimeout(function () { + WALLETS[accountId]["ORDER_BROADCASTING"] = false; }, 5000); const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] }; - zigzagws.send(JSON.stringify(resp)); - rememberOrder(chainId, - marketId, - orderId, - quote.quotePrice, - sellSymbol, - sellQuantity, - buySymbol, - buyQuantity - ); + sendMessage(JSON.stringify(resp)); + rememberOrder(chainId, marketId, orderId, quote.quotePrice, sellSymbol, sellQuantity, buySymbol, buyQuantity); } async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { - // Nonce check - const nonce = swapOffer.nonce; - const userNonce = NONCES[swapOffer.accountId]; - if (nonce <= userNonce) { - const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'r',null,"Order failed userNonce check."]]]} - zigzagws.send(JSON.stringify(orderCommitMsg)); - return; - } - // select token to match user's fee token - let feeToken; - if (FEE_TOKEN) { - feeToken = FEE_TOKEN - } else { - feeToken = (FEE_TOKEN_LIST.includes(swapOffer.tokenSell)) - ? swapOffer.tokenSell - : 'ETH' - } - - const randInt = (Math.random()*1000).toFixed(0); - console.time('syncswap' + randInt); - const swap = await wallet['syncWallet'].syncSwap({ - orders: [swapOffer, fillOrder], - feeToken: feeToken, - nonce: fillOrder.nonce - }); - const txHash = swap.txHash.split(":")[1]; - const txHashMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'b',txHash]]]} - zigzagws.send(JSON.stringify(txHashMsg)); - console.timeEnd('syncswap' + randInt); + // Nonce check + const nonce = swapOffer.nonce; + const userNonce = NONCES[swapOffer.accountId]; + if (nonce <= userNonce) { + const orderCommitMsg = { + op: "orderstatusupdate", + args: [[[chainId, orderId, "r", null, "Order failed userNonce check."]]], + }; + sendMessage(JSON.stringify(orderCommitMsg)); + return; + } + // select token to match user's fee token + let feeToken; + if (FEE_TOKEN) { + feeToken = FEE_TOKEN; + } else { + feeToken = FEE_TOKEN_LIST.includes(swapOffer.tokenSell) ? swapOffer.tokenSell : "ETH"; + } - console.time('receipt' + randInt); - let receipt, success = false; - try { - receipt = await swap.awaitReceipt(); - if (receipt.success) { - success = true; - NONCES[swapOffer.accountId] = swapOffer.nonce; - } - } catch (e) { - receipt = null; - success = false; - } - console.timeEnd('receipt' + randInt); - console.log("Swap broadcast result", {swap, receipt}); - - let newStatus, error; - if(success) { - afterFill(chainId, orderId, wallet); - newStatus = 'f'; - error = null; - } else { - newStatus = 'r'; - error = swap.error.toString(); - } - - const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,newStatus,txHash,error]]]} - zigzagws.send(JSON.stringify(orderCommitMsg)); + const randInt = (Math.random() * 1000).toFixed(0); + console.time("syncswap" + randInt); + const swap = await wallet["syncWallet"].syncSwap({ + orders: [swapOffer, fillOrder], + feeToken: feeToken, + nonce: fillOrder.nonce, + }); + const txHash = swap.txHash.split(":")[1]; + const txHashMsg = { + op: "orderstatusupdate", + args: [[[chainId, orderId, "b", txHash]]], + }; + sendMessage(JSON.stringify(txHashMsg)); + console.timeEnd("syncswap" + randInt); + + console.time("receipt" + randInt); + let receipt, + success = false; + try { + receipt = await swap.awaitReceipt(); + if (receipt.success) { + success = true; + NONCES[swapOffer.accountId] = swapOffer.nonce; + } + } catch (e) { + receipt = null; + success = false; + } + console.timeEnd("receipt" + randInt); + console.log("Swap broadcast result", { swap, receipt }); + + let newStatus, error; + if (success) { + afterFill(chainId, orderId, wallet); + newStatus = "f"; + error = null; + } else { + newStatus = "r"; + error = swap.error.toString(); + } + + const orderCommitMsg = { + op: "orderstatusupdate", + args: [[[chainId, orderId, newStatus, txHash, error]]], + }; + sendMessage(JSON.stringify(orderCommitMsg)); } async function fillOpenOrders() { - for (let orderId in OPEN_ORDERS) { - const order = OPEN_ORDERS[orderId]; - const fillable = isOrderFillable(order); - if (fillable.fillable) { - sendFillRequest(order, fillable.walletId); - delete OPEN_ORDERS[orderId]; - }else if (![ - "sending order already", - "badprice" - ].includes(fillable.reason)) { - delete OPEN_ORDERS[orderId]; - } + for (let orderId in OPEN_ORDERS) { + const order = OPEN_ORDERS[orderId]; + const fillable = isOrderFillable(order); + if (fillable.fillable) { + sendFillRequest(order, fillable.walletId); + delete OPEN_ORDERS[orderId]; + } else if (!["sending order already", "badprice"].includes(fillable.reason)) { + delete OPEN_ORDERS[orderId]; } + } } async function setupPriceFeeds() { - const cryptowatch = [], chainlink = [], uniswapV3 = []; - for (let market in MM_CONFIG.pairs) { - const pairConfig = MM_CONFIG.pairs[market]; - if(!pairConfig.active) { continue; } - // This is needed to make the price feed backwards compatalbe with old constant mode: - // "DYDX-USDC": { - // "mode": "constant", - // "initPrice": 20, - if(pairConfig.mode == "constant") { - const initPrice = pairConfig.initPrice; - pairConfig['priceFeedPrimary'] = "constant:" + initPrice.toString(); - } - const primaryPriceFeed = pairConfig.priceFeedPrimary; - const secondaryPriceFeed = pairConfig.priceFeedSecondary; - - // parse keys to lower case to match later PRICE_FEED keys - if (primaryPriceFeed) { - MM_CONFIG.pairs[market].priceFeedPrimary = primaryPriceFeed.toLowerCase(); - } - if (secondaryPriceFeed) { - MM_CONFIG.pairs[market].priceFeedSecondary = secondaryPriceFeed.toLowerCase(); - } - [primaryPriceFeed, secondaryPriceFeed].forEach(priceFeed => { - if(!priceFeed) { return; } - const [provider, id] = priceFeed.split(':'); - switch(provider.toLowerCase()) { - case 'cryptowatch': - if(!cryptowatch.includes(id)) { cryptowatch.push(id); } - break; - case 'chainlink': - if(!chainlink.includes(id)) { chainlink.push(id); } - break; - case 'uniswapv3': - if(!uniswapV3.includes(id)) { uniswapV3.push(id); } - break; - case 'constant': - PRICE_FEEDS['constant:'+id] = parseFloat(id); - break; - default: - throw new Error("Price feed provider "+provider+" is not available.") - break; + const cryptowatch = [], + chainlink = [], + uniswapV3 = []; + for (let market in MM_CONFIG.pairs) { + const pairConfig = MM_CONFIG.pairs[market]; + if (!pairConfig.active) { + continue; + } + // This is needed to make the price feed backwards compatalbe with old constant mode: + // "DYDX-USDC": { + // "mode": "constant", + // "initPrice": 20, + if (pairConfig.mode == "constant") { + const initPrice = pairConfig.initPrice; + pairConfig["priceFeedPrimary"] = "constant:" + initPrice.toString(); + } + const primaryPriceFeed = pairConfig.priceFeedPrimary; + const secondaryPriceFeed = pairConfig.priceFeedSecondary; + + // parse keys to lower case to match later PRICE_FEED keys + if (primaryPriceFeed) { + MM_CONFIG.pairs[market].priceFeedPrimary = primaryPriceFeed.toLowerCase(); + } + if (secondaryPriceFeed) { + MM_CONFIG.pairs[market].priceFeedSecondary = secondaryPriceFeed.toLowerCase(); + } + [primaryPriceFeed, secondaryPriceFeed].forEach((priceFeed) => { + if (!priceFeed) { + return; + } + const [provider, id] = priceFeed.split(":"); + switch (provider.toLowerCase()) { + case "cryptowatch": + if (!cryptowatch.includes(id)) { + cryptowatch.push(id); } - }); + break; + case "chainlink": + if (!chainlink.includes(id)) { + chainlink.push(id); + } + break; + case "uniswapv3": + if (!uniswapV3.includes(id)) { + uniswapV3.push(id); + } + break; + case "constant": + PRICE_FEEDS["constant:" + id] = parseFloat(id); + break; + default: + throw new Error("Price feed provider " + provider + " is not available."); + break; + } + }); } - if(chainlink.length > 0) await chainlinkSetup(chainlink); - if(cryptowatch.length > 0) await cryptowatchWsSetup(cryptowatch); - if(uniswapV3.length > 0) await uniswapV3Setup(uniswapV3); + if (chainlink.length > 0) await chainlinkSetup(chainlink); + if (cryptowatch.length > 0) await cryptowatchWsSetup(cryptowatch); + if (uniswapV3.length > 0) await uniswapV3Setup(uniswapV3); console.log(PRICE_FEEDS); } async function cryptowatchWsSetup(cryptowatchMarketIds) { - // Set initial prices - const cryptowatchApiKey = process.env.CRYPTOWATCH_API_KEY || MM_CONFIG.cryptowatchApiKey; - const cryptowatchMarkets = await fetch("https://api.cryptowat.ch/markets?apikey=" + cryptowatchApiKey).then(r => r.json()); - const cryptowatchMarketPrices = await fetch("https://api.cryptowat.ch/markets/prices?apikey=" + cryptowatchApiKey).then(r => r.json()); - for (let i in cryptowatchMarketIds) { - const cryptowatchMarketId = cryptowatchMarketIds[i]; - try { - const cryptowatchMarket = cryptowatchMarkets.result.find(row => row.id == cryptowatchMarketId); - const exchange = cryptowatchMarket.exchange; - const pair = cryptowatchMarket.pair; - const key = `market:${exchange}:${pair}`; - PRICE_FEEDS['cryptowatch:'+cryptowatchMarketIds[i]] = cryptowatchMarketPrices.result[key]; - } catch (e) { - console.error("Could not set price feed for cryptowatch:" + cryptowatchMarketId); - } + // Set initial prices + const cryptowatchApiKey = process.env.CRYPTOWATCH_API_KEY || MM_CONFIG.cryptowatchApiKey; + const cryptowatchMarkets = await fetch("https://api.cryptowat.ch/markets?apikey=" + cryptowatchApiKey).then((r) => r.json()); + const cryptowatchMarketPrices = await fetch("https://api.cryptowat.ch/markets/prices?apikey=" + cryptowatchApiKey).then((r) => r.json()); + for (let i in cryptowatchMarketIds) { + const cryptowatchMarketId = cryptowatchMarketIds[i]; + try { + const cryptowatchMarket = cryptowatchMarkets.result.find((row) => row.id == cryptowatchMarketId); + const exchange = cryptowatchMarket.exchange; + const pair = cryptowatchMarket.pair; + const key = `market:${exchange}:${pair}`; + PRICE_FEEDS["cryptowatch:" + cryptowatchMarketIds[i]] = cryptowatchMarketPrices.result[key]; + } catch (e) { + console.error("Could not set price feed for cryptowatch:" + cryptowatchMarketId); } + } - const subscriptionMsg = { - "subscribe": { - "subscriptions": [] - } - } - for (let i in cryptowatchMarketIds) { - const cryptowatchMarketId = cryptowatchMarketIds[i]; + const subscriptionMsg = { + subscribe: { + subscriptions: [], + }, + }; + for (let i in cryptowatchMarketIds) { + const cryptowatchMarketId = cryptowatchMarketIds[i]; - // first get initial price info + // first get initial price info - subscriptionMsg.subscribe.subscriptions.push({ - "streamSubscription": { - "resource": `markets:${cryptowatchMarketId}:book:spread` - } - }) - } - let cryptowatch_ws = new WebSocket("wss://stream.cryptowat.ch/connect?apikey=" + cryptowatchApiKey); - cryptowatch_ws.on('open', onopen); - cryptowatch_ws.on('message', onmessage); - cryptowatch_ws.on('close', onclose); - cryptowatch_ws.on('error', console.error); - - function onopen() { - cryptowatch_ws.send(JSON.stringify(subscriptionMsg)); - } - function onmessage (data) { - const msg = JSON.parse(data); - if (!msg.marketUpdate) return; - - const marketId = "cryptowatch:" + msg.marketUpdate.market.marketId; - let ask = msg.marketUpdate.orderBookSpreadUpdate.ask.priceStr; - let bid = msg.marketUpdate.orderBookSpreadUpdate.bid.priceStr; - let price = ask / 2 + bid / 2; - PRICE_FEEDS[marketId] = price; - } - function onclose () { - setTimeout(cryptowatchWsSetup, 5000, cryptowatchMarketIds); - } + subscriptionMsg.subscribe.subscriptions.push({ + streamSubscription: { + resource: `markets:${cryptowatchMarketId}:book:spread`, + }, + }); + } + let cryptowatch_ws = new WebSocket("wss://stream.cryptowat.ch/connect?apikey=" + cryptowatchApiKey); + cryptowatch_ws.on("open", onopen); + cryptowatch_ws.on("message", onmessage); + cryptowatch_ws.on("close", onclose); + cryptowatch_ws.on("error", console.error); + + function onopen() { + cryptowatch_ws.send(JSON.stringify(subscriptionMsg)); + } + function onmessage(data) { + const msg = JSON.parse(data); + if (!msg.marketUpdate) return; + + const marketId = "cryptowatch:" + msg.marketUpdate.market.marketId; + let ask = msg.marketUpdate.orderBookSpreadUpdate.ask.priceStr; + let bid = msg.marketUpdate.orderBookSpreadUpdate.bid.priceStr; + let price = ask / 2 + bid / 2; + PRICE_FEEDS[marketId] = price; + } + function onclose() { + setTimeout(cryptowatchWsSetup, 5000, cryptowatchMarketIds); + } } async function chainlinkSetup(chainlinkMarketAddress) { - const results = chainlinkMarketAddress.map(async (address) => { - try { - const aggregatorV3InterfaceABI = JSON.parse(fs.readFileSync('ABIs/chainlinkV3InterfaceABI.abi')); - const provider = new ethers.Contract(address, aggregatorV3InterfaceABI, ethersProvider); - const decimals = await provider.decimals(); - const key = 'chainlink:' + address; - CHAINLINK_PROVIDERS[key] = [provider, decimals]; - - // get inital price - const response = await provider.latestRoundData(); - PRICE_FEEDS[key] = parseFloat(response.answer) / 10**decimals; - } catch (e) { - throw new Error ("Error while setting up chainlink for "+address+", Error: "+e); - } - }); - await Promise.all(results); - setInterval(chainlinkUpdate, 30000); + const results = chainlinkMarketAddress.map(async (address) => { + try { + const aggregatorV3InterfaceABI = JSON.parse(fs.readFileSync("ABIs/chainlinkV3InterfaceABI.abi")); + const provider = new ethers.Contract(address, aggregatorV3InterfaceABI, ethersProvider); + const decimals = await provider.decimals(); + const key = "chainlink:" + address; + CHAINLINK_PROVIDERS[key] = [provider, decimals]; + + // get inital price + const response = await provider.latestRoundData(); + PRICE_FEEDS[key] = parseFloat(response.answer) / 10 ** decimals; + } catch (e) { + throw new Error("Error while setting up chainlink for " + address + ", Error: " + e); + } + }); + await Promise.all(results); + setInterval(chainlinkUpdate, 30000); } async function chainlinkUpdate() { - try { - await Promise.all(Object.keys(CHAINLINK_PROVIDERS).map(async (key) => { - const [provider, decimals] = CHAINLINK_PROVIDERS[key]; - const response = await provider.latestRoundData(); - PRICE_FEEDS[key] = parseFloat(response.answer) / 10**decimals; - })); - chainlink_error_counter = 0; - } catch (err) { - chainlink_error_counter += 1; - console.log(`Failed to update chainlink, retry: ${err.message}`); - if(chainlink_error_counter > 4) { - throw new Error ("Failed to update chainlink since 150 seconds!") - } + try { + await Promise.all( + Object.keys(CHAINLINK_PROVIDERS).map(async (key) => { + const [provider, decimals] = CHAINLINK_PROVIDERS[key]; + const response = await provider.latestRoundData(); + PRICE_FEEDS[key] = parseFloat(response.answer) / 10 ** decimals; + }) + ); + chainlink_error_counter = 0; + } catch (err) { + chainlink_error_counter += 1; + console.log(`Failed to update chainlink, retry: ${err.message}`); + if (chainlink_error_counter > 4) { + throw new Error("Failed to update chainlink since 150 seconds!"); } + } } async function uniswapV3Setup(uniswapV3Address) { - const results = uniswapV3Address.map(async (address) => { - try { - const IUniswapV3PoolABI = JSON.parse(fs.readFileSync('ABIs/IUniswapV3Pool.abi')); - const ERC20ABI = JSON.parse(fs.readFileSync('ABIs/ERC20.abi')); - - const provider = new ethers.Contract(address, IUniswapV3PoolABI, ethersProvider); - - let [ - slot0, - addressToken0, - addressToken1 - ] = await Promise.all ([ - provider.slot0(), - provider.token0(), - provider.token1() - ]); - - const tokenProvier0 = new ethers.Contract(addressToken0, ERC20ABI, ethersProvider); - const tokenProvier1 = new ethers.Contract(addressToken1, ERC20ABI, ethersProvider); - - let [ - decimals0, - decimals1 - ] = await Promise.all ([ - tokenProvier0.decimals(), - tokenProvier1.decimals() - ]); - - const key = 'uniswapv3:' + address; - const decimalsRatio = (10**decimals0 / 10**decimals1); - UNISWAP_V3_PROVIDERS[key] = [provider, decimalsRatio]; - - // get inital price - const price = (slot0.sqrtPriceX96*slot0.sqrtPriceX96*decimalsRatio) / (2**192); - PRICE_FEEDS[key] = price; - } catch (e) { - throw new Error ("Error while setting up uniswapV3 for "+address+", Error: "+e); - } - }); - await Promise.all(results); - setInterval(uniswapV3Update, 30000); + const results = uniswapV3Address.map(async (address) => { + try { + const IUniswapV3PoolABI = JSON.parse(fs.readFileSync("ABIs/IUniswapV3Pool.abi")); + const ERC20ABI = JSON.parse(fs.readFileSync("ABIs/ERC20.abi")); + + const provider = new ethers.Contract(address, IUniswapV3PoolABI, ethersProvider); + + let [slot0, addressToken0, addressToken1] = await Promise.all([provider.slot0(), provider.token0(), provider.token1()]); + + const tokenProvier0 = new ethers.Contract(addressToken0, ERC20ABI, ethersProvider); + const tokenProvier1 = new ethers.Contract(addressToken1, ERC20ABI, ethersProvider); + + let [decimals0, decimals1] = await Promise.all([tokenProvier0.decimals(), tokenProvier1.decimals()]); + + const key = "uniswapv3:" + address; + const decimalsRatio = 10 ** decimals0 / 10 ** decimals1; + UNISWAP_V3_PROVIDERS[key] = [provider, decimalsRatio]; + + // get inital price + const price = (slot0.sqrtPriceX96 * slot0.sqrtPriceX96 * decimalsRatio) / 2 ** 192; + PRICE_FEEDS[key] = price; + } catch (e) { + throw new Error("Error while setting up uniswapV3 for " + address + ", Error: " + e); + } + }); + await Promise.all(results); + setInterval(uniswapV3Update, 30000); } async function uniswapV3Update() { - try { - await Promise.all(Object.keys(UNISWAP_V3_PROVIDERS).map(async (key) => { - const [provider, decimalsRatio] = UNISWAP_V3_PROVIDERS[key]; - const slot0 = await provider.slot0(); - PRICE_FEEDS[key] = (slot0.sqrtPriceX96*slot0.sqrtPriceX96*decimalsRatio) / (2**192); - })); - // reset error counter if successful - uniswap_error_counter = 0; - } catch (err) { - uniswap_error_counter += 1; - console.log(`Failed to update uniswap, retry: ${err.message}`); - console.log(err.message); - if(uniswap_error_counter > 4) { - throw new Error ("Failed to update uniswap since 150 seconds!") - } + try { + await Promise.all( + Object.keys(UNISWAP_V3_PROVIDERS).map(async (key) => { + const [provider, decimalsRatio] = UNISWAP_V3_PROVIDERS[key]; + const slot0 = await provider.slot0(); + PRICE_FEEDS[key] = (slot0.sqrtPriceX96 * slot0.sqrtPriceX96 * decimalsRatio) / 2 ** 192; + }) + ); + // reset error counter if successful + uniswap_error_counter = 0; + } catch (err) { + uniswap_error_counter += 1; + console.log(`Failed to update uniswap, retry: ${err.message}`); + console.log(err.message); + if (uniswap_error_counter > 4) { + throw new Error("Failed to update uniswap since 150 seconds!"); } + } } -function indicateLiquidity (pairs = MM_CONFIG.pairs) { - for(const marketId in pairs) { - const mmConfig = pairs[marketId]; - if(!mmConfig || !mmConfig.active) continue; +function indicateLiquidity(pairs = MM_CONFIG.pairs) { + for (const marketId in pairs) { + const mmConfig = pairs[marketId]; + if (!mmConfig || !mmConfig.active) continue; - try { - validatePriceFeed(marketId); - } catch(e) { - console.error("Can not indicateLiquidity ("+marketId+") because: " + e); - continue; - } + try { + validatePriceFeed(marketId); + } catch (e) { + console.error("Can not indicateLiquidity (" + marketId + ") because: " + e); + continue; + } - const marketInfo = MARKETS[marketId]; - if (!marketInfo) continue; - - const midPrice = (mmConfig.invert) - ? (1 / PRICE_FEEDS[mmConfig.priceFeedPrimary]) - : PRICE_FEEDS[mmConfig.priceFeedPrimary]; - if (!midPrice) continue; - - const expires = (Date.now() / 1000 | 0) + 10; // 10s expiry - const side = mmConfig.side || 'd'; - - let maxBaseBalance = 0, maxQuoteBalance = 0; - Object.keys(WALLETS).forEach(accountId => { - const walletBase = WALLETS[accountId]['account_state'].committed.balances[marketInfo.baseAsset.symbol]; - const walletQuote = WALLETS[accountId]['account_state'].committed.balances[marketInfo.quoteAsset.symbol]; - if (Number(walletBase) > maxBaseBalance) { - maxBaseBalance = walletBase; - } - if (Number(walletQuote) > maxQuoteBalance) { - maxQuoteBalance = walletQuote; - } - }); - const baseBalance = maxBaseBalance / 10**marketInfo.baseAsset.decimals; - const quoteBalance = maxQuoteBalance / 10**marketInfo.quoteAsset.decimals; - const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); - const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize); - - // dont do splits if under 1000 USD - const usdBaseBalance = baseBalance * marketInfo.baseAsset.usdPrice; - const usdQuoteBalance = quoteBalance * marketInfo.quoteAsset.usdPrice; - let buySplits = (usdQuoteBalance && usdQuoteBalance < 1000) ? 1 : (mmConfig.numOrdersIndicated || 4); - let sellSplits = (usdBaseBalance && usdBaseBalance < 1000) ? 1 : (mmConfig.numOrdersIndicated || 4); - - if (usdQuoteBalance && usdQuoteBalance < (10 * buySplits)) buySplits = Math.floor(usdQuoteBalance / 10) - if (usdBaseBalance && usdBaseBalance < (10 * sellSplits)) sellSplits = Math.floor(usdBaseBalance / 10) - - const liquidity = []; - for (let i=1; i <= buySplits; i++) { - const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i/buySplits)); - if ((['b','d']).includes(side)) { - liquidity.push(["b", buyPrice, maxBuySize / buySplits, expires]); - } - } - for (let i=1; i <= sellSplits; i++) { - const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i/sellSplits)); - if ((['s','d']).includes(side)) { - liquidity.push(["s", sellPrice, maxSellSize / sellSplits, expires]); - } - } + const marketInfo = MARKETS[marketId]; + if (!marketInfo) continue; - const msg = { op: "indicateliq2", args: [CHAIN_ID, marketId, liquidity] }; - try { - zigzagws.send(JSON.stringify(msg)); - } catch (e) { - console.error("Could not send liquidity"); - console.error(e); - } + const midPrice = mmConfig.invert ? 1 / PRICE_FEEDS[mmConfig.priceFeedPrimary] : PRICE_FEEDS[mmConfig.priceFeedPrimary]; + if (!midPrice) continue; + + const expires = ((Date.now() / 1000) | 0) + 10; // 10s expiry + const side = mmConfig.side || "d"; + + let maxBaseBalance = 0, + maxQuoteBalance = 0; + Object.keys(WALLETS).forEach((accountId) => { + const walletBase = WALLETS[accountId]["account_state"].committed.balances[marketInfo.baseAsset.symbol]; + const walletQuote = WALLETS[accountId]["account_state"].committed.balances[marketInfo.quoteAsset.symbol]; + if (Number(walletBase) > maxBaseBalance) { + maxBaseBalance = walletBase; + } + if (Number(walletQuote) > maxQuoteBalance) { + maxQuoteBalance = walletQuote; + } + }); + const baseBalance = maxBaseBalance / 10 ** marketInfo.baseAsset.decimals; + const quoteBalance = maxQuoteBalance / 10 ** marketInfo.quoteAsset.decimals; + const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); + const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize); + + // dont do splits if under 1000 USD + const usdBaseBalance = baseBalance * marketInfo.baseAsset.usdPrice; + const usdQuoteBalance = quoteBalance * marketInfo.quoteAsset.usdPrice; + let buySplits = usdQuoteBalance && usdQuoteBalance < 1000 ? 1 : mmConfig.numOrdersIndicated || 4; + let sellSplits = usdBaseBalance && usdBaseBalance < 1000 ? 1 : mmConfig.numOrdersIndicated || 4; + + if (usdQuoteBalance && usdQuoteBalance < 10 * buySplits) buySplits = Math.floor(usdQuoteBalance / 10); + if (usdBaseBalance && usdBaseBalance < 10 * sellSplits) sellSplits = Math.floor(usdBaseBalance / 10); + + const liquidity = []; + for (let i = 1; i <= buySplits; i++) { + const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i) / buySplits); + if (["b", "d"].includes(side)) { + liquidity.push(["b", buyPrice, maxBuySize / buySplits, expires]); + } + } + for (let i = 1; i <= sellSplits; i++) { + const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i) / sellSplits); + if (["s", "d"].includes(side)) { + liquidity.push(["s", sellPrice, maxSellSize / sellSplits, expires]); + } } -} -function cancelLiquidity (chainId, marketId) { - const msg = { op: "indicateliq2", args: [chainId, marketId, []] }; + const msg = { + op: "indicateliq2", + args: [CHAIN_ID, marketId, liquidity], + }; try { - zigzagws.send(JSON.stringify(msg)); + sendMessage(JSON.stringify(msg)); } catch (e) { - console.error("Could not send liquidity"); - console.error(e); + console.error("Could not send liquidity"); + console.error(e); } + } } -async function afterFill(chainId, orderId, wallet) { - const order = PAST_ORDER_LIST[orderId]; - if(!order) { return; } - const marketId = order.marketId; - const mmConfig = MM_CONFIG.pairs[marketId]; - if(!mmConfig) { return; } - - // update account state from order - const account_state = wallet['account_state'].committed.balances; - const buyTokenParsed = syncProvider.tokenSet.parseToken ( - order.buySymbol, - order.buyQuantity - ); - const sellTokenParsed = syncProvider.tokenSet.parseToken ( - order.sellSymbol, - order.sellQuantity - ); - const oldBuyBalance = account_state[order.buySymbol] ? account_state[order.buySymbol] : '0'; - const oldSellBalance = account_state[order.sellSymbol] ? account_state[order.sellSymbol] : '0'; - const oldBuyTokenParsed = ethers.BigNumber.from(oldBuyBalance); - const oldSellTokenParsed = ethers.BigNumber.from(oldSellBalance); - account_state[order.buySymbol] = (oldBuyTokenParsed.add(buyTokenParsed)).toString(); - account_state[order.sellSymbol] = (oldSellTokenParsed.sub(sellTokenParsed)).toString(); - - const indicateMarket = {}; - indicateMarket[marketId] = mmConfig; - if(mmConfig.delayAfterFill) { - let delayAfterFillMinSize - if( - !Array.isArray(mmConfig.delayAfterFill) || - !mmConfig.delayAfterFill[1] - ) { - delayAfterFillMinSize = 0; - } else { - delayAfterFillMinSize = mmConfig.delayAfterFill[1] - } +function cancelLiquidity(chainId, marketId) { + const msg = { op: "indicateliq2", args: [chainId, marketId, []] }; + try { + sendMessage(JSON.stringify(msg)); + } catch (e) { + console.error("Could not send liquidity"); + console.error(e); + } +} - if(order.baseQuantity > delayAfterFillMinSize) { - // no array -> old config - // or array and buyQuantity over minSize - mmConfig.active = false; - cancelLiquidity (chainId, marketId); - console.log(`Set ${marketId} passive for ${mmConfig.delayAfterFill} seconds.`); - setTimeout(() => { - mmConfig.active = true; - console.log(`Set ${marketId} active.`); - indicateLiquidity(indicateMarket); - }, mmConfig.delayAfterFill * 1000); - } - } +async function afterFill(chainId, orderId, wallet) { + const order = PAST_ORDER_LIST[orderId]; + if (!order) { + return; + } + const marketId = order.marketId; + const mmConfig = MM_CONFIG.pairs[marketId]; + if (!mmConfig) { + return; + } - // increaseSpreadAfterFill size might not be set - const increaseSpreadAfterFillMinSize = (mmConfig.increaseSpreadAfterFill?.[2]) - ? mmConfig.increaseSpreadAfterFill[2] - : 0 - if( - mmConfig.increaseSpreadAfterFill && - order.baseQuantity > increaseSpreadAfterFillMinSize - - ) { - const [spread, time] = mmConfig.increaseSpreadAfterFill; - mmConfig.minSpread = mmConfig.minSpread + spread; - console.log(`Changed ${marketId} minSpread by ${spread}.`); + // update account state from order + const account_state = wallet["account_state"].committed.balances; + const buyTokenParsed = syncProvider.tokenSet.parseToken(order.buySymbol, order.buyQuantity); + const sellTokenParsed = syncProvider.tokenSet.parseToken(order.sellSymbol, order.sellQuantity); + const oldBuyBalance = account_state[order.buySymbol] ? account_state[order.buySymbol] : "0"; + const oldSellBalance = account_state[order.sellSymbol] ? account_state[order.sellSymbol] : "0"; + const oldBuyTokenParsed = ethers.BigNumber.from(oldBuyBalance); + const oldSellTokenParsed = ethers.BigNumber.from(oldSellBalance); + account_state[order.buySymbol] = oldBuyTokenParsed.add(buyTokenParsed).toString(); + account_state[order.sellSymbol] = oldSellTokenParsed.sub(sellTokenParsed).toString(); + + const indicateMarket = {}; + indicateMarket[marketId] = mmConfig; + if (mmConfig.delayAfterFill) { + let delayAfterFillMinSize; + if (!Array.isArray(mmConfig.delayAfterFill) || !mmConfig.delayAfterFill[1]) { + delayAfterFillMinSize = 0; + } else { + delayAfterFillMinSize = mmConfig.delayAfterFill[1]; + } + + if (order.baseQuantity > delayAfterFillMinSize) { + // no array -> old config + // or array and buyQuantity over minSize + mmConfig.active = false; + cancelLiquidity(chainId, marketId); + console.log(`Set ${marketId} passive for ${mmConfig.delayAfterFill} seconds.`); + setTimeout(() => { + mmConfig.active = true; + console.log(`Set ${marketId} active.`); indicateLiquidity(indicateMarket); - setTimeout(() => { - mmConfig.minSpread = mmConfig.minSpread - spread; - console.log(`Changed ${marketId} minSpread by -${spread}.`); - indicateLiquidity(indicateMarket); - }, time * 1000); + }, mmConfig.delayAfterFill * 1000); } + } - // changeSizeAfterFill size might not be set - const changeSizeAfterFillMinSize = (mmConfig.changeSizeAfterFill?.[2]) - ? mmConfig.changeSizeAfterFill[2] - : 0 - if( - mmConfig.changeSizeAfterFill && - order.baseQuantity > changeSizeAfterFillMinSize - ) { - const [size, time] = mmConfig.changeSizeAfterFill; - mmConfig.maxSize = mmConfig.maxSize + size; - console.log(`Changed ${marketId} maxSize by ${size}.`); - indicateLiquidity(indicateMarket); - setTimeout(() => { - mmConfig.maxSize = mmConfig.maxSize - size; - console.log(`Changed ${marketId} maxSize by ${(size* (-1))}.`); - indicateLiquidity(indicateMarket); - }, time * 1000); - } + // increaseSpreadAfterFill size might not be set + const increaseSpreadAfterFillMinSize = mmConfig.increaseSpreadAfterFill?.[2] ? mmConfig.increaseSpreadAfterFill[2] : 0; + if (mmConfig.increaseSpreadAfterFill && order.baseQuantity > increaseSpreadAfterFillMinSize) { + const [spread, time] = mmConfig.increaseSpreadAfterFill; + mmConfig.minSpread = mmConfig.minSpread + spread; + console.log(`Changed ${marketId} minSpread by ${spread}.`); + indicateLiquidity(indicateMarket); + setTimeout(() => { + mmConfig.minSpread = mmConfig.minSpread - spread; + console.log(`Changed ${marketId} minSpread by -${spread}.`); + indicateLiquidity(indicateMarket); + }, time * 1000); + } + + // changeSizeAfterFill size might not be set + const changeSizeAfterFillMinSize = mmConfig.changeSizeAfterFill?.[2] ? mmConfig.changeSizeAfterFill[2] : 0; + if (mmConfig.changeSizeAfterFill && order.baseQuantity > changeSizeAfterFillMinSize) { + const [size, time] = mmConfig.changeSizeAfterFill; + mmConfig.maxSize = mmConfig.maxSize + size; + console.log(`Changed ${marketId} maxSize by ${size}.`); + indicateLiquidity(indicateMarket); + setTimeout(() => { + mmConfig.maxSize = mmConfig.maxSize - size; + console.log(`Changed ${marketId} maxSize by ${size * -1}.`); + indicateLiquidity(indicateMarket); + }, time * 1000); + } } function rememberOrder(chainId, marketId, orderId, price, sellSymbol, sellQuantity, buySymbol, buyQuantity) { - const timestamp = Date.now() / 1000; - for (const [key, value] of Object.entries(PAST_ORDER_LIST)) { - if (value['expiry'] < timestamp) { - delete PAST_ORDER_LIST[key]; - } + const timestamp = Date.now() / 1000; + for (const [key, value] of Object.entries(PAST_ORDER_LIST)) { + if (value["expiry"] < timestamp) { + delete PAST_ORDER_LIST[key]; } + } - const [baseSymbol, quoteSymbol] = marketId.split('-') - let baseQuantity, quoteQuantity; - if(sellSymbol === baseSymbol) { - baseQuantity = sellQuantity; - quoteQuantity = buyQuantity; - } else { - baseQuantity = buyQuantity; - quoteQuantity = sellQuantity; - } + const [baseSymbol, quoteSymbol] = marketId.split("-"); + let baseQuantity, quoteQuantity; + if (sellSymbol === baseSymbol) { + baseQuantity = sellQuantity; + quoteQuantity = buyQuantity; + } else { + baseQuantity = buyQuantity; + quoteQuantity = sellQuantity; + } - const expiry = timestamp + 900; - PAST_ORDER_LIST[orderId] = { - 'chainId': chainId, - 'marketId': marketId, - 'price': price, - 'baseQuantity': baseQuantity, - 'quoteQuantity': quoteQuantity, - 'sellSymbol': sellSymbol, - 'sellQuantity': sellQuantity, - 'buySymbol': buySymbol, - 'buyQuantity': buyQuantity, - 'expiry':expiry - }; + const expiry = timestamp + 900; + PAST_ORDER_LIST[orderId] = { + chainId: chainId, + marketId: marketId, + price: price, + baseQuantity: baseQuantity, + quoteQuantity: quoteQuantity, + sellSymbol: sellSymbol, + sellQuantity: sellQuantity, + buySymbol: buySymbol, + buyQuantity: buyQuantity, + expiry: expiry, + }; } async function updateAccountState() { - try { - Object.keys(WALLETS).forEach(accountId => { - (WALLETS[accountId]['syncWallet']).getAccountState().then((state) => { - WALLETS[accountId]['account_state'] = state; - }) - }); - } catch(err) { - // pass - } + try { + Object.keys(WALLETS).forEach((accountId) => { + WALLETS[accountId]["syncWallet"].getAccountState().then((state) => { + WALLETS[accountId]["account_state"] = state; + }); + }); + } catch (err) { + // pass + } } +function sendMessage(msg) { + if (zigzagws.readyState === 1) { + if (msg != null) { + zigzagws.send(msg); + } + } else { + setTimeout(sendMessage(msg), 5); + } +} From 3b0abff06ca73dea98129236d10f8df2f45a4948 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 19 Mar 2023 14:13:20 +0100 Subject: [PATCH 158/160] avoid building a big callstack --- marketmaker.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index bab43f2..57e4983 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -912,12 +912,21 @@ async function updateAccountState() { } } -function sendMessage(msg) { +async function sendMessage(msg) { + let success = false; + do { + success = await waitForSocketConnection(msg); + } while (!success); +} + +async function waitForSocketConnection(msg) { if (zigzagws.readyState === 1) { if (msg != null) { zigzagws.send(msg); + return true; } } else { - setTimeout(sendMessage(msg), 5); + await new Promise(resolve => setTimeout(resolve, 10)); + return false; } -} +} \ No newline at end of file From 729c53ed0fbe4eb37f8cc51f5cf40ba76ae10aa3 Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Sun, 19 Mar 2023 14:16:26 +0100 Subject: [PATCH 159/160] fix format --- marketmaker.js | 1494 ++++++++++++++++++++++++------------------------ 1 file changed, 760 insertions(+), 734 deletions(-) diff --git a/marketmaker.js b/marketmaker.js index 57e4983..cda9afa 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -1,9 +1,9 @@ -import WebSocket from "ws"; +import WebSocket from 'ws'; import * as zksync from "zksync"; -import ethers from "ethers"; -import dotenv from "dotenv"; -import fetch from "node-fetch"; -import fs from "fs"; +import ethers from 'ethers'; +import dotenv from 'dotenv'; +import fetch from 'node-fetch'; +import fs from 'fs'; dotenv.config(); @@ -25,30 +25,34 @@ let chainlink_error_counter = 0; // Load MM config let MM_CONFIG; if (process.env.MM_CONFIG) { - MM_CONFIG = JSON.parse(process.env.MM_CONFIG); -} else { - const mmConfigFile = fs.readFileSync("config.json", "utf8"); - MM_CONFIG = JSON.parse(mmConfigFile); + MM_CONFIG = JSON.parse(process.env.MM_CONFIG); +} +else { + const mmConfigFile = fs.readFileSync("config.json", "utf8"); + MM_CONFIG = JSON.parse(mmConfigFile); } if (MM_CONFIG.feeToken) { FEE_TOKEN = MM_CONFIG.feeToken; } let activePairs = []; for (let marketId in MM_CONFIG.pairs) { - const pair = MM_CONFIG.pairs[marketId]; - if (pair.active) { - activePairs.push(marketId); - } + const pair = MM_CONFIG.pairs[marketId]; + if (pair.active) { + activePairs.push(marketId); + } } console.log("ACTIVE PAIRS", activePairs); // Connect to zksync const CHAIN_ID = parseInt(MM_CONFIG.zigzagChainId); -const ETH_NETWORK = CHAIN_ID === 1 ? "mainnet" : "goerli"; -const infureKey = process.env.infura || MM_CONFIG.infura; +const ETH_NETWORK = (CHAIN_ID === 1) ? "mainnet" : "goerli"; +const infureKey = (process.env.infura || MM_CONFIG.infura); let ethersProvider = null; if (infureKey) { - ethersProvider = new ethers.providers.InfuraProvider("mainnet", infureKey); + ethersProvider = new ethers.providers.InfuraProvider( + "mainnet", + infureKey + ); } else { ethersProvider = new ethers.getDefaultProvider("mainnet"); } @@ -58,48 +62,47 @@ await setupPriceFeeds(); let syncProvider; try { - syncProvider = await zksync.getDefaultProvider(ETH_NETWORK); - const keys = []; - const ethPrivKey = process.env.ETH_PRIVKEY || MM_CONFIG.ethPrivKey; - if (ethPrivKey && ethPrivKey != "") { - keys.push(ethPrivKey); - } - let ethPrivKeys; - if (process.env.ETH_PRIVKEYS) { - ethPrivKeys = JSON.parse(process.env.ETH_PRIVKEYS); - } else { - ethPrivKeys = MM_CONFIG.ethPrivKeys; - } - if (ethPrivKeys && ethPrivKeys.length > 0) { - ethPrivKeys.forEach((key) => { - if (key != "" && !keys.includes(key)) { - keys.push(key); - } - }); - } - for (let i = 0; i < keys.length; i++) { - let ethWallet = new ethers.Wallet(keys[i]); - let syncWallet = await zksync.Wallet.fromEthSigner(ethWallet, syncProvider); - if (!(await syncWallet.isSigningKeySet())) { - console.log("setting sign key"); - const signKeyResult = await syncWallet.setSigningKey({ - feeToken: "ETH", - ethAuthType: "ECDSA", - }); - console.log(signKeyResult); - } - let accountId = await syncWallet.getAccountId(); - let account_state = await syncWallet.getAccountState(); - WALLETS[accountId] = { - ethWallet: ethWallet, - syncWallet: syncWallet, - account_state: account_state, - ORDER_BROADCASTING: false, - }; - } + syncProvider = await zksync.getDefaultProvider(ETH_NETWORK); + const keys = []; + const ethPrivKey = (process.env.ETH_PRIVKEY || MM_CONFIG.ethPrivKey); + if(ethPrivKey && ethPrivKey != "") { keys.push(ethPrivKey); } + let ethPrivKeys; + if (process.env.ETH_PRIVKEYS) { + ethPrivKeys = JSON.parse(process.env.ETH_PRIVKEYS); + } + else { + ethPrivKeys = MM_CONFIG.ethPrivKeys; + } + if(ethPrivKeys && ethPrivKeys.length > 0) { + ethPrivKeys.forEach( key => { + if(key != "" && !keys.includes(key)) { + keys.push(key); + } + }); + } + for(let i=0; i { - clearInterval(fillOrdersInterval); - clearInterval(indicateLiquidityInterval); - zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); - zigzagws.on("open", onWsOpen); - zigzagws.on("close", onWsClose); - zigzagws.on("error", console.error); - }, 5000); +function onWsClose () { + console.log("Websocket closed. Restarting"); + setTimeout(() => { + clearInterval(fillOrdersInterval) + clearInterval(indicateLiquidityInterval) + zigzagws = new WebSocket(MM_CONFIG.zigzagWsUrl); + zigzagws.on('open', onWsOpen); + zigzagws.on('close', onWsClose); + zigzagws.on('error', console.error); + }, 5000); } async function handleMessage(json) { - const msg = JSON.parse(json); - if (!["lastprice", "liquidity2", "fillstatus", "marketinfo"].includes(msg.op)) console.log(json.toString()); - switch (msg.op) { - case "error": - const accountId = msg.args?.[1]; - if (msg.args[0] == "fillrequest" && accountId) { - WALLETS[accountId]["ORDER_BROADCASTING"] = false; - } - break; - case "orders": - const orders = msg.args[0]; - orders.forEach((order) => { - const orderId = order[1]; - const fillable = isOrderFillable(order); - console.log(fillable); - if (fillable.fillable) { - sendFillRequest(order, fillable.walletId); - } else if (["sending order already", "badprice"].includes(fillable.reason)) { - OPEN_ORDERS[orderId] = order; - } - }); - break; - case "userordermatch": - const chainId = msg.args[0]; - const orderId = msg.args[1]; - const fillOrder = msg.args[3]; - const wallet = WALLETS[fillOrder.accountId]; - if (!wallet) { - console.error("No wallet with this accountId: " + fillOrder.accountId); - break; - } else { - try { - await broadcastFill(chainId, orderId, msg.args[2], fillOrder, wallet); - } catch (e) { - const orderCommitMsg = { - op: "orderstatusupdate", - args: [[[chainId, orderId, "r", null, e.message]]], - }; - sendMessage(JSON.stringify(orderCommitMsg)); - console.error(e); - } - wallet["ORDER_BROADCASTING"] = false; - } - break; - case "marketinfo": - const marketInfo = msg.args[0]; - const marketId = marketInfo.alias; - if (!marketId) break; - let oldBaseFee = "N/A", - oldQuoteFee = "N/A"; - try { - oldBaseFee = MARKETS[marketId].baseFee; - oldQuoteFee = MARKETS[marketId].quoteFee; - } catch (e) { - // pass, no old marketInfo - } - MARKETS[marketId] = marketInfo; - const newBaseFee = MARKETS[marketId].baseFee; - const newQuoteFee = MARKETS[marketId].quoteFee; - console.log(`marketinfo ${marketId} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); - if (FEE_TOKEN) break; - if (marketInfo.baseAsset.enabledForFees) { - if (!FEE_TOKEN_LIST.includes(marketInfo.baseAsset.id)) { - FEE_TOKEN_LIST.push(marketInfo.baseAsset.id); - } - } else { - const index = FEE_TOKEN_LIST.indexOf(marketInfo.baseAsset.id); - if (index >= 0) { - FEE_TOKEN_LIST.splice(index, 1); - } - } - if (marketInfo.quoteAsset.enabledForFees) { - if (!FEE_TOKEN_LIST.includes(marketInfo.quoteAsset.id)) { - FEE_TOKEN_LIST.push(marketInfo.quoteAsset.id); - } - } else { - const index = FEE_TOKEN_LIST.indexOf(marketInfo.quoteAsset.id); - if (index >= 0) { - FEE_TOKEN_LIST.splice(index, 1); - } - } - break; - default: - break; - } + const msg = JSON.parse(json); + if (!(["lastprice", "liquidity2", "fillstatus", "marketinfo"]).includes(msg.op)) console.log(json.toString()); + switch(msg.op) { + case 'error': + const accountId = msg.args?.[1]; + if(msg.args[0] == 'fillrequest' && accountId) { + WALLETS[accountId]['ORDER_BROADCASTING'] = false; + } + break; + case 'orders': + const orders = msg.args[0]; + orders.forEach(order => { + const orderId = order[1]; + const fillable = isOrderFillable(order); + console.log(fillable); + if (fillable.fillable) { + sendFillRequest(order, fillable.walletId); + } else if ([ + "sending order already", + "badprice" + ].includes(fillable.reason)) { + OPEN_ORDERS[orderId] = order; + } + }); + break + case "userordermatch": + const chainId = msg.args[0]; + const orderId = msg.args[1]; + const fillOrder = msg.args[3]; + const wallet = WALLETS[fillOrder.accountId]; + if(!wallet) { + console.error("No wallet with this accountId: "+fillOrder.accountId); + break + } else { + try { + await broadcastFill(chainId, orderId, msg.args[2], fillOrder, wallet); + } catch (e) { + const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'r',null,e.message]]]} + sendMessage(JSON.stringify(orderCommitMsg)); + console.error(e); + } + wallet['ORDER_BROADCASTING'] = false; + } + break + case "marketinfo": + const marketInfo = msg.args[0]; + const marketId = marketInfo.alias; + if(!marketId) break + let oldBaseFee = "N/A", oldQuoteFee = "N/A"; + try { + oldBaseFee = MARKETS[marketId].baseFee; + oldQuoteFee = MARKETS[marketId].quoteFee; + } catch (e) { + // pass, no old marketInfo + } + MARKETS[marketId] = marketInfo; + const newBaseFee = MARKETS[marketId].baseFee; + const newQuoteFee = MARKETS[marketId].quoteFee; + console.log(`marketinfo ${marketId} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); + if (FEE_TOKEN) break + if(marketInfo.baseAsset.enabledForFees) { + if (!FEE_TOKEN_LIST.includes(marketInfo.baseAsset.id)) { + FEE_TOKEN_LIST.push(marketInfo.baseAsset.id); + } + } else { + const index = FEE_TOKEN_LIST.indexOf(marketInfo.baseAsset.id); + if (index >= 0) { + FEE_TOKEN_LIST.splice(index, 1); + } + } + if(marketInfo.quoteAsset.enabledForFees) { + if (!FEE_TOKEN_LIST.includes(marketInfo.quoteAsset.id)) { + FEE_TOKEN_LIST.push(marketInfo.quoteAsset.id); + } + } else { + const index = FEE_TOKEN_LIST.indexOf(marketInfo.quoteAsset.id); + if (index >= 0) { + FEE_TOKEN_LIST.splice(index, 1); + } + } + break + default: + break + } } function isOrderFillable(order) { - const chainId = order[0]; - const marketId = order[2]; - const market = MARKETS[marketId]; - const mmConfig = MM_CONFIG.pairs[marketId]; - const mmSide = mmConfig.side ? mmConfig.side : "d"; - if (chainId != CHAIN_ID) return { fillable: false, reason: "badchain" }; - if (!market) return { fillable: false, reason: "badmarket" }; - if (!mmConfig.active) return { fillable: false, reason: "inactivemarket" }; - - const baseQuantity = order[5]; - const quoteQuantity = order[6]; - const expires = order[7]; - const side = order[3]; - const price = order[4]; - - const now = (Date.now() / 1000) | 0; + const chainId = order[0]; + const marketId = order[2]; + const market = MARKETS[marketId]; + const mmConfig = MM_CONFIG.pairs[marketId]; + const mmSide = (mmConfig.side) ? mmConfig.side : 'd'; + if (chainId != CHAIN_ID) return { fillable: false, reason: "badchain" } + if (!market) return { fillable: false, reason: "badmarket" } + if (!mmConfig.active) return { fillable: false, reason: "inactivemarket" } + + const baseQuantity = order[5]; + const quoteQuantity = order[6]; + const expires = order[7]; + const side = order[3]; + const price = order[4]; + + const now = Date.now() / 1000 | 0; + + if (now > expires) { + return { fillable: false, reason: "expired" }; + } - if (now > expires) { - return { fillable: false, reason: "expired" }; - } + if (mmSide !== 'd' && mmSide == side) { + return { fillable: false, reason: "badside" }; + } - if (mmSide !== "d" && mmSide == side) { - return { fillable: false, reason: "badside" }; - } + if (baseQuantity < mmConfig.minSize) { + return { fillable: false, reason: "badsize" }; + } + else if (baseQuantity > mmConfig.maxSize) { + return { fillable: false, reason: "badsize" }; + } - if (baseQuantity < mmConfig.minSize) { - return { fillable: false, reason: "badsize" }; - } else if (baseQuantity > mmConfig.maxSize) { - return { fillable: false, reason: "badsize" }; - } + let quote; + try { + quote = genQuote(chainId, marketId, side, baseQuantity); + } catch (e) { + return { fillable: false, reason: e.message } + } + if (side == 's' && price > quote.quotePrice) { + return { fillable: false, reason: "badprice" }; + } + else if (side == 'b' && price < quote.quotePrice) { + return { fillable: false, reason: "badprice" }; + } - let quote; - try { - quote = genQuote(chainId, marketId, side, baseQuantity); - } catch (e) { - return { fillable: false, reason: e.message }; - } - if (side == "s" && price > quote.quotePrice) { - return { fillable: false, reason: "badprice" }; - } else if (side == "b" && price < quote.quotePrice) { - return { fillable: false, reason: "badprice" }; - } + const sellCurrency = (side === 's') ? market.quoteAsset.symbol : market.baseAsset.symbol; + const sellDecimals = (side === 's') ? market.quoteAsset.decimals : market.baseAsset.decimals; + const sellQuantity = (side === 's') ? quote.quoteQuantity : baseQuantity; + const neededBalanceBN = sellQuantity * 10**sellDecimals; + let goodWalletIds = []; + Object.keys(WALLETS).forEach(accountId => { + const walletBalance = WALLETS[accountId]['account_state'].committed.balances[sellCurrency]; + if (Number(walletBalance) > (neededBalanceBN * 1.05)) { + goodWalletIds.push(accountId); + } + }); - const sellCurrency = side === "s" ? market.quoteAsset.symbol : market.baseAsset.symbol; - const sellDecimals = side === "s" ? market.quoteAsset.decimals : market.baseAsset.decimals; - const sellQuantity = side === "s" ? quote.quoteQuantity : baseQuantity; - const neededBalanceBN = sellQuantity * 10 ** sellDecimals; - let goodWalletIds = []; - Object.keys(WALLETS).forEach((accountId) => { - const walletBalance = WALLETS[accountId]["account_state"].committed.balances[sellCurrency]; - if (Number(walletBalance) > neededBalanceBN * 1.05) { - goodWalletIds.push(accountId); + if (goodWalletIds.length === 0) { + return { fillable: false, reason: "badbalance" }; } - }); - - if (goodWalletIds.length === 0) { - return { fillable: false, reason: "badbalance" }; - } - goodWalletIds = goodWalletIds.filter((accountId) => { - return !WALLETS[accountId]["ORDER_BROADCASTING"]; - }); + goodWalletIds = goodWalletIds.filter(accountId => { + return !WALLETS[accountId]['ORDER_BROADCASTING']; + }); - if (goodWalletIds.length === 0) { - return { fillable: false, reason: "sending order already" }; - } + if (goodWalletIds.length === 0) { + return { fillable: false, reason: "sending order already" }; + } - return { fillable: true, reason: null, walletId: goodWalletIds[0] }; + return { fillable: true, reason: null, walletId: goodWalletIds[0]}; } function genQuote(chainId, marketId, side, baseQuantity) { const market = MARKETS[marketId]; if (CHAIN_ID !== chainId) throw new Error("badchain"); if (!market) throw new Error("badmarket"); - if (!["b", "s"].includes(side)) throw new Error("badside"); + if (!(['b','s']).includes(side)) throw new Error("badside"); if (baseQuantity <= 0) throw new Error("badquantity"); validatePriceFeed(marketId); const mmConfig = MM_CONFIG.pairs[marketId]; - const mmSide = mmConfig.side || "d"; - if (mmSide !== "d" && mmSide === side) { - throw new Error("badside"); + const mmSide = mmConfig.side || 'd'; + if (mmSide !== 'd' && mmSide === side) { + throw new Error("badside"); } - const primaryPrice = mmConfig.invert ? 1 / PRICE_FEEDS[mmConfig.priceFeedPrimary] : PRICE_FEEDS[mmConfig.priceFeedPrimary]; + const primaryPrice = (mmConfig.invert) + ? (1 / PRICE_FEEDS[mmConfig.priceFeedPrimary]) + : PRICE_FEEDS[mmConfig.priceFeedPrimary]; if (!primaryPrice) throw new Error("badprice"); - const SPREAD = mmConfig.minSpread + baseQuantity * mmConfig.slippageRate; + const SPREAD = mmConfig.minSpread + (baseQuantity * mmConfig.slippageRate); let quoteQuantity; - if (side === "b") { - quoteQuantity = baseQuantity * primaryPrice * (1 + SPREAD) + market.quoteFee; - } else if (side === "s") { - quoteQuantity = (baseQuantity - market.baseFee) * primaryPrice * (1 - SPREAD); + if (side === 'b') { + quoteQuantity = (baseQuantity * primaryPrice * (1 + SPREAD)) + market.quoteFee; + } + else if (side === 's') { + quoteQuantity = (baseQuantity - market.baseFee) * primaryPrice * (1 - SPREAD); } const quotePrice = Number((quoteQuantity / baseQuantity).toPrecision(6)); if (quotePrice < 0) throw new Error("Amount is inadequate to pay fee"); @@ -324,36 +331,37 @@ function genQuote(chainId, marketId, side, baseQuantity) { } function validatePriceFeed(marketId) { - const mmConfig = MM_CONFIG.pairs[marketId]; - const primaryPriceFeedId = mmConfig.priceFeedPrimary; - const secondaryPriceFeedId = mmConfig.priceFeedSecondary; - - // Constant mode checks - const [mode, price] = primaryPriceFeedId.split(":"); - if (mode === "constant") { - if (price > 0) return true; - else throw new Error("No initPrice available"); - } + const mmConfig = MM_CONFIG.pairs[marketId]; + const primaryPriceFeedId = mmConfig.priceFeedPrimary; + const secondaryPriceFeedId = mmConfig.priceFeedSecondary; + + // Constant mode checks + const [mode, price] = primaryPriceFeedId.split(':'); + if (mode === "constant") { + if (price > 0) return true; + else throw new Error("No initPrice available"); + } - // Check if primary price exists - const primaryPrice = PRICE_FEEDS[primaryPriceFeedId]; - if (!primaryPrice) throw new Error("Primary price feed unavailable"); + // Check if primary price exists + const primaryPrice = PRICE_FEEDS[primaryPriceFeedId]; + if (!primaryPrice) throw new Error("Primary price feed unavailable"); - // If there is no secondary price feed, the price auto-validates - if (!secondaryPriceFeedId) return true; - // Check if secondary price exists - const secondaryPrice = PRICE_FEEDS[secondaryPriceFeedId]; - if (!secondaryPrice) throw new Error("Secondary price feed unavailable"); + // If there is no secondary price feed, the price auto-validates + if (!secondaryPriceFeedId) return true; - // If the secondary price feed varies from the primary price feed by more than 1%, assume something is broken - const percentDiff = Math.abs(primaryPrice - secondaryPrice) / primaryPrice; - if (percentDiff > 0.03) { - console.error("Primary and secondary price feeds do not match!"); - throw new Error("Circuit breaker triggered"); - } + // Check if secondary price exists + const secondaryPrice = PRICE_FEEDS[secondaryPriceFeedId]; + if (!secondaryPrice) throw new Error("Secondary price feed unavailable"); - return true; + // If the secondary price feed varies from the primary price feed by more than 1%, assume something is broken + const percentDiff = Math.abs(primaryPrice - secondaryPrice) / primaryPrice; + if (percentDiff > 0.03) { + console.error("Primary and secondary price feeds do not match!"); + throw new Error("Circuit breaker triggered"); + } + + return true; } async function sendFillRequest(orderreceipt, accountId) { @@ -369,564 +377,582 @@ async function sendFillRequest(orderreceipt, accountId) { const quote = genQuote(chainId, marketId, side, baseQuantity); let tokenSell, tokenBuy, sellQuantity, buyQuantity, buySymbol, sellSymbol; if (side === "b") { - tokenSell = market.baseAssetId; - tokenBuy = market.quoteAssetId; - - sellSymbol = market.baseAsset.symbol; - buySymbol = market.quoteAsset.symbol; - // Add 1 bip to to protect against rounding errors - sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals); - buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); + tokenSell = market.baseAssetId; + tokenBuy = market.quoteAssetId; + + sellSymbol = market.baseAsset.symbol; + buySymbol = market.quoteAsset.symbol; + // Add 1 bip to to protect against rounding errors + sellQuantity = (baseQuantity * 1.0001).toFixed(market.baseAsset.decimals); + buyQuantity = (quote.quoteQuantity * 0.9999).toFixed(market.quoteAsset.decimals); } else if (side === "s") { - tokenSell = market.quoteAssetId; - tokenBuy = market.baseAssetId; - - sellSymbol = market.quoteAsset.symbol; - buySymbol = market.baseAsset.symbol; - // Add 1 bip to to protect against rounding errors - sellQuantity = (quote.quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals); - buyQuantity = (baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); - } - const sellQuantityParsed = syncProvider.tokenSet.parseToken(tokenSell, sellQuantity); + tokenSell = market.quoteAssetId; + tokenBuy = market.baseAssetId; + + sellSymbol = market.quoteAsset.symbol; + buySymbol = market.baseAsset.symbol; + // Add 1 bip to to protect against rounding errors + sellQuantity = (quote.quoteQuantity * 1.0001).toFixed(market.quoteAsset.decimals); + buyQuantity = (baseQuantity * 0.9999).toFixed(market.baseAsset.decimals); + } + const sellQuantityParsed = syncProvider.tokenSet.parseToken( + tokenSell, + sellQuantity + ); const sellQuantityPacked = zksync.utils.closestPackableTransactionAmount(sellQuantityParsed); const tokenRatio = {}; tokenRatio[tokenBuy] = buyQuantity; tokenRatio[tokenSell] = sellQuantity; - const oneMinExpiry = ((Date.now() / 1000) | 0) + 60; + const oneMinExpiry = (Date.now() / 1000 | 0) + 60; const orderDetails = { - tokenSell, - tokenBuy, - amount: sellQuantityPacked, - ratio: zksync.utils.tokenRatio(tokenRatio), - validUntil: oneMinExpiry, - }; + tokenSell, + tokenBuy, + amount: sellQuantityPacked, + ratio: zksync.utils.tokenRatio(tokenRatio), + validUntil: oneMinExpiry + } const fillOrder = await WALLETS[accountId].syncWallet.signOrder(orderDetails); // Set wallet flag - WALLETS[accountId]["ORDER_BROADCASTING"] = true; + WALLETS[accountId]['ORDER_BROADCASTING'] = true; // ORDER_BROADCASTING should not take longer as 5 sec - setTimeout(function () { - WALLETS[accountId]["ORDER_BROADCASTING"] = false; + setTimeout(function() { + WALLETS[accountId]['ORDER_BROADCASTING'] = false; }, 5000); const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] }; sendMessage(JSON.stringify(resp)); - rememberOrder(chainId, marketId, orderId, quote.quotePrice, sellSymbol, sellQuantity, buySymbol, buyQuantity); + rememberOrder(chainId, + marketId, + orderId, + quote.quotePrice, + sellSymbol, + sellQuantity, + buySymbol, + buyQuantity + ); } async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) { - // Nonce check - const nonce = swapOffer.nonce; - const userNonce = NONCES[swapOffer.accountId]; - if (nonce <= userNonce) { - const orderCommitMsg = { - op: "orderstatusupdate", - args: [[[chainId, orderId, "r", null, "Order failed userNonce check."]]], - }; - sendMessage(JSON.stringify(orderCommitMsg)); - return; - } - // select token to match user's fee token - let feeToken; - if (FEE_TOKEN) { - feeToken = FEE_TOKEN; - } else { - feeToken = FEE_TOKEN_LIST.includes(swapOffer.tokenSell) ? swapOffer.tokenSell : "ETH"; - } - - const randInt = (Math.random() * 1000).toFixed(0); - console.time("syncswap" + randInt); - const swap = await wallet["syncWallet"].syncSwap({ - orders: [swapOffer, fillOrder], - feeToken: feeToken, - nonce: fillOrder.nonce, - }); - const txHash = swap.txHash.split(":")[1]; - const txHashMsg = { - op: "orderstatusupdate", - args: [[[chainId, orderId, "b", txHash]]], - }; - sendMessage(JSON.stringify(txHashMsg)); - console.timeEnd("syncswap" + randInt); - - console.time("receipt" + randInt); - let receipt, - success = false; - try { - receipt = await swap.awaitReceipt(); - if (receipt.success) { - success = true; - NONCES[swapOffer.accountId] = swapOffer.nonce; - } - } catch (e) { - receipt = null; - success = false; - } - console.timeEnd("receipt" + randInt); - console.log("Swap broadcast result", { swap, receipt }); - - let newStatus, error; - if (success) { - afterFill(chainId, orderId, wallet); - newStatus = "f"; - error = null; - } else { - newStatus = "r"; - error = swap.error.toString(); - } + // Nonce check + const nonce = swapOffer.nonce; + const userNonce = NONCES[swapOffer.accountId]; + if (nonce <= userNonce) { + const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'r',null,"Order failed userNonce check."]]]} + sendMessage(JSON.stringify(orderCommitMsg)); + return; + } + // select token to match user's fee token + let feeToken; + if (FEE_TOKEN) { + feeToken = FEE_TOKEN + } else { + feeToken = (FEE_TOKEN_LIST.includes(swapOffer.tokenSell)) + ? swapOffer.tokenSell + : 'ETH' + } + + const randInt = (Math.random()*1000).toFixed(0); + console.time('syncswap' + randInt); + const swap = await wallet['syncWallet'].syncSwap({ + orders: [swapOffer, fillOrder], + feeToken: feeToken, + nonce: fillOrder.nonce + }); + const txHash = swap.txHash.split(":")[1]; + const txHashMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,'b',txHash]]]} + sendMessage(JSON.stringify(txHashMsg)); + console.timeEnd('syncswap' + randInt); - const orderCommitMsg = { - op: "orderstatusupdate", - args: [[[chainId, orderId, newStatus, txHash, error]]], - }; - sendMessage(JSON.stringify(orderCommitMsg)); + console.time('receipt' + randInt); + let receipt, success = false; + try { + receipt = await swap.awaitReceipt(); + if (receipt.success) { + success = true; + NONCES[swapOffer.accountId] = swapOffer.nonce; + } + } catch (e) { + receipt = null; + success = false; + } + console.timeEnd('receipt' + randInt); + console.log("Swap broadcast result", {swap, receipt}); + + let newStatus, error; + if(success) { + afterFill(chainId, orderId, wallet); + newStatus = 'f'; + error = null; + } else { + newStatus = 'r'; + error = swap.error.toString(); + } + + const orderCommitMsg = {op:"orderstatusupdate", args:[[[chainId,orderId,newStatus,txHash,error]]]} + sendMessage(JSON.stringify(orderCommitMsg)); } async function fillOpenOrders() { - for (let orderId in OPEN_ORDERS) { - const order = OPEN_ORDERS[orderId]; - const fillable = isOrderFillable(order); - if (fillable.fillable) { - sendFillRequest(order, fillable.walletId); - delete OPEN_ORDERS[orderId]; - } else if (!["sending order already", "badprice"].includes(fillable.reason)) { - delete OPEN_ORDERS[orderId]; + for (let orderId in OPEN_ORDERS) { + const order = OPEN_ORDERS[orderId]; + const fillable = isOrderFillable(order); + if (fillable.fillable) { + sendFillRequest(order, fillable.walletId); + delete OPEN_ORDERS[orderId]; + }else if (![ + "sending order already", + "badprice" + ].includes(fillable.reason)) { + delete OPEN_ORDERS[orderId]; + } } - } } async function setupPriceFeeds() { - const cryptowatch = [], - chainlink = [], - uniswapV3 = []; - for (let market in MM_CONFIG.pairs) { - const pairConfig = MM_CONFIG.pairs[market]; - if (!pairConfig.active) { - continue; - } - // This is needed to make the price feed backwards compatalbe with old constant mode: - // "DYDX-USDC": { - // "mode": "constant", - // "initPrice": 20, - if (pairConfig.mode == "constant") { - const initPrice = pairConfig.initPrice; - pairConfig["priceFeedPrimary"] = "constant:" + initPrice.toString(); - } - const primaryPriceFeed = pairConfig.priceFeedPrimary; - const secondaryPriceFeed = pairConfig.priceFeedSecondary; - - // parse keys to lower case to match later PRICE_FEED keys - if (primaryPriceFeed) { - MM_CONFIG.pairs[market].priceFeedPrimary = primaryPriceFeed.toLowerCase(); - } - if (secondaryPriceFeed) { - MM_CONFIG.pairs[market].priceFeedSecondary = secondaryPriceFeed.toLowerCase(); - } - [primaryPriceFeed, secondaryPriceFeed].forEach((priceFeed) => { - if (!priceFeed) { - return; - } - const [provider, id] = priceFeed.split(":"); - switch (provider.toLowerCase()) { - case "cryptowatch": - if (!cryptowatch.includes(id)) { - cryptowatch.push(id); - } - break; - case "chainlink": - if (!chainlink.includes(id)) { - chainlink.push(id); - } - break; - case "uniswapv3": - if (!uniswapV3.includes(id)) { - uniswapV3.push(id); + const cryptowatch = [], chainlink = [], uniswapV3 = []; + for (let market in MM_CONFIG.pairs) { + const pairConfig = MM_CONFIG.pairs[market]; + if(!pairConfig.active) { continue; } + // This is needed to make the price feed backwards compatalbe with old constant mode: + // "DYDX-USDC": { + // "mode": "constant", + // "initPrice": 20, + if(pairConfig.mode == "constant") { + const initPrice = pairConfig.initPrice; + pairConfig['priceFeedPrimary'] = "constant:" + initPrice.toString(); + } + const primaryPriceFeed = pairConfig.priceFeedPrimary; + const secondaryPriceFeed = pairConfig.priceFeedSecondary; + + // parse keys to lower case to match later PRICE_FEED keys + if (primaryPriceFeed) { + MM_CONFIG.pairs[market].priceFeedPrimary = primaryPriceFeed.toLowerCase(); + } + if (secondaryPriceFeed) { + MM_CONFIG.pairs[market].priceFeedSecondary = secondaryPriceFeed.toLowerCase(); + } + [primaryPriceFeed, secondaryPriceFeed].forEach(priceFeed => { + if(!priceFeed) { return; } + const [provider, id] = priceFeed.split(':'); + switch(provider.toLowerCase()) { + case 'cryptowatch': + if(!cryptowatch.includes(id)) { cryptowatch.push(id); } + break; + case 'chainlink': + if(!chainlink.includes(id)) { chainlink.push(id); } + break; + case 'uniswapv3': + if(!uniswapV3.includes(id)) { uniswapV3.push(id); } + break; + case 'constant': + PRICE_FEEDS['constant:'+id] = parseFloat(id); + break; + default: + throw new Error("Price feed provider "+provider+" is not available.") + break; } - break; - case "constant": - PRICE_FEEDS["constant:" + id] = parseFloat(id); - break; - default: - throw new Error("Price feed provider " + provider + " is not available."); - break; - } - }); + }); } - if (chainlink.length > 0) await chainlinkSetup(chainlink); - if (cryptowatch.length > 0) await cryptowatchWsSetup(cryptowatch); - if (uniswapV3.length > 0) await uniswapV3Setup(uniswapV3); + if(chainlink.length > 0) await chainlinkSetup(chainlink); + if(cryptowatch.length > 0) await cryptowatchWsSetup(cryptowatch); + if(uniswapV3.length > 0) await uniswapV3Setup(uniswapV3); console.log(PRICE_FEEDS); } async function cryptowatchWsSetup(cryptowatchMarketIds) { - // Set initial prices - const cryptowatchApiKey = process.env.CRYPTOWATCH_API_KEY || MM_CONFIG.cryptowatchApiKey; - const cryptowatchMarkets = await fetch("https://api.cryptowat.ch/markets?apikey=" + cryptowatchApiKey).then((r) => r.json()); - const cryptowatchMarketPrices = await fetch("https://api.cryptowat.ch/markets/prices?apikey=" + cryptowatchApiKey).then((r) => r.json()); - for (let i in cryptowatchMarketIds) { - const cryptowatchMarketId = cryptowatchMarketIds[i]; - try { - const cryptowatchMarket = cryptowatchMarkets.result.find((row) => row.id == cryptowatchMarketId); - const exchange = cryptowatchMarket.exchange; - const pair = cryptowatchMarket.pair; - const key = `market:${exchange}:${pair}`; - PRICE_FEEDS["cryptowatch:" + cryptowatchMarketIds[i]] = cryptowatchMarketPrices.result[key]; - } catch (e) { - console.error("Could not set price feed for cryptowatch:" + cryptowatchMarketId); + // Set initial prices + const cryptowatchApiKey = process.env.CRYPTOWATCH_API_KEY || MM_CONFIG.cryptowatchApiKey; + const cryptowatchMarkets = await fetch("https://api.cryptowat.ch/markets?apikey=" + cryptowatchApiKey).then(r => r.json()); + const cryptowatchMarketPrices = await fetch("https://api.cryptowat.ch/markets/prices?apikey=" + cryptowatchApiKey).then(r => r.json()); + for (let i in cryptowatchMarketIds) { + const cryptowatchMarketId = cryptowatchMarketIds[i]; + try { + const cryptowatchMarket = cryptowatchMarkets.result.find(row => row.id == cryptowatchMarketId); + const exchange = cryptowatchMarket.exchange; + const pair = cryptowatchMarket.pair; + const key = `market:${exchange}:${pair}`; + PRICE_FEEDS['cryptowatch:'+cryptowatchMarketIds[i]] = cryptowatchMarketPrices.result[key]; + } catch (e) { + console.error("Could not set price feed for cryptowatch:" + cryptowatchMarketId); + } } - } - const subscriptionMsg = { - subscribe: { - subscriptions: [], - }, - }; - for (let i in cryptowatchMarketIds) { - const cryptowatchMarketId = cryptowatchMarketIds[i]; + const subscriptionMsg = { + "subscribe": { + "subscriptions": [] + } + } + for (let i in cryptowatchMarketIds) { + const cryptowatchMarketId = cryptowatchMarketIds[i]; - // first get initial price info + // first get initial price info - subscriptionMsg.subscribe.subscriptions.push({ - streamSubscription: { - resource: `markets:${cryptowatchMarketId}:book:spread`, - }, - }); - } - let cryptowatch_ws = new WebSocket("wss://stream.cryptowat.ch/connect?apikey=" + cryptowatchApiKey); - cryptowatch_ws.on("open", onopen); - cryptowatch_ws.on("message", onmessage); - cryptowatch_ws.on("close", onclose); - cryptowatch_ws.on("error", console.error); - - function onopen() { - cryptowatch_ws.send(JSON.stringify(subscriptionMsg)); - } - function onmessage(data) { - const msg = JSON.parse(data); - if (!msg.marketUpdate) return; - - const marketId = "cryptowatch:" + msg.marketUpdate.market.marketId; - let ask = msg.marketUpdate.orderBookSpreadUpdate.ask.priceStr; - let bid = msg.marketUpdate.orderBookSpreadUpdate.bid.priceStr; - let price = ask / 2 + bid / 2; - PRICE_FEEDS[marketId] = price; - } - function onclose() { - setTimeout(cryptowatchWsSetup, 5000, cryptowatchMarketIds); - } + subscriptionMsg.subscribe.subscriptions.push({ + "streamSubscription": { + "resource": `markets:${cryptowatchMarketId}:book:spread` + } + }) + } + let cryptowatch_ws = new WebSocket("wss://stream.cryptowat.ch/connect?apikey=" + cryptowatchApiKey); + cryptowatch_ws.on('open', onopen); + cryptowatch_ws.on('message', onmessage); + cryptowatch_ws.on('close', onclose); + cryptowatch_ws.on('error', console.error); + + function onopen() { + cryptowatch_ws.send(JSON.stringify(subscriptionMsg)); + } + function onmessage (data) { + const msg = JSON.parse(data); + if (!msg.marketUpdate) return; + + const marketId = "cryptowatch:" + msg.marketUpdate.market.marketId; + let ask = msg.marketUpdate.orderBookSpreadUpdate.ask.priceStr; + let bid = msg.marketUpdate.orderBookSpreadUpdate.bid.priceStr; + let price = ask / 2 + bid / 2; + PRICE_FEEDS[marketId] = price; + } + function onclose () { + setTimeout(cryptowatchWsSetup, 5000, cryptowatchMarketIds); + } } async function chainlinkSetup(chainlinkMarketAddress) { - const results = chainlinkMarketAddress.map(async (address) => { - try { - const aggregatorV3InterfaceABI = JSON.parse(fs.readFileSync("ABIs/chainlinkV3InterfaceABI.abi")); - const provider = new ethers.Contract(address, aggregatorV3InterfaceABI, ethersProvider); - const decimals = await provider.decimals(); - const key = "chainlink:" + address; - CHAINLINK_PROVIDERS[key] = [provider, decimals]; - - // get inital price - const response = await provider.latestRoundData(); - PRICE_FEEDS[key] = parseFloat(response.answer) / 10 ** decimals; - } catch (e) { - throw new Error("Error while setting up chainlink for " + address + ", Error: " + e); - } - }); - await Promise.all(results); - setInterval(chainlinkUpdate, 30000); + const results = chainlinkMarketAddress.map(async (address) => { + try { + const aggregatorV3InterfaceABI = JSON.parse(fs.readFileSync('ABIs/chainlinkV3InterfaceABI.abi')); + const provider = new ethers.Contract(address, aggregatorV3InterfaceABI, ethersProvider); + const decimals = await provider.decimals(); + const key = 'chainlink:' + address; + CHAINLINK_PROVIDERS[key] = [provider, decimals]; + + // get inital price + const response = await provider.latestRoundData(); + PRICE_FEEDS[key] = parseFloat(response.answer) / 10**decimals; + } catch (e) { + throw new Error ("Error while setting up chainlink for "+address+", Error: "+e); + } + }); + await Promise.all(results); + setInterval(chainlinkUpdate, 30000); } async function chainlinkUpdate() { - try { - await Promise.all( - Object.keys(CHAINLINK_PROVIDERS).map(async (key) => { - const [provider, decimals] = CHAINLINK_PROVIDERS[key]; - const response = await provider.latestRoundData(); - PRICE_FEEDS[key] = parseFloat(response.answer) / 10 ** decimals; - }) - ); - chainlink_error_counter = 0; - } catch (err) { - chainlink_error_counter += 1; - console.log(`Failed to update chainlink, retry: ${err.message}`); - if (chainlink_error_counter > 4) { - throw new Error("Failed to update chainlink since 150 seconds!"); + try { + await Promise.all(Object.keys(CHAINLINK_PROVIDERS).map(async (key) => { + const [provider, decimals] = CHAINLINK_PROVIDERS[key]; + const response = await provider.latestRoundData(); + PRICE_FEEDS[key] = parseFloat(response.answer) / 10**decimals; + })); + chainlink_error_counter = 0; + } catch (err) { + chainlink_error_counter += 1; + console.log(`Failed to update chainlink, retry: ${err.message}`); + if(chainlink_error_counter > 4) { + throw new Error ("Failed to update chainlink since 150 seconds!") + } } - } } async function uniswapV3Setup(uniswapV3Address) { - const results = uniswapV3Address.map(async (address) => { - try { - const IUniswapV3PoolABI = JSON.parse(fs.readFileSync("ABIs/IUniswapV3Pool.abi")); - const ERC20ABI = JSON.parse(fs.readFileSync("ABIs/ERC20.abi")); - - const provider = new ethers.Contract(address, IUniswapV3PoolABI, ethersProvider); - - let [slot0, addressToken0, addressToken1] = await Promise.all([provider.slot0(), provider.token0(), provider.token1()]); - - const tokenProvier0 = new ethers.Contract(addressToken0, ERC20ABI, ethersProvider); - const tokenProvier1 = new ethers.Contract(addressToken1, ERC20ABI, ethersProvider); - - let [decimals0, decimals1] = await Promise.all([tokenProvier0.decimals(), tokenProvier1.decimals()]); - - const key = "uniswapv3:" + address; - const decimalsRatio = 10 ** decimals0 / 10 ** decimals1; - UNISWAP_V3_PROVIDERS[key] = [provider, decimalsRatio]; - - // get inital price - const price = (slot0.sqrtPriceX96 * slot0.sqrtPriceX96 * decimalsRatio) / 2 ** 192; - PRICE_FEEDS[key] = price; - } catch (e) { - throw new Error("Error while setting up uniswapV3 for " + address + ", Error: " + e); - } - }); - await Promise.all(results); - setInterval(uniswapV3Update, 30000); + const results = uniswapV3Address.map(async (address) => { + try { + const IUniswapV3PoolABI = JSON.parse(fs.readFileSync('ABIs/IUniswapV3Pool.abi')); + const ERC20ABI = JSON.parse(fs.readFileSync('ABIs/ERC20.abi')); + + const provider = new ethers.Contract(address, IUniswapV3PoolABI, ethersProvider); + + let [ + slot0, + addressToken0, + addressToken1 + ] = await Promise.all ([ + provider.slot0(), + provider.token0(), + provider.token1() + ]); + + const tokenProvier0 = new ethers.Contract(addressToken0, ERC20ABI, ethersProvider); + const tokenProvier1 = new ethers.Contract(addressToken1, ERC20ABI, ethersProvider); + + let [ + decimals0, + decimals1 + ] = await Promise.all ([ + tokenProvier0.decimals(), + tokenProvier1.decimals() + ]); + + const key = 'uniswapv3:' + address; + const decimalsRatio = (10**decimals0 / 10**decimals1); + UNISWAP_V3_PROVIDERS[key] = [provider, decimalsRatio]; + + // get inital price + const price = (slot0.sqrtPriceX96*slot0.sqrtPriceX96*decimalsRatio) / (2**192); + PRICE_FEEDS[key] = price; + } catch (e) { + throw new Error ("Error while setting up uniswapV3 for "+address+", Error: "+e); + } + }); + await Promise.all(results); + setInterval(uniswapV3Update, 30000); } async function uniswapV3Update() { - try { - await Promise.all( - Object.keys(UNISWAP_V3_PROVIDERS).map(async (key) => { - const [provider, decimalsRatio] = UNISWAP_V3_PROVIDERS[key]; - const slot0 = await provider.slot0(); - PRICE_FEEDS[key] = (slot0.sqrtPriceX96 * slot0.sqrtPriceX96 * decimalsRatio) / 2 ** 192; - }) - ); - // reset error counter if successful - uniswap_error_counter = 0; - } catch (err) { - uniswap_error_counter += 1; - console.log(`Failed to update uniswap, retry: ${err.message}`); - console.log(err.message); - if (uniswap_error_counter > 4) { - throw new Error("Failed to update uniswap since 150 seconds!"); - } - } -} - -function indicateLiquidity(pairs = MM_CONFIG.pairs) { - for (const marketId in pairs) { - const mmConfig = pairs[marketId]; - if (!mmConfig || !mmConfig.active) continue; - try { - validatePriceFeed(marketId); - } catch (e) { - console.error("Can not indicateLiquidity (" + marketId + ") because: " + e); - continue; + await Promise.all(Object.keys(UNISWAP_V3_PROVIDERS).map(async (key) => { + const [provider, decimalsRatio] = UNISWAP_V3_PROVIDERS[key]; + const slot0 = await provider.slot0(); + PRICE_FEEDS[key] = (slot0.sqrtPriceX96*slot0.sqrtPriceX96*decimalsRatio) / (2**192); + })); + // reset error counter if successful + uniswap_error_counter = 0; + } catch (err) { + uniswap_error_counter += 1; + console.log(`Failed to update uniswap, retry: ${err.message}`); + console.log(err.message); + if(uniswap_error_counter > 4) { + throw new Error ("Failed to update uniswap since 150 seconds!") + } } +} - const marketInfo = MARKETS[marketId]; - if (!marketInfo) continue; - - const midPrice = mmConfig.invert ? 1 / PRICE_FEEDS[mmConfig.priceFeedPrimary] : PRICE_FEEDS[mmConfig.priceFeedPrimary]; - if (!midPrice) continue; +function indicateLiquidity (pairs = MM_CONFIG.pairs) { + for(const marketId in pairs) { + const mmConfig = pairs[marketId]; + if(!mmConfig || !mmConfig.active) continue; - const expires = ((Date.now() / 1000) | 0) + 10; // 10s expiry - const side = mmConfig.side || "d"; + try { + validatePriceFeed(marketId); + } catch(e) { + console.error("Can not indicateLiquidity ("+marketId+") because: " + e); + continue; + } - let maxBaseBalance = 0, - maxQuoteBalance = 0; - Object.keys(WALLETS).forEach((accountId) => { - const walletBase = WALLETS[accountId]["account_state"].committed.balances[marketInfo.baseAsset.symbol]; - const walletQuote = WALLETS[accountId]["account_state"].committed.balances[marketInfo.quoteAsset.symbol]; - if (Number(walletBase) > maxBaseBalance) { - maxBaseBalance = walletBase; - } - if (Number(walletQuote) > maxQuoteBalance) { - maxQuoteBalance = walletQuote; - } - }); - const baseBalance = maxBaseBalance / 10 ** marketInfo.baseAsset.decimals; - const quoteBalance = maxQuoteBalance / 10 ** marketInfo.quoteAsset.decimals; - const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); - const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize); - - // dont do splits if under 1000 USD - const usdBaseBalance = baseBalance * marketInfo.baseAsset.usdPrice; - const usdQuoteBalance = quoteBalance * marketInfo.quoteAsset.usdPrice; - let buySplits = usdQuoteBalance && usdQuoteBalance < 1000 ? 1 : mmConfig.numOrdersIndicated || 4; - let sellSplits = usdBaseBalance && usdBaseBalance < 1000 ? 1 : mmConfig.numOrdersIndicated || 4; - - if (usdQuoteBalance && usdQuoteBalance < 10 * buySplits) buySplits = Math.floor(usdQuoteBalance / 10); - if (usdBaseBalance && usdBaseBalance < 10 * sellSplits) sellSplits = Math.floor(usdBaseBalance / 10); - - const liquidity = []; - for (let i = 1; i <= buySplits; i++) { - const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i) / buySplits); - if (["b", "d"].includes(side)) { - liquidity.push(["b", buyPrice, maxBuySize / buySplits, expires]); - } - } - for (let i = 1; i <= sellSplits; i++) { - const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i) / sellSplits); - if (["s", "d"].includes(side)) { - liquidity.push(["s", sellPrice, maxSellSize / sellSplits, expires]); + const marketInfo = MARKETS[marketId]; + if (!marketInfo) continue; + + const midPrice = (mmConfig.invert) + ? (1 / PRICE_FEEDS[mmConfig.priceFeedPrimary]) + : PRICE_FEEDS[mmConfig.priceFeedPrimary]; + if (!midPrice) continue; + + const expires = (Date.now() / 1000 | 0) + 10; // 10s expiry + const side = mmConfig.side || 'd'; + + let maxBaseBalance = 0, maxQuoteBalance = 0; + Object.keys(WALLETS).forEach(accountId => { + const walletBase = WALLETS[accountId]['account_state'].committed.balances[marketInfo.baseAsset.symbol]; + const walletQuote = WALLETS[accountId]['account_state'].committed.balances[marketInfo.quoteAsset.symbol]; + if (Number(walletBase) > maxBaseBalance) { + maxBaseBalance = walletBase; + } + if (Number(walletQuote) > maxQuoteBalance) { + maxQuoteBalance = walletQuote; + } + }); + const baseBalance = maxBaseBalance / 10**marketInfo.baseAsset.decimals; + const quoteBalance = maxQuoteBalance / 10**marketInfo.quoteAsset.decimals; + const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); + const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize); + + // dont do splits if under 1000 USD + const usdBaseBalance = baseBalance * marketInfo.baseAsset.usdPrice; + const usdQuoteBalance = quoteBalance * marketInfo.quoteAsset.usdPrice; + let buySplits = (usdQuoteBalance && usdQuoteBalance < 1000) ? 1 : (mmConfig.numOrdersIndicated || 4); + let sellSplits = (usdBaseBalance && usdBaseBalance < 1000) ? 1 : (mmConfig.numOrdersIndicated || 4); + + if (usdQuoteBalance && usdQuoteBalance < (10 * buySplits)) buySplits = Math.floor(usdQuoteBalance / 10) + if (usdBaseBalance && usdBaseBalance < (10 * sellSplits)) sellSplits = Math.floor(usdBaseBalance / 10) + + const liquidity = []; + for (let i=1; i <= buySplits; i++) { + const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i/buySplits)); + if ((['b','d']).includes(side)) { + liquidity.push(["b", buyPrice, maxBuySize / buySplits, expires]); + } + } + for (let i=1; i <= sellSplits; i++) { + const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i/sellSplits)); + if ((['s','d']).includes(side)) { + liquidity.push(["s", sellPrice, maxSellSize / sellSplits, expires]); + } } + + const msg = { op: "indicateliq2", args: [CHAIN_ID, marketId, liquidity] }; + try { + sendMessage(JSON.stringify(msg)); + } catch (e) { + console.error("Could not send liquidity"); + console.error(e); + } } +} - const msg = { - op: "indicateliq2", - args: [CHAIN_ID, marketId, liquidity], - }; +function cancelLiquidity (chainId, marketId) { + const msg = { op: "indicateliq2", args: [chainId, marketId, []] }; try { - sendMessage(JSON.stringify(msg)); + sendMessage(JSON.stringify(msg)); } catch (e) { - console.error("Could not send liquidity"); - console.error(e); + console.error("Could not send liquidity"); + console.error(e); } - } -} - -function cancelLiquidity(chainId, marketId) { - const msg = { op: "indicateliq2", args: [chainId, marketId, []] }; - try { - sendMessage(JSON.stringify(msg)); - } catch (e) { - console.error("Could not send liquidity"); - console.error(e); - } } async function afterFill(chainId, orderId, wallet) { - const order = PAST_ORDER_LIST[orderId]; - if (!order) { - return; - } - const marketId = order.marketId; - const mmConfig = MM_CONFIG.pairs[marketId]; - if (!mmConfig) { - return; - } + const order = PAST_ORDER_LIST[orderId]; + if(!order) { return; } + const marketId = order.marketId; + const mmConfig = MM_CONFIG.pairs[marketId]; + if(!mmConfig) { return; } + + // update account state from order + const account_state = wallet['account_state'].committed.balances; + const buyTokenParsed = syncProvider.tokenSet.parseToken ( + order.buySymbol, + order.buyQuantity + ); + const sellTokenParsed = syncProvider.tokenSet.parseToken ( + order.sellSymbol, + order.sellQuantity + ); + const oldBuyBalance = account_state[order.buySymbol] ? account_state[order.buySymbol] : '0'; + const oldSellBalance = account_state[order.sellSymbol] ? account_state[order.sellSymbol] : '0'; + const oldBuyTokenParsed = ethers.BigNumber.from(oldBuyBalance); + const oldSellTokenParsed = ethers.BigNumber.from(oldSellBalance); + account_state[order.buySymbol] = (oldBuyTokenParsed.add(buyTokenParsed)).toString(); + account_state[order.sellSymbol] = (oldSellTokenParsed.sub(sellTokenParsed)).toString(); + + const indicateMarket = {}; + indicateMarket[marketId] = mmConfig; + if(mmConfig.delayAfterFill) { + let delayAfterFillMinSize + if( + !Array.isArray(mmConfig.delayAfterFill) || + !mmConfig.delayAfterFill[1] + ) { + delayAfterFillMinSize = 0; + } else { + delayAfterFillMinSize = mmConfig.delayAfterFill[1] + } - // update account state from order - const account_state = wallet["account_state"].committed.balances; - const buyTokenParsed = syncProvider.tokenSet.parseToken(order.buySymbol, order.buyQuantity); - const sellTokenParsed = syncProvider.tokenSet.parseToken(order.sellSymbol, order.sellQuantity); - const oldBuyBalance = account_state[order.buySymbol] ? account_state[order.buySymbol] : "0"; - const oldSellBalance = account_state[order.sellSymbol] ? account_state[order.sellSymbol] : "0"; - const oldBuyTokenParsed = ethers.BigNumber.from(oldBuyBalance); - const oldSellTokenParsed = ethers.BigNumber.from(oldSellBalance); - account_state[order.buySymbol] = oldBuyTokenParsed.add(buyTokenParsed).toString(); - account_state[order.sellSymbol] = oldSellTokenParsed.sub(sellTokenParsed).toString(); - - const indicateMarket = {}; - indicateMarket[marketId] = mmConfig; - if (mmConfig.delayAfterFill) { - let delayAfterFillMinSize; - if (!Array.isArray(mmConfig.delayAfterFill) || !mmConfig.delayAfterFill[1]) { - delayAfterFillMinSize = 0; - } else { - delayAfterFillMinSize = mmConfig.delayAfterFill[1]; - } - - if (order.baseQuantity > delayAfterFillMinSize) { - // no array -> old config - // or array and buyQuantity over minSize - mmConfig.active = false; - cancelLiquidity(chainId, marketId); - console.log(`Set ${marketId} passive for ${mmConfig.delayAfterFill} seconds.`); - setTimeout(() => { - mmConfig.active = true; - console.log(`Set ${marketId} active.`); - indicateLiquidity(indicateMarket); - }, mmConfig.delayAfterFill * 1000); + if(order.baseQuantity > delayAfterFillMinSize) { + // no array -> old config + // or array and buyQuantity over minSize + mmConfig.active = false; + cancelLiquidity (chainId, marketId); + console.log(`Set ${marketId} passive for ${mmConfig.delayAfterFill} seconds.`); + setTimeout(() => { + mmConfig.active = true; + console.log(`Set ${marketId} active.`); + indicateLiquidity(indicateMarket); + }, mmConfig.delayAfterFill * 1000); + } } - } - // increaseSpreadAfterFill size might not be set - const increaseSpreadAfterFillMinSize = mmConfig.increaseSpreadAfterFill?.[2] ? mmConfig.increaseSpreadAfterFill[2] : 0; - if (mmConfig.increaseSpreadAfterFill && order.baseQuantity > increaseSpreadAfterFillMinSize) { - const [spread, time] = mmConfig.increaseSpreadAfterFill; - mmConfig.minSpread = mmConfig.minSpread + spread; - console.log(`Changed ${marketId} minSpread by ${spread}.`); - indicateLiquidity(indicateMarket); - setTimeout(() => { - mmConfig.minSpread = mmConfig.minSpread - spread; - console.log(`Changed ${marketId} minSpread by -${spread}.`); - indicateLiquidity(indicateMarket); - }, time * 1000); - } + // increaseSpreadAfterFill size might not be set + const increaseSpreadAfterFillMinSize = (mmConfig.increaseSpreadAfterFill?.[2]) + ? mmConfig.increaseSpreadAfterFill[2] + : 0 + if( + mmConfig.increaseSpreadAfterFill && + order.baseQuantity > increaseSpreadAfterFillMinSize + + ) { + const [spread, time] = mmConfig.increaseSpreadAfterFill; + mmConfig.minSpread = mmConfig.minSpread + spread; + console.log(`Changed ${marketId} minSpread by ${spread}.`); + indicateLiquidity(indicateMarket); + setTimeout(() => { + mmConfig.minSpread = mmConfig.minSpread - spread; + console.log(`Changed ${marketId} minSpread by -${spread}.`); + indicateLiquidity(indicateMarket); + }, time * 1000); + } - // changeSizeAfterFill size might not be set - const changeSizeAfterFillMinSize = mmConfig.changeSizeAfterFill?.[2] ? mmConfig.changeSizeAfterFill[2] : 0; - if (mmConfig.changeSizeAfterFill && order.baseQuantity > changeSizeAfterFillMinSize) { - const [size, time] = mmConfig.changeSizeAfterFill; - mmConfig.maxSize = mmConfig.maxSize + size; - console.log(`Changed ${marketId} maxSize by ${size}.`); - indicateLiquidity(indicateMarket); - setTimeout(() => { - mmConfig.maxSize = mmConfig.maxSize - size; - console.log(`Changed ${marketId} maxSize by ${size * -1}.`); - indicateLiquidity(indicateMarket); - }, time * 1000); - } + // changeSizeAfterFill size might not be set + const changeSizeAfterFillMinSize = (mmConfig.changeSizeAfterFill?.[2]) + ? mmConfig.changeSizeAfterFill[2] + : 0 + if( + mmConfig.changeSizeAfterFill && + order.baseQuantity > changeSizeAfterFillMinSize + ) { + const [size, time] = mmConfig.changeSizeAfterFill; + mmConfig.maxSize = mmConfig.maxSize + size; + console.log(`Changed ${marketId} maxSize by ${size}.`); + indicateLiquidity(indicateMarket); + setTimeout(() => { + mmConfig.maxSize = mmConfig.maxSize - size; + console.log(`Changed ${marketId} maxSize by ${(size* (-1))}.`); + indicateLiquidity(indicateMarket); + }, time * 1000); + } } function rememberOrder(chainId, marketId, orderId, price, sellSymbol, sellQuantity, buySymbol, buyQuantity) { - const timestamp = Date.now() / 1000; - for (const [key, value] of Object.entries(PAST_ORDER_LIST)) { - if (value["expiry"] < timestamp) { - delete PAST_ORDER_LIST[key]; + const timestamp = Date.now() / 1000; + for (const [key, value] of Object.entries(PAST_ORDER_LIST)) { + if (value['expiry'] < timestamp) { + delete PAST_ORDER_LIST[key]; + } } - } - const [baseSymbol, quoteSymbol] = marketId.split("-"); - let baseQuantity, quoteQuantity; - if (sellSymbol === baseSymbol) { - baseQuantity = sellQuantity; - quoteQuantity = buyQuantity; - } else { - baseQuantity = buyQuantity; - quoteQuantity = sellQuantity; - } + const [baseSymbol, quoteSymbol] = marketId.split('-') + let baseQuantity, quoteQuantity; + if(sellSymbol === baseSymbol) { + baseQuantity = sellQuantity; + quoteQuantity = buyQuantity; + } else { + baseQuantity = buyQuantity; + quoteQuantity = sellQuantity; + } - const expiry = timestamp + 900; - PAST_ORDER_LIST[orderId] = { - chainId: chainId, - marketId: marketId, - price: price, - baseQuantity: baseQuantity, - quoteQuantity: quoteQuantity, - sellSymbol: sellSymbol, - sellQuantity: sellQuantity, - buySymbol: buySymbol, - buyQuantity: buyQuantity, - expiry: expiry, - }; + const expiry = timestamp + 900; + PAST_ORDER_LIST[orderId] = { + 'chainId': chainId, + 'marketId': marketId, + 'price': price, + 'baseQuantity': baseQuantity, + 'quoteQuantity': quoteQuantity, + 'sellSymbol': sellSymbol, + 'sellQuantity': sellQuantity, + 'buySymbol': buySymbol, + 'buyQuantity': buyQuantity, + 'expiry':expiry + }; } async function updateAccountState() { - try { - Object.keys(WALLETS).forEach((accountId) => { - WALLETS[accountId]["syncWallet"].getAccountState().then((state) => { - WALLETS[accountId]["account_state"] = state; - }); - }); - } catch (err) { - // pass - } + try { + Object.keys(WALLETS).forEach(accountId => { + (WALLETS[accountId]['syncWallet']).getAccountState().then((state) => { + WALLETS[accountId]['account_state'] = state; + }) + }); + } catch(err) { + // pass + } } async function sendMessage(msg) { - let success = false; - do { - success = await waitForSocketConnection(msg); - } while (!success); + let success = false; + do { + success = await waitForSocketConnection(msg); + } while (!success); } async function waitForSocketConnection(msg) { - if (zigzagws.readyState === 1) { - if (msg != null) { - zigzagws.send(msg); - return true; - } - } else { - await new Promise(resolve => setTimeout(resolve, 10)); - return false; - } + if (zigzagws.readyState === 1) { + if (msg != null) { + zigzagws.send(msg); + return true; + } + } else { + await new Promise(resolve => setTimeout(resolve, 10)); + return false; + } } \ No newline at end of file From d55e39b189dddf0c787c2f2cc2ad47134f927ebd Mon Sep 17 00:00:00 2001 From: TrooperCrypto Date: Wed, 31 May 2023 21:48:56 +0200 Subject: [PATCH 160/160] add ethereumRPC for custom rpc's --- README.md | 11 +++++------ config.json.EXAMPLE | 1 + marketmaker.js | 24 +++++++++++++++++++++++- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 687b702..f069923 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ node marketmaker.js ## Configuration Via Environment Variables -It is __recommended__ to use environment variables to set your private keys. You can set `ETH_PRIVKEY`, `CRYPTOWATCH_API_KEY` and `INFURA` using them. You can set them using `ETH_PRIVKEY=0x____`. For more informations on private keys read [this](https://linuxize.com/post/how-to-set-and-list-environment-variables-in-linux/). +It is __recommended__ to use environment variables to set your private keys. You can set `ETH_PRIVKEY`, `CRYPTOWATCH_API_KEY` and `RPC` using them. You can set them using `ETH_PRIVKEY=0x____`. For more informations on private keys read [this](https://linuxize.com/post/how-to-set-and-list-environment-variables-in-linux/). If your hosting service requires you to pass in configs via environment variables you can compress `config.json`: @@ -139,10 +139,9 @@ Example: ``` ###### Chainlink -With chainlink you have access to price oracles via blockchain. The requests are read-calls to a smart contract. The public ethers provider might be too slow for a higher number of pairs or at times of high demand. Therefore, it might be needed to have access to an Infura account (100000 Requests/Day for free). You can get an endpoint for your market maker (like https://mainnet.infura.io/v3/...), You can add this with the `infuraUrl` field in `config.json`, like this: +With chainlink you have access to price oracles via blockchain. The requests are read-calls to a smart contract. The public ethers provider might be too slow for a higher number of pairs or at times of high demand. Therefore, it might be needed to have access to an RPC provider account like quicknode, alchemy, ... (some offer free calls). You can get an endpoint for your market maker (like "https://eth.llamarpc.com"), You can add this with the `ethereumRPC` field in `config.json`, like this: ``` - -"infuraUrl": "https://mainnet.infura.io/v3/xxxxxxxx", +"ethereumRPC": "https://eth.llamarpc.com", "pairs": { "ETH-USDC": { "zigzagChainId": 1, @@ -161,9 +160,9 @@ You can get the available market contracts [here.](https://docs.chain.link/docs/ ``` ###### UniswapV3 -With uniswapV3 you have access to price feed's via blockchain. The requests are read-calls to a smart contract. The public ethers provider might be too slow for a higher number of pairs or at times of high demand. Therefore, it might be needed to have access to an Infura account (100000 Requests/Day for free). You can get an endpoint for your market maker (like https://mainnet.infura.io/v3/...), You can add this with the `infuraUrl` field in `config.json`, like this: +With uniswapV3 you have access to price feed's via blockchain. The requests are read-calls to a smart contract. The public ethers provider might be too slow for a higher number of pairs or at times of high demand. Therefore, it might be needed to have access to an RPC provider account like quicknode, alchemy, ... (some offer free calls). You can get an endpoint for your market maker (like "https://eth.llamarpc.com"), You can add this with the `ethereumRPC` field in `config.json`, like this: ``` -"infuraUrl": "https://mainnet.infura.io/v3/xxxxxxxx", +"ethereumRPC": "https://eth.llamarpc.com", "pairs": { "ETH-USDC": { "zigzagChainId": 1, diff --git a/config.json.EXAMPLE b/config.json.EXAMPLE index 9c76b35..57dff94 100644 --- a/config.json.EXAMPLE +++ b/config.json.EXAMPLE @@ -6,6 +6,7 @@ ], "zigzagChainId": 1, "zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com", + "ethereumRPC": "https://eth.llamarpc.com", "pairs": { "ETH-USDC": { "priceFeedPrimary": "cryptowatch:6631", diff --git a/marketmaker.js b/marketmaker.js index cda9afa..e3e92f0 100644 --- a/marketmaker.js +++ b/marketmaker.js @@ -47,14 +47,36 @@ console.log("ACTIVE PAIRS", activePairs); const CHAIN_ID = parseInt(MM_CONFIG.zigzagChainId); const ETH_NETWORK = (CHAIN_ID === 1) ? "mainnet" : "goerli"; const infureKey = (process.env.infura || MM_CONFIG.infura); +const ethereumRPC = process.env.infuraUrl || process.env.RPC || process.env.ethereumRPC || MM_CONFIG.infuraUrl || MM_CONFIG.RPC || MM_CONFIG.ethereumRPC; let ethersProvider = null; if (infureKey) { ethersProvider = new ethers.providers.InfuraProvider( "mainnet", infureKey ); +} else if (ethereumRPC) { + ethersProvider = new ethers.providers.JsonRpcProvider(ethereumRPC); } else { - ethersProvider = new ethers.getDefaultProvider("mainnet"); + console.error(` + You did not provider an rpc url with "ethereumRPC" inside your config + or with ETHEREUM_RPC in the environment variables. + Please add a custom one. There are some providers with free plans. + + Using a public provider, there is no guarantee that it is stable, for a stable market-maker create a custom RPC.` + ) + ethersProvider = new ethers.providers.JsonRpcProvider('https://eth.llamarpc.com'); +} + +const resProvider = await Promise.race([ + ethersProvider.ready, + new Promise((_, reject) => { + setTimeout(() => reject(new Error('Request timed out')), 10_000) + }) +]) + +// check if provider is working +if (Number(resProvider.chainId) !== 1) { + throw new Error(`Cant connect provider, use "ethereumRPC" in the config to add an ethereumRPC`) } // Start price feeds