From ba1a874ebb22c34992d838687f2809a45fd14133 Mon Sep 17 00:00:00 2001 From: Maximilian Schneider Date: Fri, 11 Jun 2021 15:53:17 -0400 Subject: [PATCH] 5 tokens (#6) * Get token decimals by index * Update default mango group * Update readme * Add account filtering behind feature flag * prettier; changed the default loading for targets * updated version number and fixed updating of min coll ratio account Co-authored-by: Riordan Panayides Co-authored-by: dd --- README.md | 21 +- package.json | 4 +- src/index.ts | 6 +- src/liquidate.ts | 11 +- src/partial.ts | 555 +++++++++++++++++++++++++++++------------------ src/test.ts | 4 +- yarn.lock | 133 +++++++++--- 7 files changed, 461 insertions(+), 273 deletions(-) diff --git a/README.md b/README.md index cdf89bc..3627a4a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ### Prerequisites To run the liquidator you will need: * A Solana account with some SOL deposited to cover transaction fees -* Token accounts for each currency in the Mango Group (e.g. BTC, ETH, USDT) +* Token accounts for each currency in the Mango Group (e.g. BTC, ETH, SOL, SRM, USDT) * Roughly equal deposits for each token. You will need base currencies to liquidate shorts, and quote currency to liquidate longs. * Serum Dex OpenOrders accounts associated with your account. This is required for balance wallets functionality. * The easiest way to set these up is by placing an order on Serum Dex for each currency pair then immediately cancelling it. @@ -15,16 +15,19 @@ export CLUSTER="mainnet-beta" export CLUSTER_URL="https://solana-api.projectserum.com" export KEYPAIR=~/.config/solana/id.json export NODE_ENV=production -export TARGETS="0.1 2" -export GROUP_NAME="BTC_ETH_USDT" +export TARGETS="0.1 2.0 100.0 500.0" +export GROUP_NAME="BTC_ETH_SOL_SRM_USDT" export CHECK_INTERVAL="1000.0" +export FILTER_ACCOUNTS=true ``` -TARGETS represents the BTC and ETH amounts the partial liquidator should try to maintain +TARGETS represents the amounts of each token the partial liquidator should try to maintain in the liquidator's wallet. Any excess of that amount in the wallet will be market sold on Serum DEX. CHECK_INTERVAL is the amount of milliseconds to wait between querying all margin accounts +FILTER_ACCOUNTS uses a more efficient method of querying marginAccounts by only returning accounts with open borrows. This is only supported on Mango Groups released after 'BTC_ETH_SOL_SRM_USDT'. Disabled by default. + ### Run ``` yarn install @@ -32,16 +35,6 @@ source .env yarn partialLiquidate ``` -## Setup Full Liquidator [DEPRECATED] -Make sure to edit the .env file to look something like this: -``` -export CLUSTER="mainnet-beta" -export CLUSTER_URL="https://solana-api.projectserum.com" -export KEYPAIR=~/.config/solana/id.json -export NODE_ENV=production -export GROUP_NAME="BTC_ETH_USDT" -``` - ### Run ``` yarn install diff --git a/package.json b/package.json index f82b5cf..f5070dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mango/liquidator", - "version": "0.0.1", + "version": "2.0.0", "description": "Library for interacting with the serum dex", "engines": { "node": ">=10" @@ -32,7 +32,7 @@ "typescript": "^4.0.5" }, "dependencies": { - "@blockworks-foundation/mango-client": "^0.1.19", + "@blockworks-foundation/mango-client": "^2.0.0", "@project-serum/serum": "^0.13.20", "@solana/spl-token": "0.0.13", "@solana/web3.js": "^0.90.0", diff --git a/src/index.ts b/src/index.ts index 51dcbec..a89e860 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,7 @@ async function setupMarginAccounts() { const clusterIds = IDS[cluster] const connection = new Connection(IDS.cluster_urls[cluster], 'singleGossip') - const mangoGroupPk = new PublicKey(clusterIds.mango_groups.BTC_ETH_USDC.mango_group_pk); + const mangoGroupPk = new PublicKey(clusterIds.mango_groups['BTC_ETH_USDC'].mango_group_pk); const mangoProgramId = new PublicKey(clusterIds.mango_program_id); const dexProgramId = new PublicKey(clusterIds.dex_program_id) @@ -139,7 +139,7 @@ async function testing() { const dexProgramId = new PublicKey(IDS[cluster].dex_program_id) // Address of the MangoGroup - const mangoGroupPk = new PublicKey(IDS[cluster].mango_groups.BTC_ETH_USDC.mango_group_pk) + const mangoGroupPk = new PublicKey(IDS[cluster].mango_groups['BTC_ETH_USDC'].mango_group_pk) // TODO fetch these automatically @@ -161,4 +161,4 @@ async function testing() { // setupMarginAccounts() // main() -// testing() \ No newline at end of file +// testing() diff --git a/src/liquidate.ts b/src/liquidate.ts index b2bebb2..82e77a5 100644 --- a/src/liquidate.ts +++ b/src/liquidate.ts @@ -69,7 +69,7 @@ async function drainAccount( for (let i = 0; i < NUM_TOKENS - 1; i++) { const marketIndex = netValues[i][0] const market = markets[marketIndex] - const tokenDecimals = tokenToDecimals[marketIndex === 0 ? 'BTC' : 'ETH'] + const tokenDecimals = mangoGroup.getTokenDecimals(marketIndex) const tokenDecimalAdj = Math.pow(10, tokenDecimals) if (netValues[i][1] > 0) { // sell to close @@ -106,7 +106,7 @@ async function drainAccount( async function runLiquidator() { const client = new MangoClient() const cluster = process.env.CLUSTER || 'mainnet-beta' - const group_name = process.env.GROUP_NAME || 'BTC_ETH_USDT' + const group_name = process.env.GROUP_NAME || 'BTC_ETH_SOL_SRM_USDT' const clusterUrl = process.env.CLUSTER_URL || IDS.cluster_urls[cluster] const connection = new Connection(clusterUrl, 'singleGossip') @@ -146,8 +146,11 @@ async function runLiquidator() { while (true) { try { mangoGroup = await client.getMangoGroup(connection, mangoGroupPk) - let [marginAccounts, prices, vaultAccs, liqorAccs] = await Promise.all([ - client.getAllMarginAccounts(connection, programId, mangoGroup), + let marginAccounts = process.env.FILTER_ACCOUNTS ? + await client.getAllMarginAccountsWithBorrows(connection, programId, mangoGroup) : + await client.getAllMarginAccounts(connection, programId, mangoGroup) + + let [prices, vaultAccs, liqorAccs] = await Promise.all([ mangoGroup.getPrices(connection), getMultipleAccounts(connection, mangoGroup.vaults), getMultipleAccounts(connection, tokenWallets), diff --git a/src/partial.ts b/src/partial.ts index 9022de1..6341c52 100644 --- a/src/partial.ts +++ b/src/partial.ts @@ -3,7 +3,8 @@ import { getMultipleAccounts, IDS, MangoClient, - MangoGroup, MarginAccount, + MangoGroup, + MarginAccount, nativeToUi, NUM_MARKETS, NUM_TOKENS, @@ -11,7 +12,13 @@ import { parseTokenAccountData, tokenToDecimals, } from '@blockworks-foundation/mango-client'; -import { Account, Commitment, Connection, PublicKey, Transaction } from '@solana/web3.js'; +import { + Account, + Commitment, + Connection, + PublicKey, + Transaction, +} from '@solana/web3.js'; import { homedir } from 'os'; import fs from 'fs'; import { notify, sleep } from './utils'; @@ -22,7 +29,6 @@ import { } from '@blockworks-foundation/mango-client/lib/instruction'; import BN = require('bn.js'); - /* After a liquidation, the amounts in each wallet become unbalanced Make sure to sell or buy quantities different from the target on base currencies @@ -37,269 +43,353 @@ async function balanceWallets( liqorWallets: PublicKey[], liqorValuesUi: number[], liqorOpenOrdersKeys: PublicKey[], - targets: number[] + targets: number[], ) { - // Retrieve orders from order book by owner - const liqorOrders = await Promise.all(markets.map((m) => - m.loadOrdersForOwner(connection, liqor.publicKey))); + const liqorOrders = await Promise.all( + markets.map((m) => m.loadOrdersForOwner(connection, liqor.publicKey)), + ); // Cancel all - const cancelTransactions: Promise[] = [] + const cancelTransactions: Promise[] = []; for (let i = 0; i < NUM_MARKETS; i++) { for (let order of liqorOrders[i]) { - console.log(`Cancelling liqor order on market ${i} size=${order.size} price=${order.price}`) + console.log( + `Cancelling liqor order on market ${i} size=${order.size} price=${order.price}`, + ); cancelTransactions.push(markets[i].cancelOrder(connection, liqor, order)); } } - await Promise.all(cancelTransactions) + await Promise.all(cancelTransactions); // Load open orders accounts - const liqorOpenOrders = await Promise.all(liqorOpenOrdersKeys.map((pk) => OpenOrders.load(connection, pk, mangoGroup.dexProgramId))) + const liqorOpenOrders = await Promise.all( + liqorOpenOrdersKeys.map((pk) => + OpenOrders.load(connection, pk, mangoGroup.dexProgramId), + ), + ); // Settle all - const settleTransactions: Promise[] = [] + const settleTransactions: Promise[] = []; for (let i = 0; i < NUM_MARKETS; i++) { - const oo = liqorOpenOrders[i] - if (parseFloat(oo.quoteTokenTotal.toString()) > 0 || parseFloat(oo.baseTokenTotal.toString()) > 0) { - console.log(`Settling funds to liqor wallet ${i} quote:${oo.quoteTokenTotal.toString()} base:${oo.baseTokenTotal.toString()}`) - settleTransactions.push(markets[i].settleFunds(connection, liqor, oo, liqorWallets[i], liqorWallets[NUM_TOKENS-1])) + const oo = liqorOpenOrders[i]; + if ( + parseFloat(oo.quoteTokenTotal.toString()) > 0 || + parseFloat(oo.baseTokenTotal.toString()) > 0 + ) { + console.log( + `Settling funds to liqor wallet ${i} quote:${oo.quoteTokenTotal.toString()} base:${oo.baseTokenTotal.toString()}`, + ); + settleTransactions.push( + markets[i].settleFunds( + connection, + liqor, + oo, + liqorWallets[i], + liqorWallets[NUM_TOKENS - 1], + ), + ); } } - await Promise.all(settleTransactions) + await Promise.all(settleTransactions); // Account wallets should instantly update - const liqorWalletAccounts = await getMultipleAccounts(connection, liqorWallets, 'processed' as Commitment) - liqorValuesUi = liqorWalletAccounts.map( - (a, i) => nativeToUi(parseTokenAccountData(a.accountInfo.data).amount, mangoGroup.mintDecimals[i]) - ) + const liqorWalletAccounts = await getMultipleAccounts( + connection, + liqorWallets, + 'processed' as Commitment, + ); + liqorValuesUi = liqorWalletAccounts.map((a, i) => + nativeToUi( + parseTokenAccountData(a.accountInfo.data).amount, + mangoGroup.mintDecimals[i], + ), + ); - const diffs: number[] = [] - const netValues: [number, number][] = [] + const diffs: number[] = []; + const netValues: [number, number][] = []; // Go to each base currency and see if it's above or below target for (let i = 0; i < NUM_TOKENS - 1; i++) { - const diff = liqorValuesUi[i] - targets[i] - diffs.push(diff) - netValues.push([i, diff * prices[i]]) + const diff = liqorValuesUi[i] - targets[i]; + diffs.push(diff); + netValues.push([i, diff * prices[i]]); } // Sort in decreasing order so you sell first then buy - netValues.sort((a, b) => (b[1] - a[1])) + netValues.sort((a, b) => b[1] - a[1]); for (let i = 0; i < NUM_TOKENS - 1; i++) { - const marketIndex = netValues[i][0] - const market = markets[marketIndex] - const tokenDecimals = tokenToDecimals[marketIndex === 0 ? 'BTC' : 'ETH'] // TODO make this mapping allow arbitrary mango groups - const tokenDecimalAdj = Math.pow(10, tokenDecimals) - if (netValues[i][1] > 0) { // sell to close - const price = prices[marketIndex] * 0.95 - const size = Math.floor(diffs[marketIndex] * tokenDecimalAdj) / tokenDecimalAdj // round down the size + const marketIndex = netValues[i][0]; + const market = markets[marketIndex]; + const tokenDecimals = mangoGroup.getTokenDecimals(marketIndex); + const tokenDecimalAdj = Math.pow(10, tokenDecimals); + if (netValues[i][1] > 0) { + // sell to close + const price = prices[marketIndex] * 0.95; + const size = + Math.floor(diffs[marketIndex] * tokenDecimalAdj) / tokenDecimalAdj; // round down the size if (size === 0) { - continue + continue; } - console.log(`Sell to close ${marketIndex} ${size} @ ${price}`) - let txid = await market.placeOrder( - connection, - { - owner: liqor, - payer: liqorWallets[marketIndex], - side: 'sell', - price, - size, - orderType: 'ioc', - openOrdersAddressKey: liqorOpenOrdersKeys[marketIndex], - feeDiscountPubkey: null // TODO find liqor's SRM fee account - } - ) + console.log(`Sell to close ${marketIndex} ${size} @ ${price}`); + let txid = await market.placeOrder(connection, { + owner: liqor, + payer: liqorWallets[marketIndex], + side: 'sell', + price, + size, + orderType: 'ioc', + openOrdersAddressKey: liqorOpenOrdersKeys[marketIndex], + feeDiscountPubkey: null, // TODO find liqor's SRM fee account + }); // TODO add a SettleFunds instruction to this transaction - console.log(`Place order successful: ${txid}; Settling funds`) - await market.settleFunds(connection, liqor, liqorOpenOrders[marketIndex], liqorWallets[marketIndex], liqorWallets[NUM_TOKENS-1]) - - } else if (netValues[i][1] < 0) { // buy to close - const price = prices[marketIndex] * 1.05 // buy at up to 5% higher than oracle price - const size = Math.ceil(-diffs[marketIndex] * tokenDecimalAdj) / tokenDecimalAdj - - console.log(`Buy to close ${marketIndex} ${size} @ ${price}`) - let txid = await market.placeOrder( + console.log(`Place order successful: ${txid}; Settling funds`); + await market.settleFunds( connection, - { - owner: liqor, - payer: liqorWallets[NUM_TOKENS-1], - side: 'buy', - price, - size, - orderType: 'ioc', - openOrdersAddressKey: liqorOpenOrdersKeys[marketIndex], - feeDiscountPubkey: null - } - ) - console.log(`Place order successful: ${txid}; Settling funds`) - await market.settleFunds(connection, liqor, liqorOpenOrders[marketIndex], liqorWallets[marketIndex], liqorWallets[NUM_TOKENS-1]) + liqor, + liqorOpenOrders[marketIndex], + liqorWallets[marketIndex], + liqorWallets[NUM_TOKENS - 1], + ); + } else if (netValues[i][1] < 0) { + // buy to close + const price = prices[marketIndex] * 1.05; // buy at up to 5% higher than oracle price + const size = + Math.ceil(-diffs[marketIndex] * tokenDecimalAdj) / tokenDecimalAdj; + + console.log(`Buy to close ${marketIndex} ${size} @ ${price}`); + let txid = await market.placeOrder(connection, { + owner: liqor, + payer: liqorWallets[NUM_TOKENS - 1], + side: 'buy', + price, + size, + orderType: 'ioc', + openOrdersAddressKey: liqorOpenOrdersKeys[marketIndex], + feeDiscountPubkey: null, + }); + console.log(`Place order successful: ${txid}; Settling funds`); + await market.settleFunds( + connection, + liqor, + liqorOpenOrders[marketIndex], + liqorWallets[marketIndex], + liqorWallets[NUM_TOKENS - 1], + ); } } } async function runPartialLiquidator() { - const client = new MangoClient() - const cluster = process.env.CLUSTER || 'mainnet-beta' - const group_name = process.env.GROUP_NAME || 'BTC_ETH_USDT' - const clusterUrl = process.env.CLUSTER_URL || IDS.cluster_urls[cluster] - const targetsStr = process.env.TARGETS || "0.1 2" - const checkInterval = parseFloat(process.env.CHECK_INTERVAL || "1000.0") - const targets = targetsStr.split(' ').map((s) => parseFloat(s)) - const connection = new Connection(clusterUrl, 'singleGossip') + const client = new MangoClient(); + const cluster = process.env.CLUSTER || 'mainnet-beta'; + const group_name = process.env.GROUP_NAME || 'BTC_ETH_SOL_SRM_USDC'; + const clusterUrl = process.env.CLUSTER_URL || IDS.cluster_urls[cluster]; + const targetsStr = process.env.TARGETS || '0.1 2.0 100.0 1000.0'; + const checkInterval = parseFloat(process.env.CHECK_INTERVAL || '1000.0'); + const targets = targetsStr.split(' ').map((s) => parseFloat(s)); + const connection = new Connection(clusterUrl, 'singleGossip'); // The address of the Mango Program on the blockchain - const programId = new PublicKey(IDS[cluster].mango_program_id) + const programId = new PublicKey(IDS[cluster].mango_program_id); // The address of the serum dex program on the blockchain: https://github.com/project-serum/serum-dex - const dexProgramId = new PublicKey(IDS[cluster].dex_program_id) + const dexProgramId = new PublicKey(IDS[cluster].dex_program_id); // Address of the MangoGroup - const mangoGroupPk = new PublicKey(IDS[cluster].mango_groups[group_name].mango_group_pk) + const mangoGroupPk = new PublicKey( + IDS[cluster].mango_groups[group_name].mango_group_pk, + ); // liquidator's keypair - const keyPairPath = process.env.KEYPAIR || homedir() + '/.config/solana/id.json' - const payer = new Account(JSON.parse(fs.readFileSync(keyPairPath, 'utf-8'))) + const keyPairPath = + process.env.KEYPAIR || homedir() + '/.config/solana/id.json'; + const payer = new Account(JSON.parse(fs.readFileSync(keyPairPath, 'utf-8'))); notify(`partial liquidator launched cluster=${cluster} group=${group_name}`); - let mangoGroup = await client.getMangoGroup(connection, mangoGroupPk) + let mangoGroup = await client.getMangoGroup(connection, mangoGroupPk); - const tokenWallets = (await Promise.all( - mangoGroup.tokens.map( - (mint) => findLargestTokenAccountForOwner(connection, payer.publicKey, mint).then( - (response) => response.publicKey - ) - ) - )) + const tokenWallets = await Promise.all( + mangoGroup.tokens.map((mint) => + findLargestTokenAccountForOwner(connection, payer.publicKey, mint).then( + (response) => response.publicKey, + ), + ), + ); // load all markets - const markets = await Promise.all(mangoGroup.spotMarkets.map( - (pk) => Market.load(connection, pk, {skipPreflight: true, commitment: 'singleGossip'}, dexProgramId) - )) + const markets = await Promise.all( + mangoGroup.spotMarkets.map((pk) => + Market.load( + connection, + pk, + { skipPreflight: true, commitment: 'singleGossip' }, + dexProgramId, + ), + ), + ); // TODO handle failures in any of the steps // Find a way to get all margin accounts without querying fresh--get incremental updates to margin accounts - const liqorOpenOrdersKeys: PublicKey[] = [] + const liqorOpenOrdersKeys: PublicKey[] = []; for (let i = 0; i < NUM_MARKETS; i++) { - let openOrdersAccounts: OpenOrders[] = await markets[i].findOpenOrdersAccountsForOwner(connection, payer.publicKey) - if(openOrdersAccounts.length) { - liqorOpenOrdersKeys.push(openOrdersAccounts[0].publicKey) + let openOrdersAccounts: OpenOrders[] = await markets[ + i + ].findOpenOrdersAccountsForOwner(connection, payer.publicKey); + if (openOrdersAccounts.length) { + liqorOpenOrdersKeys.push(openOrdersAccounts[0].publicKey); } else { - console.log(`No OpenOrders account found for market ${markets[i].publicKey.toBase58()}`) + console.log( + `No OpenOrders account found for market ${markets[ + i + ].publicKey.toBase58()}`, + ); } } - if(liqorOpenOrdersKeys.length != NUM_MARKETS) { - console.log('Warning: Missing OpenOrders accounts. Wallet balancing has been disabled.') + if (liqorOpenOrdersKeys.length != NUM_MARKETS) { + console.log( + 'Warning: Missing OpenOrders accounts. Wallet balancing has been disabled.', + ); } - const cancelLimit = 5 + const cancelLimit = 5; while (true) { try { - mangoGroup = await client.getMangoGroup(connection, mangoGroupPk) + mangoGroup = await client.getMangoGroup(connection, mangoGroupPk); + let marginAccounts = process.env.FILTER_ACCOUNTS + ? await client.getAllMarginAccountsWithBorrows( + connection, + programId, + mangoGroup, + ) + : await client.getAllMarginAccounts(connection, programId, mangoGroup); - let [marginAccounts, prices, vaultAccs, liqorAccs] = await Promise.all([ - client.getAllMarginAccounts(connection, programId, mangoGroup), + let [prices, vaultAccs, liqorAccs] = await Promise.all([ mangoGroup.getPrices(connection), getMultipleAccounts(connection, mangoGroup.vaults), - getMultipleAccounts(connection, tokenWallets, 'processed' as Commitment), - ]) + getMultipleAccounts( + connection, + tokenWallets, + 'processed' as Commitment, + ), + ]); - const vaultValues = vaultAccs.map( - (a, i) => nativeToUi(parseTokenAccountData(a.accountInfo.data).amount, mangoGroup.mintDecimals[i]) - ) + const vaultValues = vaultAccs.map((a, i) => + nativeToUi( + parseTokenAccountData(a.accountInfo.data).amount, + mangoGroup.mintDecimals[i], + ), + ); const liqorTokenValues = liqorAccs.map( - (a) => parseTokenAccount(a.accountInfo.data).amount - ) - const liqorTokenUi = liqorAccs.map( - (a, i) => nativeToUi(parseTokenAccountData(a.accountInfo.data).amount, mangoGroup.mintDecimals[i]) - ) + (a) => parseTokenAccount(a.accountInfo.data).amount, + ); + const liqorTokenUi = liqorAccs.map((a, i) => + nativeToUi( + parseTokenAccountData(a.accountInfo.data).amount, + mangoGroup.mintDecimals[i], + ), + ); - console.log(prices) - console.log(vaultValues) - console.log(liqorTokenUi) + console.log(prices); + console.log(vaultValues); + console.log(liqorTokenUi); // FIXME: added bias to collRatio to allow other liquidators to step in for testing - let coll_bias = 0 + let coll_bias = 0; if (process.env.COLL_BIAS) { - coll_bias = parseFloat(process.env.COLL_BIAS) + coll_bias = parseFloat(process.env.COLL_BIAS); } let maxBorrAcc: MarginAccount | undefined = undefined; let maxBorrVal = 0; let minCollAcc: MarginAccount | undefined = undefined; let minCollVal = 99999; - for (let ma of marginAccounts) { // parallelize this if possible + for (let ma of marginAccounts) { + // parallelize this if possible - let description = '' + let description = ''; try { - const assetsVal = ma.getAssetsVal(mangoGroup, prices) - const liabsVal = ma.getLiabsVal(mangoGroup, prices) + const assetsVal = ma.getAssetsVal(mangoGroup, prices); + const liabsVal = ma.getLiabsVal(mangoGroup, prices); if (liabsVal > maxBorrVal) { - maxBorrVal = liabsVal - maxBorrAcc = ma + maxBorrVal = liabsVal; + maxBorrAcc = ma; } - if (liabsVal < 0.1) { // too small of an account; number precision may cause errors - continue + if (liabsVal < 0.1) { + // too small of an account; number precision may cause errors + continue; + } + let collRatio = assetsVal / liabsVal; + if (collRatio < minCollVal) { + minCollVal = collRatio; + minCollAcc = ma; } - if (!ma.beingLiquidated) { - let collRatio = (assetsVal / liabsVal) - - const deficit = liabsVal * mangoGroup.initCollRatio - assetsVal - if (deficit < 0.1) { // too small of an account; number precision may cause errors - continue - } - - if (collRatio < minCollVal) - { - minCollVal = collRatio - minCollAcc = ma + const deficit = liabsVal * mangoGroup.initCollRatio - assetsVal; + if (deficit < 0.1) { + // too small of an account; number precision may cause errors + continue; } if (collRatio + coll_bias >= mangoGroup.maintCollRatio) { - continue + continue; } - } - description = ma.toPrettyString(mangoGroup, prices) - console.log(`Liquidatable\n${description}\nbeingLiquidated: ${ma.beingLiquidated}`) - notify(`Liquidatable\n${description}\nbeingLiquidated: ${ma.beingLiquidated}`) + description = ma.toPrettyString(mangoGroup, prices); + console.log( + `Liquidatable\n${description}\nbeingLiquidated: ${ma.beingLiquidated}`, + ); + notify( + `Liquidatable\n${description}\nbeingLiquidated: ${ma.beingLiquidated}`, + ); // find the market with the most value in OpenOrdersAccount - let maxMarketIndex = -1 - let maxMarketVal = 0 + let maxMarketIndex = -1; + let maxMarketVal = 0; for (let i = 0; i < NUM_MARKETS; i++) { - const openOrdersAccount = ma.openOrdersAccounts[i] + const openOrdersAccount = ma.openOrdersAccounts[i]; if (openOrdersAccount === undefined) { - continue + continue; } - const marketVal = openOrdersAccount.quoteTokenTotal.toNumber() + openOrdersAccount.baseTokenTotal.toNumber() * prices[i] + const marketVal = + openOrdersAccount.quoteTokenTotal.toNumber() + + openOrdersAccount.baseTokenTotal.toNumber() * prices[i]; if (marketVal > maxMarketVal) { - maxMarketIndex = i - maxMarketVal = marketVal + maxMarketIndex = i; + maxMarketVal = marketVal; } } - const transaction = new Transaction() + + // Cancel orders on the market with the most value in open orders + const transaction = new Transaction(); if (maxMarketIndex !== -1) { // force cancel orders on this particular market - const spotMarket = markets[maxMarketIndex] - const [bids, asks] = await Promise.all([spotMarket.loadBids(connection), spotMarket.loadAsks(connection)]) - const openOrdersAccount = ma.openOrdersAccounts[maxMarketIndex] + const spotMarket = markets[maxMarketIndex]; + const [bids, asks] = await Promise.all([ + spotMarket.loadBids(connection), + spotMarket.loadAsks(connection), + ]); + const openOrdersAccount = ma.openOrdersAccounts[maxMarketIndex]; if (openOrdersAccount === undefined) { - console.log('error state') - continue + console.log('error state'); + continue; } - let numOrders = spotMarket.filterForOpenOrders(bids, asks, [openOrdersAccount]).length + let numOrders = spotMarket.filterForOpenOrders(bids, asks, [ + openOrdersAccount, + ]).length; const dexSigner = await PublicKey.createProgramAddress( [ spotMarket.publicKey.toBuffer(), - spotMarket['_decoded'].vaultSignerNonce.toArrayLike(Buffer, 'le', 8) + spotMarket['_decoded'].vaultSignerNonce.toArrayLike( + Buffer, + 'le', + 8, + ), ], - spotMarket.programId - ) + spotMarket.programId, + ); for (let i = 0; i < 10; i++) { const instruction = makeForceCancelOrdersInstruction( @@ -308,7 +398,7 @@ async function runPartialLiquidator() { payer.publicKey, ma.publicKey, mangoGroup.vaults[maxMarketIndex], - mangoGroup.vaults[NUM_TOKENS-1], + mangoGroup.vaults[NUM_TOKENS - 1], spotMarket.publicKey, spotMarket.bidsAddress, spotMarket.asksAddress, @@ -320,58 +410,74 @@ async function runPartialLiquidator() { spotMarket.programId, ma.openOrders, mangoGroup.oracles, - new BN(cancelLimit) - ) - transaction.add(instruction) - numOrders -= cancelLimit + new BN(cancelLimit), + ); + transaction.add(instruction); + numOrders -= cancelLimit; if (numOrders <= 0) { - break + break; } } } - // Find the market with the highest borrows and lowest deposits - const deposits = ma.getAssets(mangoGroup) - const borrows = ma.getLiabs(mangoGroup) - let minNet = 0 - let minNetIndex = -1 - let maxNet = 0 - let maxNetIndex = NUM_TOKENS-1 + // Find the market with the highest borrows and lowest assets + const deposits = ma.getAssets(mangoGroup); + const borrows = ma.getLiabs(mangoGroup); + let minNet = 0; + let minNetIndex = -1; + let maxNet = 0; + let maxNetIndex = NUM_TOKENS - 1; for (let i = 0; i < NUM_TOKENS; i++) { - const netDeposit = (deposits[i] - borrows[i]) * prices[i] + const netDeposit = (deposits[i] - borrows[i]) * prices[i]; if (netDeposit < minNet) { - minNet = netDeposit - minNetIndex = i + minNet = netDeposit; + minNetIndex = i; } else if (netDeposit > maxNet) { - maxNet = netDeposit - maxNetIndex = i + maxNet = netDeposit; + maxNetIndex = i; } } - transaction.add(makePartialLiquidateInstruction( - programId, - mangoGroup.publicKey, - payer.publicKey, - liqorAccs[minNetIndex].publicKey, - liqorAccs[maxNetIndex].publicKey, - ma.publicKey, - mangoGroup.vaults[minNetIndex], - mangoGroup.vaults[maxNetIndex], - mangoGroup.signerKey, - ma.openOrders, - mangoGroup.oracles, - liqorTokenValues[minNetIndex] - )) + transaction.add( + makePartialLiquidateInstruction( + programId, + mangoGroup.publicKey, + payer.publicKey, + liqorAccs[minNetIndex].publicKey, + liqorAccs[maxNetIndex].publicKey, + ma.publicKey, + mangoGroup.vaults[minNetIndex], + mangoGroup.vaults[maxNetIndex], + mangoGroup.signerKey, + ma.openOrders, + mangoGroup.oracles, + liqorTokenValues[minNetIndex], + ), + ); - await client.sendTransaction(connection, transaction, payer, []) - await sleep(2000) - ma = await client.getMarginAccount(connection, ma.publicKey, dexProgramId) - console.log(`Successful partial liquidation\n${ma.toPrettyString(mangoGroup, prices)}\nbeingLiquidated: ${ma.beingLiquidated}`) - notify(`Successful partial liquidation\n${ma.toPrettyString(mangoGroup, prices)}\nbeingLiquidated: ${ma.beingLiquidated}`) - break // This is so wallets get balanced + await client.sendTransaction(connection, transaction, payer, []); + await sleep(2000); + ma = await client.getMarginAccount( + connection, + ma.publicKey, + dexProgramId, + ); + console.log( + `Successful partial liquidation\n${ma.toPrettyString( + mangoGroup, + prices, + )}\nbeingLiquidated: ${ma.beingLiquidated}`, + ); + notify( + `Successful partial liquidation\n${ma.toPrettyString( + mangoGroup, + prices, + )}\nbeingLiquidated: ${ma.beingLiquidated}`, + ); + break; // This is so wallets get balanced } catch (e) { if (!e.timeout) { - throw e + throw e; } else { notify(`unknown error: ${e}`); console.error(e); @@ -379,28 +485,45 @@ async function runPartialLiquidator() { } } - const maxBorrAccPk = maxBorrAcc ? maxBorrAcc.publicKey.toBase58() : "" - const maxBorrAccCr = maxBorrAcc ? maxBorrAcc.getCollateralRatio(mangoGroup, prices) : 0 - const minCollAccPk = minCollAcc ? minCollAcc.publicKey.toBase58() : "" - const minCollBorrVal = minCollAcc ? minCollAcc.getLiabsVal(mangoGroup, prices) : 0 + const maxBorrAccPk = maxBorrAcc ? maxBorrAcc.publicKey.toBase58() : ''; + const maxBorrAccCr = maxBorrAcc + ? maxBorrAcc.getCollateralRatio(mangoGroup, prices) + : 0; + const minCollAccPk = minCollAcc ? minCollAcc.publicKey.toBase58() : ''; + const minCollBorrVal = minCollAcc + ? minCollAcc.getLiabsVal(mangoGroup, prices) + : 0; - console.log(`Max Borrow Account: ${maxBorrAccPk} | Borrow Val: ${maxBorrVal} | CR: ${maxBorrAccCr}`) - console.log(`Min Coll-Ratio Account: ${minCollAccPk} | Borrow Val: ${minCollBorrVal} | CR: ${minCollVal}`) - if(liqorOpenOrdersKeys.length == NUM_MARKETS) { - await balanceWallets(connection, mangoGroup, prices, markets, payer, tokenWallets, liqorTokenUi, liqorOpenOrdersKeys, targets) + console.log( + `Max Borrow Account: ${maxBorrAccPk} | Borrow Val: ${maxBorrVal} | CR: ${maxBorrAccCr}`, + ); + console.log( + `Min Coll-Ratio Account: ${minCollAccPk} | Borrow Val: ${minCollBorrVal} | CR: ${minCollVal}`, + ); + if (liqorOpenOrdersKeys.length == NUM_MARKETS) { + await balanceWallets( + connection, + mangoGroup, + prices, + markets, + payer, + tokenWallets, + liqorTokenUi, + liqorOpenOrdersKeys, + targets, + ); } else { - console.log('Could not balance wallets due to missing OpenOrders account') + console.log( + 'Could not balance wallets due to missing OpenOrders account', + ); } - } catch (e) { notify(`unknown error: ${e}`); console.error(e); } finally { - await sleep(checkInterval) + await sleep(checkInterval); } } - } -runPartialLiquidator() - +runPartialLiquidator(); diff --git a/src/test.ts b/src/test.ts index ee36610..8c3ad57 100644 --- a/src/test.ts +++ b/src/test.ts @@ -38,7 +38,7 @@ async function genMarginAccounts() { const dexProgramId = new PublicKey(IDS[cluster].dex_program_id) // Address of the MangoGroup - const mangoGroupPk = new PublicKey(IDS[cluster].mango_groups.BTC_ETH_USDC.mango_group_pk) + const mangoGroupPk = new PublicKey(IDS[cluster].mango_groups['BTC_ETH_USDC'].mango_group_pk) const keyPairPath = '/home/dd/.config/solana/id.json' const payer = new Account(JSON.parse(fs.readFileSync(keyPairPath, 'utf-8'))) @@ -362,4 +362,4 @@ async function testAll() { testAll() -// testServer() \ No newline at end of file +// testServer() diff --git a/yarn.lock b/yarn.lock index a683d88..7f6c0fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -267,7 +267,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.12.13" -"@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.3.1": +"@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1": version "7.14.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6" integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA== @@ -310,15 +310,16 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@blockworks-foundation/mango-client@^0.1.19": - version "0.1.19" - resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-0.1.19.tgz#f138712a9a9d3db7beded1e20be1e6ac0ebbdbb0" - integrity sha512-w7oPJU84uYZOUAsZBQ7WoGWLV83sQElOwHkhna58+hxFFBVo9B5siuAwLf8vN+m9n/mf4p+t8PZR9tnpp7+63A== +"@blockworks-foundation/mango-client@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-2.0.0.tgz#2040497f7137d97aebd8ef459a887ddcef4ef9e0" + integrity sha512-lDRjARyXVuBbsohllnrAfkZd2xCY8iOA8xbimAGe9qmYeQIJDqN6SSwd5dcCkIFmihIQrObEym54MvaRAQaWVg== dependencies: + "@project-serum/common" "^0.0.1-beta.3" "@project-serum/serum" "^0.13.20" "@project-serum/sol-wallet-adapter" "^0.1.4" - "@solana/spl-token" "^0.0.13" - "@solana/web3.js" "^0.90.0" + "@solana/spl-token" "0.0.13" + "@solana/web3.js" "^0.95.0" bn.js "^5.1.2" borsh "https://github.com/defactojob/borsh-js#field-mapper" buffer-layout "^1.2.0" @@ -554,6 +555,15 @@ "@nodelib/fs.scandir" "2.1.4" fastq "^1.6.0" +"@project-serum/common@^0.0.1-beta.3": + version "0.0.1-beta.3" + resolved "https://registry.yarnpkg.com/@project-serum/common/-/common-0.0.1-beta.3.tgz#53586eaff9d9fd7e8938b1e12080c935b8b6ad07" + integrity sha512-gnQE/eUydTtto5okCgLWj1M97R9RRPJqnhKklikYI7jP/pnNhDmngSXC/dmfzED2GXSJEIKNIlxVw1k+E2Aw3w== + dependencies: + "@project-serum/serum" "^0.13.21" + bn.js "^5.1.2" + superstruct "0.8.3" + "@project-serum/serum@^0.13.20": version "0.13.34" resolved "https://registry.yarnpkg.com/@project-serum/serum/-/serum-0.13.34.tgz#c76477c27e14d975afa38b6c352b3abe92af6e52" @@ -563,6 +573,15 @@ bn.js "^5.1.2" buffer-layout "^1.2.0" +"@project-serum/serum@^0.13.21": + version "0.13.38" + resolved "https://registry.yarnpkg.com/@project-serum/serum/-/serum-0.13.38.tgz#ef50a0f50bd69fd7b51309fbb44ad995a1e6e210" + integrity sha512-TOph1Hxoi5kOUg72tWbbNGviqBw29SrP4BH70gXtqUDiFTQJLrE2yfS5HC7p0JaU8p9WdrYGnxcFKCddZJ3ing== + dependencies: + "@solana/web3.js" "^0.90.0" + bn.js "^5.1.2" + buffer-layout "^1.2.0" + "@project-serum/sol-wallet-adapter@^0.1.4": version "0.1.8" resolved "https://registry.yarnpkg.com/@project-serum/sol-wallet-adapter/-/sol-wallet-adapter-0.1.8.tgz#90c6c1da793d32ed4ba3c67c5702a5bc804ef197" @@ -585,7 +604,7 @@ dependencies: "@sinonjs/commons" "^1.7.0" -"@solana/spl-token@0.0.13", "@solana/spl-token@^0.0.13": +"@solana/spl-token@0.0.13": version "0.0.13" resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.0.13.tgz#5e0b235b1f8b34643280401dbfddeb34d13d1acd" integrity sha512-WT8M9V/hxURR5jLbhr3zgwVsgcY6m8UhHtK045w7o+jx8FJ9MKARkj387WBFU7mKiFq0k8jw/8YL7XmnIUuH8Q== @@ -643,6 +662,25 @@ tweetnacl "^1.0.0" ws "^7.0.0" +"@solana/web3.js@^0.95.0": + version "0.95.0" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-0.95.0.tgz#c028c800bf078bec90945240370df51026f8bdab" + integrity sha512-jdjAESYTChAYI1aZwH1RggcoT22BxdB9tDKBOt2Iw9SSpZ1Bb81ydrP+7eksE1AwQj6lpzrgtVt2d3sFosqupQ== + dependencies: + "@babel/runtime" "^7.12.5" + bn.js "^5.0.0" + bs58 "^4.0.1" + buffer "6.0.1" + buffer-layout "^1.2.0" + crypto-hash "^1.2.2" + jayson "^3.4.4" + js-sha3 "^0.8.0" + node-fetch "^2.6.1" + rpc-websockets "^7.4.2" + secp256k1 "^4.0.2" + superstruct "^0.14.2" + tweetnacl "^1.0.0" + "@tsconfig/node14@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.0.tgz#5bd046e508b1ee90bc091766758838741fdefd6e" @@ -696,9 +734,9 @@ "@types/node" "*" "@types/express-serve-static-core@^4.17.9": - version "4.17.19" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz#00acfc1632e729acac4f1530e9e16f6dd1508a1d" - integrity sha512-DJOSHzX7pCiSElWaGR8kCprwibCB/3yW6vcT8VG3P0SJjnv19gnWG/AZMfM60Xj/YJIp/YCaDHyvzsFVeniARA== + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.21.tgz#a427278e106bca77b83ad85221eae709a3414d42" + integrity sha512-gwCiEZqW6f7EoR8TTEfalyEhb1zA5jQJnRngr97+3pzMaO1RKoI1w2bw07TK72renMUVWcWS5mLI6rk1NqN0nA== dependencies: "@types/node" "*" "@types/qs" "*" @@ -744,19 +782,19 @@ integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== "@types/lodash@^4.14.159": - version "4.14.169" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.169.tgz#83c217688f07a4d9ef8f28a3ebd1d318f6ff4cbb" - integrity sha512-DvmZHoHTFJ8zhVYwCLWbQ7uAbYQEk52Ev2/ZiQ7Y7gQGeV9pjBqjnQpECMHfKS1rCYAhMI7LHVxwyZLZinJgdw== + version "4.14.170" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.170.tgz#0d67711d4bf7f4ca5147e9091b847479b87925d6" + integrity sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q== "@types/node@*": - version "15.0.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-15.0.3.tgz#ee09fcaac513576474c327da5818d421b98db88a" - integrity sha512-/WbxFeBU+0F79z9RdEOXH4CsDga+ibi5M8uEYr91u3CkT/pdWcV8MCook+4wDPnZBexRdwWS+PiVZ2xJviAzcQ== + version "15.12.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.1.tgz#9b60797dee1895383a725f828a869c86c6caa5c2" + integrity sha512-zyxJM8I1c9q5sRMtVF+zdd13Jt6RU4r4qfhTd7lQubyThvLfx6yYekWSQjGCGV2Tkecgxnlpl/DNlb6Hg+dmEw== "@types/node@^12.12.54": - version "12.20.13" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.13.tgz#e743bae112bd779ac9650f907197dd2caa7f0364" - integrity sha512-1x8W5OpxPq+T85OUsHRP6BqXeosKmeXRtjoF39STcdf/UWLqUsoehstZKOi0CunhVqHG17AyZgpj20eRVooK6A== + version "12.20.14" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.14.tgz#9caf7eea0df08b406829889cc015256a6d81ab10" + integrity sha512-iFJOS5Q470FF+r4Ol2pSley7/wCNVqf+jgjhtxLLaJcDs+To2iCxlXIkJXrGLD9w9G/oJ9ibySu7z92DCwr7Pg== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -1201,9 +1239,9 @@ boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -"borsh@https://github.com/defactojob/borsh-js#field-mapper": +"borsh@git+https://github.com/defactojob/borsh-js.git#field-mapper": version "0.3.1" - resolved "https://github.com/defactojob/borsh-js#33a0d24af281112c0a48efb3fa503f3212443de9" + resolved "git+https://github.com/defactojob/borsh-js.git#33a0d24af281112c0a48efb3fa503f3212443de9" dependencies: "@types/bn.js" "^4.11.5" bn.js "^5.0.0" @@ -1293,6 +1331,14 @@ buffer-layout@^1.2.0: resolved "https://registry.yarnpkg.com/buffer-layout/-/buffer-layout-1.2.1.tgz#17b6db7abea303cab35eebd77919b89de026297d" integrity sha512-RUTGEYG1vX0Zp1dStQFl8yeU/LEBPXVtHwzzDbPWkE5zq+Prt9fkFLKNiwmaeHg6BBiRMcQAgj4cynazO6eekw== +buffer@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.1.tgz#3cbea8c1463e5a0779e30b66d4c88c6ffa182ac2" + integrity sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + buffer@^5.4.3: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -2923,10 +2969,10 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jayson@^3.0.1: - version "3.6.2" - resolved "https://registry.yarnpkg.com/jayson/-/jayson-3.6.2.tgz#e551e25abf2efe333051a6ed88b10f08c5288f50" - integrity sha512-hbl+x2xH6FT7nckw+Pq3lKOIJaMBKOgNJEVfvloDBWB8iSfzn/1U2igj1A5rplqNMFN/OnnaTNw8qPKVmoq83Q== +jayson@^3.0.1, jayson@^3.4.4: + version "3.6.3" + resolved "https://registry.yarnpkg.com/jayson/-/jayson-3.6.3.tgz#b0bb8d2e37e34e39e68044ab925fd92f103f1bd9" + integrity sha512-H/JuWKaJwU8FbwofPHROvtGoMF6R3DB0GGPpYyIaRzXU50Ser/4likFVfo/bpTGe0csB7n/+uybxJpBvX40VOQ== dependencies: "@types/connect" "^3.4.33" "@types/express-serve-static-core" "^4.17.9" @@ -3313,6 +3359,11 @@ jest@^26.4.0: import-local "^3.0.2" jest-cli "^26.6.3" +js-sha3@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -3784,7 +3835,7 @@ node-addon-api@^2.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== -node-fetch@^2.2.0: +node-fetch@^2.2.0, node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== @@ -4361,16 +4412,16 @@ rimraf@^3.0.0, rimraf@^3.0.2: glob "^7.1.3" rpc-websockets@^7.4.2: - version "7.4.11" - resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.4.11.tgz#ac8105116f1a888fbc3dd6b8394ea135ee59499c" - integrity sha512-/6yKCkRrEEb+TlJb6Q/pNBD4WdO/tFxE22rQYBl1YyIgz3SpzQDQ/0qAMWWksjFkDayiq3xVxmkP8e/tL422ZA== + version "7.4.12" + resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.4.12.tgz#6a187a772cbe9ee48ed04e001b6d9c29b8e69bae" + integrity sha512-WxZRM4443SiYbJhsLwVJc6P/VAQJIkeDS89CQAuHqyMt/GX8GEplWZezcLw6MMGemzA6Kp32kz7CbQppMTLQxA== dependencies: "@babel/runtime" "^7.11.2" assert-args "^1.2.1" circular-json "^0.5.9" eventemitter3 "^4.0.7" uuid "^8.3.0" - ws "^7.3.1" + ws "^7.4.5" optionalDependencies: bufferutil "^4.0.1" utf-8-validate "^5.0.2" @@ -4753,6 +4804,19 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +superstruct@0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.8.3.tgz#fb4d8901aca3bf9f79afab1bbab7a7f335cc4ef2" + integrity sha512-LbtbFpktW1FcwxVIJlxdk7bCyBq/GzOx2FSFLRLTUhWIA1gHkYPIl3aXRG5mBdGZtnPNT6t+4eEcLDCMOuBHww== + dependencies: + kind-of "^6.0.2" + tiny-invariant "^1.0.6" + +superstruct@^0.14.2: + version "0.14.2" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.14.2.tgz#0dbcdf3d83676588828f1cf5ed35cda02f59025b" + integrity sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ== + superstruct@^0.8.3: version "0.8.4" resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.8.4.tgz#478a19649f6b02c6319c02044db6a1f5863c391f" @@ -5249,7 +5313,12 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@^7.0.0, ws@^7.3.1, ws@^7.4.4: +ws@^7.0.0, ws@^7.4.5: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + +ws@^7.4.4: version "7.4.5" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.5.tgz#a484dd851e9beb6fdb420027e3885e8ce48986c1" integrity sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==