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 <riordan@panayid.es>
Co-authored-by: dd <dafydd.durairaj@gmail.com>
This commit is contained in:
Maximilian Schneider 2021-06-11 15:53:17 -04:00 committed by GitHub
parent 39b5ab3b4f
commit ba1a874ebb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 461 additions and 273 deletions

View File

@ -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

View File

@ -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",

View File

@ -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()
// testing()

View File

@ -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),

View File

@ -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<string>[] = []
const cancelTransactions: Promise<string>[] = [];
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<string>[] = []
const settleTransactions: Promise<string>[] = [];
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();

View File

@ -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()
// testServer()

133
yarn.lock
View File

@ -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==