mirror of
https://github.com/ZigZagExchange/zksync-lite-market-maker.git
synced 2025-12-18 07:34:21 +01:00
32
README.md
32
README.md
@@ -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:
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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
1611
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user