Upgrade mango-client

This commit is contained in:
Riordan Panayides 2022-04-12 12:13:19 +01:00
parent 14736b4308
commit 410bfc5691
3 changed files with 188 additions and 92 deletions

View File

@ -37,7 +37,7 @@
"trailingComma": "all"
},
"dependencies": {
"@blockworks-foundation/mango-client": "latest",
"@blockworks-foundation/mango-client": "^3.4.3",
"@project-serum/anchor": "^0.16.2",
"@project-serum/serum": "0.13.55",
"@project-serum/sol-wallet-adapter": "^0.2.0",

View File

@ -22,14 +22,19 @@ import {
sleep,
ZERO_I80F48,
} from '@blockworks-foundation/mango-client';
import { Account, Commitment, Connection, PublicKey } from '@solana/web3.js';
import {
Commitment,
Connection,
Keypair,
PublicKey,
} from '@solana/web3.js';
import { Market, OpenOrders } from '@project-serum/serum';
import BN from 'bn.js';
import { Orderbook } from '@project-serum/serum/lib/market';
import axios from 'axios';
import * as Env from 'dotenv';
import envExpand from 'dotenv-expand';
import {Client as RpcWebSocketClient} from 'rpc-websockets';
import { Client as RpcWebSocketClient } from 'rpc-websockets';
import { AsyncBlockingQueue } from './AsyncBlockingQueue';
envExpand(Env.config());
@ -41,7 +46,8 @@ const refreshAccountsInterval = parseInt(
const refreshWebsocketInterval = parseInt(
process.env.INTERVAL_WEBSOCKET || '300000',
);
const liquidatableFeedWebsocketAddress = process.env.LIQUIDATABLE_FEED_WEBSOCKET_ADDRESS;
const liquidatableFeedWebsocketAddress =
process.env.LIQUIDATABLE_FEED_WEBSOCKET_ADDRESS;
const rebalanceInterval = parseInt(process.env.INTERVAL_REBALANCE || '10000');
const checkTriggers = process.env.CHECK_TRIGGERS
? process.env.CHECK_TRIGGERS === 'true'
@ -55,36 +61,43 @@ const config = new Config(IDS);
const cluster = (process.env.CLUSTER || 'mainnet') as Cluster;
const groupName = process.env.GROUP || 'mainnet.1';
const groupIds = config.getGroup(cluster, groupName) ?? (() => { throw new Error(`Group ${groupName} not found`); })();
const groupIds =
config.getGroup(cluster, groupName) ??
(() => {
throw new Error(`Group ${groupName} not found`);
})();
// Target values to keep in spot, ordered the same as in mango client's ids.json
// Example:
//
// MNGO BTC ETH SOL USDT SRM RAY COPE FTT MSOL
// TARGETS=0 0 0 1 0 0 0 0 0 0
const TARGETS = process.env.TARGETS?.replace(/\s+/g,' ').trim().split(' ').map((s) => parseFloat(s))
?? [0, 0, 0, 0, 0, 0, 0, 0, 0];
const TARGETS = process.env.TARGETS?.replace(/\s+/g, ' ')
.trim()
.split(' ')
.map((s) => parseFloat(s)) ?? [0, 0, 0, 0, 0, 0, 0, 0, 0];
// Do not liquidate accounts that have less than this much in value
const minEquity = parseInt(
process.env.MIN_EQUITY || '0',
);
if(minEquity > 0) {
const minEquity = parseInt(process.env.MIN_EQUITY || '0');
if (minEquity > 0) {
console.log(`Minimum equity required to liquidate: ${minEquity}`);
}
const mangoProgramId = groupIds.mangoProgramId;
const mangoGroupKey = groupIds.publicKey;
const payer = new Account(
JSON.parse(
process.env.PRIVATE_KEY ||
fs.readFileSync(
process.env.KEYPAIR || os.homedir() + '/.config/solana/id.json',
'utf-8',
),
const payer = Keypair.fromSecretKey(
Uint8Array.from(
JSON.parse(
process.env.PRIVATE_KEY ||
fs.readFileSync(
process.env.KEYPAIR || os.homedir() + '/.config/solana/id.json',
'utf-8',
),
),
),
);
console.log(`Payer: ${payer.publicKey.toBase58()}`);
const rpcEndpoint = process.env.ENDPOINT_URL || config.cluster_urls[cluster];
const connection = new Connection(rpcEndpoint, 'processed' as Commitment);
@ -274,56 +287,53 @@ async function liquidatableFromSolanaRpc() {
}
}
async function maybeLiquidateAccount(mangoAccount: MangoAccount): Promise<boolean> {
const mangoAccountKeyString = mangoAccount.publicKey.toBase58();
async function maybeLiquidateAccount(
mangoAccount: MangoAccount,
): Promise<boolean> {
const mangoAccountKeyString = mangoAccount.publicKey.toBase58();
if (!mangoAccount.isLiquidatable(mangoGroup, cache)) {
console.log(
`Account ${mangoAccountKeyString} no longer liquidatable`,
);
return false;
}
if (!mangoAccount.isLiquidatable(mangoGroup, cache)) {
console.log(`Account ${mangoAccountKeyString} no longer liquidatable`);
return false;
}
const equity = mangoAccount.computeValue(mangoGroup, cache).toNumber()
if (equity < minEquity && minEquity > 0) {
// console.log(`Account ${mangoAccountKeyString} only has ${equity}, PASS`);
return false;
}
const equity = mangoAccount.computeValue(mangoGroup, cache).toNumber();
if (equity < minEquity && minEquity > 0) {
// console.log(`Account ${mangoAccountKeyString} only has ${equity}, PASS`);
return false;
}
const health = mangoAccount.getHealthRatio(mangoGroup, cache, 'Maint');
const accountInfoString = mangoAccount.toPrettyString(
groupIds,
const health = mangoAccount.getHealthRatio(mangoGroup, cache, 'Maint');
const accountInfoString = mangoAccount.toPrettyString(
groupIds,
mangoGroup,
cache,
);
console.log(
`Sick account ${mangoAccountKeyString} health ratio: ${health.toString()}\n${accountInfoString}`,
);
notify(`Sick account\n${accountInfoString}`);
try {
await liquidateAccount(
mangoGroup,
cache,
spotMarkets,
rootBanks,
perpMarkets,
mangoAccount,
liqorMangoAccount,
);
console.log(
`Sick account ${mangoAccountKeyString} health ratio: ${health.toString()}\n${accountInfoString}`,
console.log('Liquidated account', mangoAccountKeyString);
notify(`Liquidated account ${mangoAccountKeyString}`);
} catch (err: any) {
console.error(
`Failed to liquidate account ${mangoAccountKeyString}: ${err}`,
);
notify(`Sick account\n${accountInfoString}`);
try {
await liquidateAccount(
mangoGroup,
cache,
spotMarkets,
rootBanks,
perpMarkets,
mangoAccount,
liqorMangoAccount,
);
notify(`Failed to liquidate account ${mangoAccountKeyString}: ${err}`);
}
console.log('Liquidated account', mangoAccountKeyString);
notify(`Liquidated account ${mangoAccountKeyString}`);
} catch (err: any) {
console.error(
`Failed to liquidate account ${mangoAccountKeyString}: ${err}`,
);
notify(
`Failed to liquidate account ${mangoAccountKeyString}: ${err}`,
);
}
return true;
return true;
}
async function newAccountOnLiquidatableFeed(account) {
@ -332,7 +342,7 @@ async function newAccountOnLiquidatableFeed(account) {
const mangoAccountKey = new PublicKey(account);
const mangoAccount = new MangoAccount(mangoAccountKey, null);
[cache, liqorMangoAccount, ] = await Promise.all([
[cache, liqorMangoAccount] = await Promise.all([
mangoGroup.loadCache(connection),
liqorMangoAccount.reload(connection, mangoGroup.dexProgramId),
mangoAccount.reload(connection, mangoGroup.dexProgramId),
@ -364,16 +374,16 @@ async function liquidatableFromLiquidatableFeed() {
const ws = new RpcWebSocketClient(liquidatableFeedWebsocketAddress, {
max_reconnects: Infinity,
});
ws.on('open', (x) => console.log("opened liquidatable feed"));
ws.on('error', (status) => console.log("error on liquidatable feed", status));
ws.on('close', (err) => console.log("closed liquidatable feed", err));
ws.on('open', (x) => console.log('opened liquidatable feed'));
ws.on('error', (status) => console.log('error on liquidatable feed', status));
ws.on('close', (err) => console.log('closed liquidatable feed', err));
ws.on('candidate', (params) => {
const account = params.account;
if (!candidatesSet.has(account)) {
candidatesSet.add(account);
candidates.enqueue(account);
console.log(`Enqueued ${account.publicKey.toBase58()}`)
}
const account = params.account;
if (!candidatesSet.has(account)) {
candidatesSet.add(account);
candidates.enqueue(account);
console.log(`Enqueued ${account.publicKey.toBase58()}`);
}
});
while (true) {
@ -567,7 +577,7 @@ async function liquidateAccount(
rootBanks: (RootBank | undefined)[],
perpMarkets: PerpMarket[],
liqee: MangoAccount,
liqor: MangoAccount
liqor: MangoAccount,
) {
const hasPerpOpenOrders = liqee.perpAccounts.some(
(pa) => pa.bidsQuantity.gt(ZERO_BN) || pa.asksQuantity.gt(ZERO_BN),
@ -830,7 +840,7 @@ async function liquidatePerps(
perpMarkets: PerpMarket[],
rootBanks: (RootBank | undefined)[],
liqee: MangoAccount,
liqor: MangoAccount
liqor: MangoAccount,
) {
console.log('liquidatePerps');
const lowestHealthMarket = perpMarkets
@ -894,9 +904,17 @@ async function liquidatePerps(
// token liquidation of 0.
//
// https://discord.com/channels/791995070613159966/826034521261604874/934629112734167060
if (quoteRootBank) {
await client.settlePosPnl(mangoGroup, cache, liqee, perpMarkets, quoteRootBank, payer, mangoAccounts)
await client.settlePosPnl(
mangoGroup,
cache,
liqee,
perpMarkets,
quoteRootBank,
payer,
mangoAccounts,
);
}
for (let i = 0; i < mangoGroup.tokens.length; i++) {
@ -1017,7 +1035,13 @@ function getDiffsAndNet(
const marketIndex = groupIds!.spotMarkets[i].marketIndex;
const diff = mangoAccount
.getUiDeposit(cache.rootBankCache[marketIndex], mangoGroup, marketIndex)
.sub(mangoAccount.getUiBorrow(cache.rootBankCache[marketIndex], mangoGroup, marketIndex))
.sub(
mangoAccount.getUiBorrow(
cache.rootBankCache[marketIndex],
mangoGroup,
marketIndex,
),
)
.sub(I80F48.fromNumber(target));
diffs.push(diff);
netValues.push([i, diff.mul(cache.priceCache[i].price), marketIndex]);
@ -1075,7 +1099,7 @@ async function balanceTokens(
console.log('balanceTokens');
await mangoAccount.reload(connection, mangoGroup.dexProgramId);
const cache = await mangoGroup.loadCache(connection);
const cancelOrdersPromises: Promise<string>[] = [];
const cancelOrdersPromises: Promise<string | undefined>[] = [];
const bidsInfo = await getMultipleAccounts(
connection,
markets.map((m) => m.bidsAddress),
@ -1116,7 +1140,7 @@ async function balanceTokens(
connection,
mangoGroup.dexProgramId,
);
const settlePromises: Promise<string>[] = [];
const settlePromises: Promise<string | undefined>[] = [];
for (let i = 0; i < markets.length; i++) {
const marketIndex = mangoGroup.getSpotMarketIndex(markets[i].publicKey);
const oo = openOrders[marketIndex];
@ -1143,8 +1167,12 @@ async function balanceTokens(
for (let i = 0; i < groupIds!.spotMarkets.length; i++) {
const marketIndex = netValues[i][2];
const netIndex = netValues[i][0];
const marketConfig = groupIds!.spotMarkets.find((m) => m.marketIndex == marketIndex)!
const market = markets.find((m) => m.publicKey.equals(mangoGroup.spotMarkets[marketIndex].spotMarket))!;
const marketConfig = groupIds!.spotMarkets.find(
(m) => m.marketIndex == marketIndex,
)!;
const market = markets.find((m) =>
m.publicKey.equals(mangoGroup.spotMarkets[marketIndex].spotMarket),
)!;
const liquidationFee = mangoGroup.spotMarkets[marketIndex].liquidationFee;
if (Math.abs(diffs[netIndex].toNumber()) > market!.minOrderSize) {
const side = netValues[i][1].gt(ZERO_I80F48) ? 'sell' : 'buy';

View File

@ -37,20 +37,22 @@
dependencies:
regenerator-runtime "^0.13.4"
"@blockworks-foundation/mango-client@latest":
version "3.2.24"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-3.2.24.tgz#c46f2cff3dca06d88e304610643f40fddbf0ee76"
integrity sha512-1rWvmMBK1wGVLG7UfTpHI2FPnXfRNTLFWifGa9q/FkRLp07GzGDY3fGhquJW2NxGv2KPwzX/YJEAHX+2x485OA==
"@blockworks-foundation/mango-client@^3.4.3":
version "3.4.3"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-3.4.3.tgz#a5d72cc4520736edf843caaf8aff5376d1c056a4"
integrity sha512-8JFyE3nlwolfcqbhAYIenv25VnNvvYcEIUUUczHc31WaCtrcwC96Epj7wprHQO4OP6uLc9TBF4zuuGlZY3Fd6Q==
dependencies:
"@project-serum/anchor" "^0.16.2"
"@project-serum/anchor" "^0.21.0"
"@project-serum/serum" "0.13.55"
"@project-serum/sol-wallet-adapter" "^0.2.0"
"@solana/spl-token" "^0.1.6"
"@solana/web3.js" "^1.31.0"
big.js "^6.1.1"
bn.js "^5.2.0"
bn.js "^5.1.0"
buffer-layout "^1.2.1"
cross-fetch "^3.1.5"
dotenv "^10.0.0"
toformat "^2.0.0"
yargs "^17.0.1"
"@eslint/eslintrc@^0.4.3":
@ -164,6 +166,27 @@
snake-case "^3.0.4"
toml "^3.0.0"
"@project-serum/anchor@^0.21.0":
version "0.21.0"
resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.21.0.tgz#ad5fb33744991ec1900cdb2fd22707c908b12b5f"
integrity sha512-flRuW/F+iC8mitNokx82LOXyND7Dyk6n5UUPJpQv/+NfySFrNFlzuQZaBZJ4CG5g9s8HS/uaaIz1nVkDR8V/QA==
dependencies:
"@project-serum/borsh" "^0.2.4"
"@solana/web3.js" "^1.17.0"
base64-js "^1.5.1"
bn.js "^5.1.2"
bs58 "^4.0.1"
buffer-layout "^1.2.2"
camelcase "^5.3.1"
cross-fetch "^3.1.5"
crypto-hash "^1.3.0"
eventemitter3 "^4.0.7"
find "^0.3.0"
js-sha256 "^0.9.0"
pako "^2.0.3"
snake-case "^3.0.4"
toml "^3.0.0"
"@project-serum/borsh@^0.2.2":
version "0.2.2"
resolved "https://registry.yarnpkg.com/@project-serum/borsh/-/borsh-0.2.2.tgz#63e558f2d6eb6ab79086bf499dea94da3182498f"
@ -172,6 +195,14 @@
bn.js "^5.1.2"
buffer-layout "^1.2.0"
"@project-serum/borsh@^0.2.4":
version "0.2.5"
resolved "https://registry.yarnpkg.com/@project-serum/borsh/-/borsh-0.2.5.tgz#6059287aa624ecebbfc0edd35e4c28ff987d8663"
integrity sha512-UmeUkUoKdQ7rhx6Leve1SssMR/Ghv8qrEiyywyxSWg7ooV7StdpPBhciiy5eB3T0qU1BXvdRNC8TdrkxK7WC5Q==
dependencies:
bn.js "^5.1.2"
buffer-layout "^1.2.0"
"@project-serum/serum@0.13.55":
version "0.13.55"
resolved "https://registry.yarnpkg.com/@project-serum/serum/-/serum-0.13.55.tgz#2ac44fe7b07651274eb57ac54ea9325789df5dd7"
@ -654,7 +685,7 @@ buffer-from@^1.0.0:
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
buffer-layout@^1.2.0, buffer-layout@^1.2.1:
buffer-layout@^1.2.0, buffer-layout@^1.2.1, buffer-layout@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/buffer-layout/-/buffer-layout-1.2.2.tgz#b9814e7c7235783085f9ca4966a0cfff112259d5"
integrity sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA==
@ -813,6 +844,13 @@ cross-fetch@^3.1.4:
dependencies:
node-fetch "2.6.1"
cross-fetch@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
dependencies:
node-fetch "2.6.7"
cross-spawn@^7.0.1, cross-spawn@^7.0.2:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@ -1651,6 +1689,13 @@ node-fetch@2.6.1:
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-fetch@2.6.7:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
dependencies:
whatwg-url "^5.0.0"
node-gyp-build@^4.2.0, node-gyp-build@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3"
@ -2023,11 +2068,21 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
toformat@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/toformat/-/toformat-2.0.0.tgz#7a043fd2dfbe9021a4e36e508835ba32056739d8"
integrity sha512-03SWBVop6nU8bpyZCx7SodpYznbZF5R4ljwNLBcTQzKOD9xuihRo/psX58llS1BMFhhAI08H3luot5GoXJz2pQ==
toml@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee"
integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
traverse-chain@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/traverse-chain/-/traverse-chain-0.1.0.tgz#61dbc2d53b69ff6091a12a168fd7d433107e40f1"
@ -2129,6 +2184,19 @@ vscode-textmate@5.2.0:
resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.2.0.tgz#01f01760a391e8222fe4f33fbccbd1ad71aed74e"
integrity sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
which@2.0.2, which@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
@ -2191,9 +2259,9 @@ yargs-parser@^20.2.2:
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
yargs-parser@^21.0.0:
version "21.0.0"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.0.tgz#a485d3966be4317426dd56bdb6a30131b281dc55"
integrity sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==
version "21.0.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35"
integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==
yargs-unparser@2.0.0:
version "2.0.0"
@ -2219,9 +2287,9 @@ yargs@16.2.0:
yargs-parser "^20.2.2"
yargs@^17.0.1:
version "17.3.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.3.1.tgz#da56b28f32e2fd45aefb402ed9c26f42be4c07b9"
integrity sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==
version "17.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.4.1.tgz#ebe23284207bb75cee7c408c33e722bfb27b5284"
integrity sha512-WSZD9jgobAg3ZKuCQZSa3g9QOJeCCqLoLAykiWgmXnDo9EPnn4RPf5qVTtzgOx66o6/oqhcA5tHtJXpG8pMt3g==
dependencies:
cliui "^7.0.2"
escalade "^3.1.1"