mirror of
https://github.com/ZigZagExchange/zksync-lite-market-maker.git
synced 2025-12-17 07:04:23 +01:00
44
README.md
44
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
|
||||
@@ -99,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:<address>", like this:
|
||||
```
|
||||
@@ -119,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:<address>", like this:
|
||||
```
|
||||
|
||||
@@ -16,6 +16,8 @@ const MARKETS = {};
|
||||
const CHAINLINK_PROVIDERS = {};
|
||||
const UNISWAP_V3_PROVIDERS = {};
|
||||
const PAST_ORDER_LIST = {};
|
||||
const FEE_TOKEN_LIST = [];
|
||||
let FEE_TOKEN = null;
|
||||
|
||||
let uniswap_error_counter = 0;
|
||||
let chainlink_error_counter = 0;
|
||||
@@ -29,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];
|
||||
@@ -152,8 +157,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)) {
|
||||
@@ -195,6 +199,19 @@ 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_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
|
||||
default:
|
||||
break
|
||||
@@ -269,7 +286,6 @@ function isOrderFillable(order) {
|
||||
else if (side == 'b' && price < quote.quotePrice) {
|
||||
return { fillable: false, reason: "badprice" };
|
||||
}
|
||||
|
||||
return { fillable: true, reason: null, walletId: goodWalletIds[0]};
|
||||
}
|
||||
|
||||
@@ -352,6 +368,7 @@ async function sendFillRequest(orderreceipt, accountId) {
|
||||
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
|
||||
@@ -360,6 +377,7 @@ async function sendFillRequest(orderreceipt, accountId) {
|
||||
} 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
|
||||
@@ -414,11 +432,21 @@ async function broadcastFill(chainId, orderId, swapOffer, fillOrder, wallet) {
|
||||
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: "ETH",
|
||||
feeToken: feeToken,
|
||||
nonce: fillOrder.nonce
|
||||
});
|
||||
const txHash = swap.txHash.split(":")[1];
|
||||
@@ -486,6 +514,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 +675,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];
|
||||
|
||||
@@ -710,18 +746,30 @@ 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));
|
||||
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 / splits, expires]);
|
||||
}
|
||||
if ((['s','d']).includes(side)) {
|
||||
liquidity.push(["s", sellPrice, maxSellSize / 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)) {
|
||||
liquidity.push(["s", sellPrice, maxSellSize / sellSplits, expires]);
|
||||
}
|
||||
}
|
||||
|
||||
const msg = { op: "indicateliq2", args: [CHAIN_ID, marketId, liquidity] };
|
||||
try {
|
||||
zigzagws.send(JSON.stringify(msg));
|
||||
@@ -758,11 +806,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;
|
||||
|
||||
1611
package-lock.json
generated
1611
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user