Merge pull request #210 from blockworks-foundation/ckamm/liq-force-close

Liquidate serum open orders
This commit is contained in:
Christian Kamm 2022-09-02 13:15:42 +02:00 committed by GitHub
commit 14913c70d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 374 additions and 117 deletions

1
Cargo.lock generated
View File

@ -3092,6 +3092,7 @@ dependencies = [
"futures 0.3.21",
"futures-core",
"futures-util",
"itertools 0.10.3",
"jemallocator",
"jsonrpc-core 18.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-core-client",

View File

@ -511,12 +511,19 @@ impl MangoClient {
.map_err(prettify_client_error)
}
fn serum3_data<'a>(&'a self, name: &str) -> Result<Serum3Data<'a>, ClientError> {
fn serum3_data_by_market_name<'a>(&'a self, name: &str) -> Result<Serum3Data<'a>, ClientError> {
let market_index = *self
.context
.serum3_market_indexes_by_name
.get(name)
.unwrap();
self.serum3_data_by_market_index(market_index)
}
fn serum3_data_by_market_index<'a>(
&'a self,
market_index: Serum3MarketIndex,
) -> Result<Serum3Data<'a>, ClientError> {
let serum3_info = self.context.serum3_markets.get(&market_index).unwrap();
let quote_info = self.context.token(serum3_info.market.quote_token_index);
@ -542,7 +549,7 @@ impl MangoClient {
client_order_id: u64,
limit: u16,
) -> anyhow::Result<Signature> {
let s3 = self.serum3_data(name)?;
let s3 = self.serum3_data_by_market_name(name)?;
let account = self.mango_account()?;
let open_orders = account.serum3_orders(s3.market_index).unwrap().open_orders;
@ -659,7 +666,7 @@ impl MangoClient {
}
pub fn serum3_settle_funds(&self, name: &str) -> anyhow::Result<Signature> {
let s3 = self.serum3_data(name)?;
let s3 = self.serum3_data_by_market_name(name)?;
let account = self.mango_account()?;
let open_orders = account.serum3_orders(s3.market_index).unwrap().open_orders;
@ -727,13 +734,64 @@ impl MangoClient {
Ok(orders)
}
pub fn serum3_liq_force_cancel_orders(
&self,
liqee: (&Pubkey, &MangoAccountValue),
market_index: Serum3MarketIndex,
open_orders: &Pubkey,
) -> anyhow::Result<Signature> {
let s3 = self.serum3_data_by_market_index(market_index)?;
let health_remaining_ams = self
.context
.derive_health_check_remaining_account_metas(liqee.1, vec![], false)
.unwrap();
self.program()
.request()
.instruction(Instruction {
program_id: mango_v4::id(),
accounts: {
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::Serum3LiqForceCancelOrders {
group: self.group(),
account: *liqee.0,
open_orders: *open_orders,
serum_market: s3.market.address,
serum_program: s3.market.market.serum_program,
serum_market_external: s3.market.market.serum_market_external,
market_bids: s3.market.bids,
market_asks: s3.market.asks,
market_event_queue: s3.market.event_q,
market_base_vault: s3.market.coin_vault,
market_quote_vault: s3.market.pc_vault,
market_vault_signer: s3.market.vault_signer,
quote_bank: s3.quote.mint_info.first_bank(),
quote_vault: s3.quote.mint_info.first_vault(),
base_bank: s3.base.mint_info.first_bank(),
base_vault: s3.base.mint_info.first_vault(),
token_program: Token::id(),
},
None,
);
ams.extend(health_remaining_ams.into_iter());
ams
},
data: anchor_lang::InstructionData::data(
&mango_v4::instruction::Serum3LiqForceCancelOrders { limit: 5 },
),
})
.send()
.map_err(prettify_client_error)
}
pub fn serum3_cancel_order(
&self,
market_name: &str,
side: Serum3Side,
order_id: u128,
) -> anyhow::Result<()> {
let s3 = self.serum3_data(market_name)?;
let s3 = self.serum3_data_by_market_name(market_name)?;
let account = self.mango_account()?;
let open_orders = account.serum3_orders(s3.market_index).unwrap().open_orders;

View File

@ -22,6 +22,7 @@ fixed = { version = "=1.11.0", features = ["serde"] }
futures = "0.3.17"
futures-core = "0.3"
futures-util = "0.3"
itertools = "0.10.3"
jemallocator = "0.3.2"
jsonrpc-core = "18.0.0"
jsonrpc-core-client = { version = "18.0.0", features = ["ws", "http", "tls"] }

View File

@ -5,9 +5,11 @@ use crate::account_shared_data::KeyedAccountSharedData;
use client::{chain_data, AccountFetcher, MangoClient, MangoClientError, MangoGroupContext};
use mango_v4::state::{
new_health_cache, Bank, FixedOrderAccountRetriever, HealthCache, HealthType, MangoAccountValue,
TokenIndex, QUOTE_TOKEN_INDEX,
Serum3Orders, TokenIndex, QUOTE_TOKEN_INDEX,
};
use itertools::Itertools;
use rand::seq::SliceRandom;
use {anyhow::Context, fixed::types::I80F48, solana_sdk::pubkey::Pubkey};
pub struct Config {
@ -156,6 +158,24 @@ pub fn maybe_liquidate_account(
.collect::<anyhow::Result<Vec<(TokenIndex, I80F48, I80F48)>>>()?;
tokens.sort_by(|a, b| a.2.cmp(&b.2));
// look for any open serum orders or settleable balances
let serum_force_cancels = account
.active_serum3_orders()
.map(|orders| {
let open_orders_account = account_fetcher.fetch_raw_account(orders.open_orders)?;
let open_orders = mango_v4::serum3_cpi::load_open_orders(&open_orders_account)?;
let can_force_cancel = open_orders.native_coin_total > 0
|| open_orders.native_pc_total > 0
|| open_orders.referrer_rebates_accrued > 0;
if can_force_cancel {
Ok(Some(*orders))
} else {
Ok(None)
}
})
.filter_map_ok(|v| v)
.collect::<anyhow::Result<Vec<Serum3Orders>>>()?;
let get_max_liab_transfer = |source, target| -> anyhow::Result<I80F48> {
let mut liqor = account_fetcher
.fetch_fresh_mango_account(&mango_client.mango_account_address)
@ -175,7 +195,23 @@ pub fn maybe_liquidate_account(
};
// try liquidating
let txsig = if is_bankrupt {
let txsig = if !serum_force_cancels.is_empty() {
// pick a random market to force-cancel orders on
let serum_orders = serum_force_cancels.choose(&mut rand::thread_rng()).unwrap();
let sig = mango_client.serum3_liq_force_cancel_orders(
(pubkey, &account),
serum_orders.market_index,
&serum_orders.open_orders,
)?;
log::info!(
"Force cancelled serum market on account {}, market index {}, maint_health was {}, tx sig {:?}",
pubkey,
serum_orders.market_index,
maint_health,
sig
);
sig
} else if is_bankrupt {
if tokens.is_empty() {
anyhow::bail!("mango account {}, is bankrupt has no active tokens", pubkey);
}

View File

@ -318,6 +318,11 @@ async fn main() -> anyhow::Result<()> {
if one_snapshot_done {
if let Err(err) = rebalance::zero_all_non_quote(&mango_client, &account_fetcher, &cli.liqor_mango_account, &rebalance_config) {
log::error!("failed to rebalance liqor: {:?}", err);
// Workaround: We really need a sequence enforcer in the liquidator since we don't want to
// accidentally send a similar tx again when we incorrectly believe an earlier one got forked
// off. For now, hard sleep on error to avoid the most frequent error cases.
std::thread::sleep(Duration::from_secs(10));
}
}
}

View File

@ -186,7 +186,7 @@ pub fn zero_all_non_quote(
if !refresh_mango_account(account_fetcher, txsig)? {
return Ok(());
}
} else {
} else if amount > dust_threshold {
anyhow::bail!(
"unexpected {} position after rebalance swap: {} native",
token.name,

View File

@ -351,6 +351,12 @@ export class Group {
);
}
public findSerum3MarketByName(name: string): Serum3Market | undefined {
return Array.from(this.serum3MarketsMapByExternal.values()).find(
(serum3Market) => serum3Market.name === name,
);
}
public async loadSerum3BidsForMarket(
client: MangoClient,
externalMarketPk: PublicKey,

View File

@ -44,7 +44,7 @@ import {
import { SERUM3_PROGRAM_ID } from './constants';
import { Id } from './ids';
import { IDL, MangoV4 } from './mango_v4';
import { FlashLoanType } from './types';
import { FlashLoanType, InterestRateParams } from './types';
import {
createAssociatedTokenAccountIdempotentInstruction,
getAssociatedTokenAddress,
@ -266,37 +266,39 @@ export class MangoClient {
public async tokenEdit(
group: Group,
mintPk: PublicKey,
oracle: PublicKey,
oracleConfFilter: number,
groupInsuranceFund: boolean | undefined,
adjustmentFactor: number,
util0: number,
rate0: number,
util1: number,
rate1: number,
maxRate: number,
loanFeeRate: number,
loanOriginationFeeRate: number,
maintAssetWeight: number,
initAssetWeight: number,
maintLiabWeight: number,
initLiabWeight: number,
liquidationFee: number,
oracle: PublicKey | null,
oracleConfFilter: number | null,
groupInsuranceFund: boolean | null,
interestRateParams: InterestRateParams | null,
loanFeeRate: number | null,
loanOriginationFeeRate: number | null,
maintAssetWeight: number | null,
initAssetWeight: number | null,
maintLiabWeight: number | null,
initLiabWeight: number | null,
liquidationFee: number | null,
): Promise<TransactionSignature> {
const bank = group.getFirstBankByMint(mintPk);
const mintInfo = group.mintInfosMapByTokenIndex.get(bank.tokenIndex)!;
let oracleConf;
if (oracleConfFilter !== null) {
oracleConf = {
confFilter: {
val: I80F48.fromNumber(oracleConfFilter).getData(),
},
} as any; // future: nested custom types dont typecheck, fix if possible?
} else {
oracleConf = null;
}
return await this.program.methods
.tokenEdit(
new BN(0),
oracle,
{
confFilter: {
val: I80F48.fromNumber(oracleConfFilter).getData(),
},
} as any, // future: nested custom types dont typecheck, fix if possible?
groupInsuranceFund ?? null,
{ adjustmentFactor, util0, rate0, util1, rate1, maxRate },
oracleConf,
groupInsuranceFund,
interestRateParams,
loanFeeRate,
loanOriginationFeeRate,
maintAssetWeight,
@ -541,7 +543,7 @@ export class MangoClient {
name?: string,
): Promise<TransactionSignature> {
const transaction = await this.program.methods
.accountCreate(accountNumber ?? 0, 8, 0, 0, 0, name ?? '')
.accountCreate(accountNumber ?? 0, 8, 8, 0, 0, name ?? '')
.accounts({
group: group.publicKey,
owner: (this.program.provider as AnchorProvider).wallet.publicKey,

View File

@ -395,13 +395,15 @@ async function main() {
usdcDevnetMint,
usdcDevnetOracle.publicKey,
0.1,
undefined,
0.004,
0.7,
0.1,
0.85,
0.2,
2.0,
null,
{
adjustmentFactor: 0.004,
util0: 0.7,
rate0: 0.1,
util1: 0.85,
rate1: 0.2,
maxRate: 2.0,
},
0.005,
0.0005,
1,
@ -424,13 +426,15 @@ async function main() {
usdcDevnetMint,
usdcDevnetOracle.publicKey,
0.1,
undefined,
0.004,
0.7,
0.1,
0.85,
0.2,
2.0,
null,
{
adjustmentFactor: 0.004,
util0: 0.7,
rate0: 0.1,
util1: 0.85,
rate1: 0.2,
maxRate: 2.0,
},
0.005,
0.0005,
0.9,
@ -453,13 +457,15 @@ async function main() {
usdcDevnetMint,
usdcDevnetOracle.publicKey,
0.1,
undefined,
0.004,
0.7,
0.1,
0.85,
0.2,
2.0,
null,
{
adjustmentFactor: 0.004,
util0: 0.7,
rate0: 0.1,
util1: 0.85,
rate1: 0.2,
maxRate: 2.0,
},
0.005,
0.0005,
0.9,

View File

@ -13,7 +13,7 @@ const GROUP_NUM = process.env.GROUP_NUM;
async function main() {
const options = AnchorProvider.defaultOptions();
const connection = new Connection(process.env.MB_CLUSTER_URL, options);
const connection = new Connection(process.env.MB_CLUSTER_URL!, options);
const admin = Keypair.fromSecretKey(
Buffer.from(
@ -27,6 +27,8 @@ async function main() {
adminProvider,
'mainnet-beta',
MANGO_V4_ID['mainnet-beta'],
{},
'get-program-accounts',
);
const groups = await (async () => {
@ -53,16 +55,19 @@ async function main() {
}
// close all banks
for (const bank of group.banksMap.values()) {
sig = await client.tokenDeregister(group, bank.name);
for (const banks of group.banksMapByMint.values()) {
sig = await client.tokenDeregister(group, banks[0].mint);
console.log(
`Removed token ${bank.name}, sig https://explorer.solana.com/tx/${sig}`,
`Removed token ${banks[0].name}, sig https://explorer.solana.com/tx/${sig}`,
);
}
// deregister all serum markets
for (const market of group.serum3MarketsMap.values()) {
sig = await client.serum3deregisterMarket(group, market.name);
for (const market of group.serum3MarketsMapByExternal.values()) {
sig = await client.serum3deregisterMarket(
group,
market.serumMarketExternal,
);
console.log(
`Deregistered serum market ${market.name}, sig https://explorer.solana.com/tx/${sig}`,
);

View File

@ -10,7 +10,7 @@ import { MANGO_V4_ID } from '../constants';
//
// default to group 1, to not conflict with the normal group
const GROUP_NUM = Number(process.env.GROUP_NUM || 1);
const GROUP_NUM = Number(process.env.GROUP_NUM || 200);
const MAINNET_MINTS = new Map([
['USDC', 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'],
@ -24,9 +24,16 @@ const STUB_PRICES = new Map([
['SOL', 0.04], // sol has 9 decimals, equivalent to $40 per SOL
]);
// External markets are matched with those in https://github.com/blockworks-foundation/mango-client-v3/blob/main/src/ids.json
// and verified to have best liquidity for pair on https://openserum.io/
const MAINNET_SERUM3_MARKETS = new Map([
['BTC/USDC', 'A8YFbxQYFVqKZaoYJLLUVcQiWP7G2MeEgW5wsAQgMvFw'],
['SOL/USDC', '9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT'],
]);
async function main() {
const options = AnchorProvider.defaultOptions();
const connection = new Connection(process.env.CLUSTER_URL, options);
const connection = new Connection(process.env.CLUSTER_URL!, options);
const admin = Keypair.fromSecretKey(
Buffer.from(
@ -42,6 +49,8 @@ async function main() {
adminProvider,
'mainnet-beta',
MANGO_V4_ID['mainnet-beta'],
{},
'get-program-accounts',
);
// group
@ -61,7 +70,7 @@ async function main() {
console.log(`Creating stub oracle for ${name}...`);
const mintPk = new PublicKey(mint);
try {
const price = STUB_PRICES.get(name);
const price = STUB_PRICES.get(name)!;
await client.stubOracleCreate(group, mintPk, price);
} catch (error) {
console.log(error);
@ -71,37 +80,6 @@ async function main() {
oracles.set(name, oracle.publicKey);
}
// register token 1
console.log(`Registering BTC...`);
const btcMainnetMint = new PublicKey(MAINNET_MINTS.get('BTC')!);
const btcMainnetOracle = oracles.get('BTC');
try {
await client.tokenRegister(
group,
btcMainnetMint,
btcMainnetOracle,
0.1,
1,
'BTC',
0.01,
0.4,
0.07,
0.7,
0.88,
1.5,
0.0,
0.0001,
0.9,
0.8,
1.1,
1.2,
0.05,
);
await group.reloadAll(client);
} catch (error) {
console.log(error);
}
// register token 0
console.log(`Registering USDC...`);
const usdcMainnetMint = new PublicKey(MAINNET_MINTS.get('USDC')!);
@ -133,6 +111,37 @@ async function main() {
console.log(error);
}
// register token 1
console.log(`Registering BTC...`);
const btcMainnetMint = new PublicKey(MAINNET_MINTS.get('BTC')!);
const btcMainnetOracle = oracles.get('BTC');
try {
await client.tokenRegister(
group,
btcMainnetMint,
btcMainnetOracle,
0.1,
1,
'BTC',
0.01,
0.4,
0.07,
0.7,
0.88,
1.5,
0.0,
0.0001,
0.9,
0.8,
1.1,
1.2,
0.05,
);
await group.reloadAll(client);
} catch (error) {
console.log(error);
}
// register token 2
console.log(`Registering SOL...`);
const solMainnetMint = new PublicKey(MAINNET_MINTS.get('SOL')!);
@ -165,10 +174,20 @@ async function main() {
}
// log tokens/banks
for (const bank of await group.banksMap.values()) {
for (const bank of await group.banksMapByMint.values()) {
console.log(`${bank.toString()}`);
}
console.log('Registering SOL/USDC serum market...');
await client.serum3RegisterMarket(
group,
new PublicKey(MAINNET_SERUM3_MARKETS.get('SOL/USDC')!),
group.getFirstBankByMint(new PublicKey(MAINNET_MINTS.get('SOL')!)),
group.getFirstBankByMint(new PublicKey(MAINNET_MINTS.get('USDC')!)),
1,
'SOL/USDC',
);
process.exit();
}

View File

@ -8,12 +8,12 @@ import { MANGO_V4_ID } from '../constants';
// This script deposits some tokens, so other liquidation scripts can borrow.
//
const GROUP_NUM = Number(process.env.GROUP_NUM || 1);
const GROUP_NUM = Number(process.env.GROUP_NUM || 200);
const ACCOUNT_NUM = Number(process.env.ACCOUNT_NUM || 0);
async function main() {
const options = AnchorProvider.defaultOptions();
const connection = new Connection(process.env.CLUSTER_URL, options);
const connection = new Connection(process.env.CLUSTER_URL!, options);
const admin = Keypair.fromSecretKey(
Buffer.from(
@ -28,6 +28,8 @@ async function main() {
userProvider,
'mainnet-beta',
MANGO_V4_ID['mainnet-beta'],
{},
'get-program-accounts',
);
console.log(`User ${userWallet.publicKey.toBase58()}`);
@ -37,26 +39,31 @@ async function main() {
// create + fetch account
console.log(`Creating mangoaccount...`);
const mangoAccount = await client.getOrCreateMangoAccount(
const mangoAccount = (await client.getOrCreateMangoAccount(
group,
admin.publicKey,
ACCOUNT_NUM,
);
'LIQTEST, FUNDING',
))!;
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
console.log(mangoAccount.toString());
const usdcMint = group.banksMapByName.get('USDC')![0].mint;
const btcMint = group.banksMapByName.get('BTC')![0].mint;
const solMint = group.banksMapByName.get('SOL')![0].mint;
// deposit
try {
console.log(`...depositing 10 USDC`);
await client.tokenDeposit(group, mangoAccount, 'USDC', 10);
await client.tokenDeposit(group, mangoAccount, usdcMint, 10);
await mangoAccount.reload(client, group);
console.log(`...depositing 0.0004 BTC`);
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0004);
await client.tokenDeposit(group, mangoAccount, btcMint, 0.0004);
await mangoAccount.reload(client, group);
console.log(`...depositing 0.25 SOL`);
await client.tokenDeposit(group, mangoAccount, 'SOL', 0.25);
await client.tokenDeposit(group, mangoAccount, solMint, 0.25);
await mangoAccount.reload(client, group);
} catch (error) {
console.log(error);

View File

@ -1,6 +1,11 @@
import { AnchorProvider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import fs from 'fs';
import {
Serum3OrderType,
Serum3SelfTradeBehavior,
Serum3Side,
} from '../accounts/serum3';
import { MangoClient } from '../client';
import { MANGO_V4_ID } from '../constants';
@ -8,7 +13,7 @@ import { MANGO_V4_ID } from '../constants';
// This script creates liquidation candidates
//
const GROUP_NUM = Number(process.env.GROUP_NUM || 1);
const GROUP_NUM = Number(process.env.GROUP_NUM || 200);
// native prices
const PRICES = {
@ -23,17 +28,16 @@ const MAINNET_MINTS = new Map([
['SOL', 'So11111111111111111111111111111111111111112'],
]);
const SCENARIOS: [string, string, number, string, number][] = [
const TOKEN_SCENARIOS: [string, string, number, string, number][] = [
['LIQTEST, LIQOR', 'USDC', 1000000, 'USDC', 0],
['LIQTEST, A: USDC, L: SOL', 'USDC', 1000 * PRICES.SOL, 'SOL', 920],
['LIQTEST, A: SOL, L: USDC', 'SOL', 1000, 'USDC', 920 * PRICES.SOL],
// TODO: needs the fix on liq+dust to go live
//['LIQTEST, A: BTC, L: SOL', 'BTC', 20, 'SOL', 18 * PRICES.BTC / PRICES.SOL],
['LIQTEST, A: BTC, L: SOL', 'BTC', 20, 'SOL', 18 * PRICES.BTC / PRICES.SOL],
];
async function main() {
const options = AnchorProvider.defaultOptions();
const connection = new Connection(process.env.CLUSTER_URL, options);
const connection = new Connection(process.env.CLUSTER_URL!, options);
const admin = Keypair.fromSecretKey(
Buffer.from(
@ -48,6 +52,8 @@ async function main() {
userProvider,
'mainnet-beta',
MANGO_V4_ID['mainnet-beta'],
{},
'get-program-accounts',
);
console.log(`User ${userWallet.publicKey.toBase58()}`);
@ -59,41 +65,45 @@ async function main() {
group,
admin.publicKey,
);
let maxAccountNum = Math.max(...accounts.map((a) => a.accountNum));
let maxAccountNum = Math.max(0, ...accounts.map((a) => a.accountNum));
for (const scenario of SCENARIOS) {
for (const scenario of TOKEN_SCENARIOS) {
const [name, assetName, assetAmount, liabName, liabAmount] = scenario;
// create account
console.log(`Creating mangoaccount...`);
let mangoAccount = await client.getOrCreateMangoAccount(
let mangoAccount = (await client.getOrCreateMangoAccount(
group,
admin.publicKey,
maxAccountNum + 1,
);
name,
))!;
maxAccountNum = maxAccountNum + 1;
console.log(
`...created mangoAccount ${mangoAccount.publicKey} for ${name}`,
);
const assetMint = new PublicKey(MAINNET_MINTS.get(assetName)!);
const liabMint = new PublicKey(MAINNET_MINTS.get(liabName)!);
await client.tokenDepositNative(
group,
mangoAccount,
assetName,
assetMint,
assetAmount,
);
await mangoAccount.reload(client, group);
if (liabAmount > 0) {
// temporarily drop the borrowed token value, so the borrow goes through
const oracle = group.banksMap.get(liabName).oracle;
const oracle = group.banksMapByName.get(liabName)![0].oracle;
try {
await client.stubOracleSet(group, oracle, PRICES[liabName] / 2);
await client.tokenWithdrawNative(
group,
mangoAccount,
liabName,
liabMint,
liabAmount,
true,
);
@ -104,6 +114,80 @@ async function main() {
}
}
// Serum order scenario
{
const name = 'LIQTEST, serum orders';
console.log(`Creating mangoaccount...`);
let mangoAccount = (await client.getOrCreateMangoAccount(
group,
admin.publicKey,
maxAccountNum + 1,
name,
))!;
maxAccountNum = maxAccountNum + 1;
console.log(
`...created mangoAccount ${mangoAccount.publicKey} for ${name}`,
);
const market = group.findSerum3MarketByName('SOL/USDC')!;
const sellMint = new PublicKey(MAINNET_MINTS.get('USDC')!);
const buyMint = new PublicKey(MAINNET_MINTS.get('SOL')!);
await client.tokenDepositNative(group, mangoAccount, sellMint, 100000);
await mangoAccount.reload(client, group);
// temporarily up the init asset weight of the bought token
await client.tokenEdit(
group,
buyMint,
null,
null,
null,
null,
null,
null,
1.0,
1.0,
null,
null,
null,
);
try {
// At a price of $1/ui-SOL we can buy 0.1 ui-SOL for the 100k native-USDC we have.
// With maint weight of 0.9 we have 10x main-leverage. Buying 12x as much causes liquidation.
await client.serum3PlaceOrder(
group,
mangoAccount,
market.serumMarketExternal,
Serum3Side.bid,
1,
12 * 0.1,
Serum3SelfTradeBehavior.abortTransaction,
Serum3OrderType.limit,
0,
5,
);
} finally {
// restore the weights
await client.tokenEdit(
group,
buyMint,
null,
null,
null,
null,
null,
null,
0.9,
0.8,
null,
null,
null,
);
}
}
process.exit();
}

View File

@ -9,7 +9,7 @@ import { MANGO_V4_ID } from '../constants';
// by MANGO_MAINNET_PAYER_KEYPAIR in the group.
//
const GROUP_NUM = Number(process.env.GROUP_NUM || 2);
const GROUP_NUM = Number(process.env.GROUP_NUM || 200);
const CLUSTER_URL =
process.env.CLUSTER_URL ||
'https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88';
@ -18,7 +18,7 @@ const MANGO_MAINNET_PAYER_KEYPAIR =
async function main() {
const options = AnchorProvider.defaultOptions();
const connection = new Connection(CLUSTER_URL, options);
const connection = new Connection(CLUSTER_URL!, options);
const admin = Keypair.fromSecretKey(
Buffer.from(
@ -31,6 +31,8 @@ async function main() {
userProvider,
'mainnet-beta',
MANGO_V4_ID['mainnet-beta'],
{},
'get-program-accounts',
);
console.log(`User ${userWallet.publicKey.toBase58()}`);
@ -44,13 +46,27 @@ async function main() {
console.log(group.toString());
let accounts = await client.getMangoAccountsForOwner(group, admin.publicKey);
for (let account of accounts) {
for (let serumOrders of account.serum3Active()) {
const serumMarket = group.findSerum3Market(serumOrders.marketIndex)!;
const serumExternal = serumMarket.serumMarketExternal;
console.log(
`closing serum orders on: ${account} for market ${serumMarket.name}`,
);
await client.serum3CancelAllorders(group, account, serumExternal, 10);
await client.serum3SettleFunds(group, account, serumExternal);
await client.serum3CloseOpenOrders(group, account, serumExternal);
}
}
accounts = await client.getMangoAccountsForOwner(group, admin.publicKey);
for (let account of accounts) {
console.log(`settling borrows on account: ${account}`);
// first, settle all borrows
for (let token of account.tokensActive()) {
const bank = group.getFirstBankByTokenIndex(token.tokenIndex);
const amount = token.native(bank).toNumber();
const amount = token.balance(bank).toNumber();
if (amount < 0) {
try {
await client.tokenDepositNative(
@ -64,6 +80,7 @@ async function main() {
console.log(
`failed to deposit ${bank.name} into ${account.publicKey}: ${error}`,
);
process.exit();
}
}
}
@ -76,7 +93,7 @@ async function main() {
// withdraw all funds
for (let token of account.tokensActive()) {
const bank = group.getFirstBankByTokenIndex(token.tokenIndex);
const amount = token.native(bank).toNumber();
const amount = token.balance(bank).toNumber();
if (amount > 0) {
try {
const allowBorrow = false;
@ -92,6 +109,7 @@ async function main() {
console.log(
`failed to withdraw ${bank.name} from ${account.publicKey}: ${error}`,
);
process.exit();
}
}
}

View File

@ -9,3 +9,12 @@ export class FlashLoanType {
static unknown = { unknown: {} };
static swap = { swap: {} };
}
export class InterestRateParams {
util0: number;
rate0: number;
util1: number;
rate1: number;
maxRate: number;
adjustmentFactor: number;
}