Files
zksync-lite-market-maker/marketmaker.js
2021-11-20 22:26:39 -08:00

246 lines
7.2 KiB
JavaScript

import WebSocket from 'ws';
import * as zksync from "zksync";
import ethers from 'ethers';
import dotenv from 'dotenv';
dotenv.config();
let syncWallet, ethersProvider, syncProvider, ethWallet, noncesSinceLastCommitment,
lastPongReceived, pingServerInterval, fillOrdersInterval;
ethersProvider = ethers.getDefaultProvider(process.env.ETH_NETWORK);
try {
syncProvider = await zksync.getDefaultProvider(process.env.ETH_NETWORK);
ethWallet = new ethers.Wallet(process.env.ETH_PRIVKEY);
syncWallet = await zksync.Wallet.fromEthSigner(ethWallet, syncProvider);
noncesSinceLastCommitment = 0;
lastPongReceived = Date.now();
} catch (e) {
throw new Error("Could not connect to zksync API");
}
const spotPrices = {};
const openOrders = {};
const CHAIN_ID = process.env.CHAIN_ID;
const MARKET_PAIRS = ["ETH-USDT", "ETH-USDC", "USDC-USDT"];
const CURRENCY_INFO = {
"ETH": {
decimals: 18,
chain: {
1: { tokenId: 0 },
1000: { tokenId: 0 },
}
},
"USDC": {
decimals: 6,
chain: {
1: { tokenId: 2 },
1000: { tokenId: 2 },
}
},
"USDT": {
decimals: 6,
chain: {
1: { tokenId: 4 },
1000: { tokenId: 1 },
}
},
}
let zigzagws = new WebSocket(process.env.ZIGZAG_WS_URL);
zigzagws.on('open', onWsOpen);
function onWsOpen() {
zigzagws.on('message', handleMessage);
zigzagws.on('close', onWsClose);
pingServerInterval = setInterval(pingServer, 5000);
fillOrdersInterval = setInterval(fillOpenOrders, 10000);
MARKET_PAIRS.forEach(market => {
const msg = {op:"subscribemarket", args:[CHAIN_ID, market]};
zigzagws.send(JSON.stringify(msg));
});
}
function onWsClose () {
console.log("Websocket closed. Restarting");
setTimeout(() => {
clearInterval(pingServerInterval)
clearInterval(fillOrdersInterval)
zigzagws = new WebSocket(process.env.ZIGZAG_WS_URL);
zigzagws.on('open', onWsOpen);
zigzagws.on('error', onWsClose);
}, 5000);
}
function pingServer() {
const msg = {op:"ping"};
zigzagws.send(JSON.stringify(msg));
if (Date.now() - lastPongReceived > 20000) {
console.log("Greater than 20s since last pong");
zigzagws.close();
}
}
async function handleMessage(json) {
console.log(json.toString());
const msg = JSON.parse(json);
switch(msg.op) {
case 'pong':
lastPongReceived = Date.now();
break
case 'lastprice':
const prices = msg.args[0];
prices.forEach(row => {
const market = row[0];
const price = row[1];
spotPrices[market] = price;
});
break
case 'openorders':
const orders = msg.args[0];
orders.forEach(order => {
const orderid = order[1];
if (isOrderFillable(order)) {
sendfillrequest(order);
}
else {
openOrders[orderid] = order;
}
});
break
case "userordermatch":
const chainid = msg.args[0];
const orderid = msg.args[1];
const result = await broadcastfill(chainid, orderid, msg.args[2], msg.args[3]);
break
case "cancelorderack":
const canceled_ids = msg.args[0];
canceled_ids.forEach(orderid => {
delete openOrders[orderid]
});
break
default:
break
}
}
function isOrderFillable(order) {
const chainid = order[0];
const market = order[2];
const baseCurrency = market.split("-")[0];
if (chainid != CHAIN_ID || !MARKET_PAIRS.includes(market)) {
return false;
}
const spotPrice = spotPrices[market];
if (!spotPrice) return false;
let botAsk, botBid;
if (baseCurrency === "ETH") {
botAsk = spotPrice * 1.0007;
botBid = spotPrice * 0.9993;
}
else if (baseCurrency === "USDC") {
botAsk = spotPrice * 1.0003;
botBid = spotPrice * 0.9997;
}
const side = order[3];
const price = order[4];
if (side == 's' && price < botBid) {
return true;
}
else if (side == 'b' && price > botAsk) {
return true;
}
return false;
}
async function sendfillrequest(orderreceipt) {
const chainId = orderreceipt[0];
const orderId = orderreceipt[1];
const market = orderreceipt[2];
const baseCurrency = market.split("-")[0];
const quoteCurrency = market.split("-")[1];
const side = orderreceipt[3];
let price = orderreceipt[4];
const baseQuantity = orderreceipt[5];
const quoteQuantity = orderreceipt[6];
let tokenSell, tokenBuy, sellQuantity;
if (side === "b") {
price = price * 0.9997; // Add a margin of error to price
tokenSell = baseCurrency;
tokenBuy = quoteCurrency;
sellQuantity = baseQuantity.toString();
} else if (side === "s") {
price = price * 1.0003; // Add a margin of error to price
tokenSell = quoteCurrency;
tokenBuy = baseCurrency;
sellQuantity = (quoteQuantity * 1.0001).toFixed(6); // Add a margin of error to sellQuantity
}
const tokenRatio = {};
tokenRatio[baseCurrency] = 1;
tokenRatio[quoteCurrency] = parseFloat(price.toFixed(6));
console.log(`${side} ${baseQuantity} ${baseCurrency} @ ${price}`);
const orderDetails = {
tokenSell,
tokenBuy,
amount: syncProvider.tokenSet.parseToken(
tokenSell,
sellQuantity
),
ratio: zksync.utils.tokenRatio(tokenRatio),
}
if (noncesSinceLastCommitment > 0) {
let nonce = await syncWallet.getNonce();
nonce += noncesSinceLastCommitment;
orderDetails.nonce = nonce;
}
const fillOrder = await syncWallet.getOrder(orderDetails);
noncesSinceLastCommitment++;
const resp = { op: "fillrequest", args: [chainId, orderId, fillOrder] };
zigzagws.send(JSON.stringify(resp));
}
async function broadcastfill(chainid, orderid, swapOffer, fillOrder) {
const randint = (Math.random()*1000).toFixed(0);
console.time('syncswap' + randint);
const swap = await 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;
try {
receipt = await swap.awaitReceipt();
if (receipt.success) success = true;
} catch (e) {
receipt = null;
success = false;
}
console.timeEnd('receipt' + randint);
noncesSinceLastCommitment = 0;
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));
}
async function fillOpenOrders() {
for (let orderid in openOrders) {
const order = openOrders[orderid];
if (isOrderFillable(order)) {
sendfillrequest(order);
delete openOrders[orderid];
}
}
}