Merge pull request #70 from ZigZagExchange/dev

update master to dev
This commit is contained in:
Trooper
2022-05-10 23:42:39 +02:00
committed by GitHub
3 changed files with 1698 additions and 43 deletions

View File

@@ -32,6 +32,23 @@ node marketmaker.js
## Settings ## 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 #### Mainnet zkSync
- "zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com" - "zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com"
- "zigzagChainId": 1 - "zigzagChainId": 1
@@ -99,12 +116,14 @@ Example:
###### 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). 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", "infuraUrl": "https://mainnet.infura.io/v3/xxxxxxxx",
"pairs": {
"ETH-USDC": {
"zigzagChainId": 1, "zigzagChainId": 1,
"zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com", "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:<address>", like this: You can get the available market contracts [here.](https://docs.chain.link/docs/ethereum-addresses/)Add those to you pair config as "chainlink:<address>", like this:
``` ```
@@ -119,12 +138,13 @@ You can get the available market contracts [here.](https://docs.chain.link/docs/
###### UniswapV3 ###### 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 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",
"infuraUrl": "https://mainnet.infura.io/v3/xxxxxxxx", "pairs": {
"ETH-USDC": {
"zigzagChainId": 1, "zigzagChainId": 1,
"zigzagWsUrl": "wss://zigzag-exchange.herokuapp.com", "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:<address>", like this: 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:<address>", like this:
``` ```

View File

@@ -16,6 +16,8 @@ const MARKETS = {};
const CHAINLINK_PROVIDERS = {}; const CHAINLINK_PROVIDERS = {};
const UNISWAP_V3_PROVIDERS = {}; const UNISWAP_V3_PROVIDERS = {};
const PAST_ORDER_LIST = {}; const PAST_ORDER_LIST = {};
const FEE_TOKEN_LIST = [];
let FEE_TOKEN = null;
let uniswap_error_counter = 0; let uniswap_error_counter = 0;
let chainlink_error_counter = 0; let chainlink_error_counter = 0;
@@ -29,6 +31,9 @@ else {
const mmConfigFile = fs.readFileSync("config.json", "utf8"); const mmConfigFile = fs.readFileSync("config.json", "utf8");
MM_CONFIG = JSON.parse(mmConfigFile); MM_CONFIG = JSON.parse(mmConfigFile);
} }
if (MM_CONFIG.feeToken) {
FEE_TOKEN = MM_CONFIG.feeToken;
}
let activePairs = []; let activePairs = [];
for (let marketId in MM_CONFIG.pairs) { for (let marketId in MM_CONFIG.pairs) {
const pair = MM_CONFIG.pairs[marketId]; const pair = MM_CONFIG.pairs[marketId];
@@ -152,8 +157,7 @@ async function handleMessage(json) {
console.log(fillable); console.log(fillable);
if (fillable.fillable) { if (fillable.fillable) {
sendFillRequest(order, fillable.walletId); sendFillRequest(order, fillable.walletId);
} } else if ([
else if ([
"sending order already", "sending order already",
"badprice" "badprice"
].includes(fillable.reason)) { ].includes(fillable.reason)) {
@@ -195,6 +199,19 @@ async function handleMessage(json) {
const newBaseFee = MARKETS[marketId].baseFee; const newBaseFee = MARKETS[marketId].baseFee;
const newQuoteFee = MARKETS[marketId].quoteFee; const newQuoteFee = MARKETS[marketId].quoteFee;
console.log(`marketinfo ${marketId} - update baseFee ${oldBaseFee} -> ${newBaseFee}, quoteFee ${oldQuoteFee} -> ${newQuoteFee}`); 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);
}
break break
default: default:
break break
@@ -269,7 +286,6 @@ function isOrderFillable(order) {
else if (side == 'b' && price < quote.quotePrice) { else if (side == 'b' && price < quote.quotePrice) {
return { fillable: false, reason: "badprice" }; return { fillable: false, reason: "badprice" };
} }
return { fillable: true, reason: null, walletId: goodWalletIds[0]}; return { fillable: true, reason: null, walletId: goodWalletIds[0]};
} }
@@ -352,6 +368,7 @@ async function sendFillRequest(orderreceipt, accountId) {
if (side === "b") { if (side === "b") {
tokenSell = market.baseAssetId; tokenSell = market.baseAssetId;
tokenBuy = market.quoteAssetId; tokenBuy = market.quoteAssetId;
sellSymbol = market.baseAsset.symbol; sellSymbol = market.baseAsset.symbol;
buySymbol = market.quoteAsset.symbol; buySymbol = market.quoteAsset.symbol;
// Add 1 bip to to protect against rounding errors // Add 1 bip to to protect against rounding errors
@@ -360,6 +377,7 @@ async function sendFillRequest(orderreceipt, accountId) {
} else if (side === "s") { } else if (side === "s") {
tokenSell = market.quoteAssetId; tokenSell = market.quoteAssetId;
tokenBuy = market.baseAssetId; tokenBuy = market.baseAssetId;
sellSymbol = market.quoteAsset.symbol; sellSymbol = market.quoteAsset.symbol;
buySymbol = market.baseAsset.symbol; buySymbol = market.baseAsset.symbol;
// Add 1 bip to to protect against rounding errors // Add 1 bip to to protect against rounding errors
@@ -414,11 +432,21 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) {
zigzagws.send(JSON.stringify(orderCommitMsg)); zigzagws.send(JSON.stringify(orderCommitMsg));
return; 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); const randInt = (Math.random()*1000).toFixed(0);
console.time('syncswap' + randInt); console.time('syncswap' + randInt);
const swap = await wallet['syncWallet'].syncSwap({ const swap = await wallet['syncWallet'].syncSwap({
orders: [swapOffer, fillOrder], orders: [swapOffer, fillOrder],
feeToken: "ETH", feeToken: feeToken,
nonce: fillOrder.nonce nonce: fillOrder.nonce
}); });
const txHash = swap.txHash.split(":")[1]; const txHash = swap.txHash.split(":")[1];
@@ -486,6 +514,14 @@ async function setupPriceFeeds() {
} }
const primaryPriceFeed = pairConfig.priceFeedPrimary; const primaryPriceFeed = pairConfig.priceFeedPrimary;
const secondaryPriceFeed = pairConfig.priceFeedSecondary; 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 => { [primaryPriceFeed, secondaryPriceFeed].forEach(priceFeed => {
if(!priceFeed) { return; } if(!priceFeed) { return; }
const [provider, id] = priceFeed.split(':'); const [provider, id] = priceFeed.split(':');
@@ -639,7 +675,7 @@ async function uniswapV3Setup(uniswapV3Address) {
tokenProvier1.decimals() tokenProvier1.decimals()
]); ]);
const key = 'uniswapV3:' + address; const key = 'uniswapv3:' + address;
const decimalsRatio = (10**decimals0 / 10**decimals1); const decimalsRatio = (10**decimals0 / 10**decimals1);
UNISWAP_V3_PROVIDERS[key] = [provider, decimalsRatio]; UNISWAP_V3_PROVIDERS[key] = [provider, decimalsRatio];
@@ -710,18 +746,30 @@ function indicateLiquidity (pairs = MM_CONFIG.pairs) {
const maxSellSize = Math.min(baseBalance, mmConfig.maxSize); const maxSellSize = Math.min(baseBalance, mmConfig.maxSize);
const maxBuySize = Math.min(quoteBalance / midPrice, 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 = []; 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/splits)); const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i/buySplits));
const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i/splits));
if ((['b','d']).includes(side)) { if ((['b','d']).includes(side)) {
liquidity.push(["b", buyPrice, maxBuySize / splits, expires]); 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)) { 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] }; const msg = { op: "indicateliq2", args: [CHAIN_ID, marketId, liquidity] };
try { try {
zigzagws.send(JSON.stringify(msg)); zigzagws.send(JSON.stringify(msg));
@@ -759,10 +807,12 @@ async function afterFill(chainId, orderId, wallet) {
order.sellSymbol, order.sellSymbol,
order.sellQuantity order.sellQuantity
); );
const oldbuyTokenParsed = ethers.BigNumber.from(account_state[order.buySymbol]); const oldBuyBalance = account_state[order.buySymbol] ? account_state[order.buySymbol] : '0';
const oldsellTokenParsed = ethers.BigNumber.from(account_state[order.sellSymbol]); const oldSellBalance = account_state[order.sellSymbol] ? account_state[order.sellSymbol] : '0';
account_state[order.buySymbol] = (oldbuyTokenParsed.add(buyTokenParsed)).toString(); const oldBuyTokenParsed = ethers.BigNumber.from(oldBuyBalance);
account_state[order.sellSymbol] = (oldsellTokenParsed.sub(sellTokenParsed)).toString(); const oldSellTokenParsed = ethers.BigNumber.from(oldSellBalance);
account_state[order.buySymbol] = (oldBuyTokenParsed.add(buyTokenParsed)).toString();
account_state[order.sellSymbol] = (oldSellTokenParsed.sub(sellTokenParsed)).toString();
const indicateMarket = {}; const indicateMarket = {};
indicateMarket[marketId] = mmConfig; indicateMarket[marketId] = mmConfig;

1611
package-lock.json generated

File diff suppressed because it is too large Load Diff